stanfordnlp/CoreNLP · error · RuntimeException

Label dictionary is already locked

Error message

Label dictionary is already locked

What it means

lock() finalizes the LabelDictionary: it retains frequent observations, builds the int[][] label dictionary and observation index, and frees the counters. It must run exactly once; a second call finds labelDictionary != null and throws to prevent destroying the already-computed constrained label sets.

Solutions

  1. Guard the call: only lock if !isLocked() (check the internal state or expose/track a boolean).
  2. Move lock() into a one-time initialization path that is provably executed once per dictionary.
  3. Use a fresh LabelDictionary if a new lock with different threshold/labelIndex is genuinely needed.

Example fix

// before
labelDict.lock(threshold, labelIndex); // second call throws
labelDict.lock(threshold, labelIndex);

// after
if (!labelDict.isLocked()) {
  labelDict.lock(threshold, labelIndex);
}
Defensive patterns

Strategy: validation

Validate before calling

// Java: make lock idempotent at the call site
if (!lockedDictionaries.contains(dict)) {
  dict.lock(threshold, labelIndex);
  lockedDictionaries.add(dict);
}

Try / catch

// Java
try {
  dict.lock(threshold, labelIndex);
} catch (RuntimeException e) {
  if (!e.getMessage().contains("already locked")) throw e;
  // safe to ignore: dictionary already finalized
}

Prevention

When it happens

Trigger: Calling lock(threshold, labelIndex) twice on the same LabelDictionary — e.g. a setup routine that may run multiple times (re-loading a model, re-running feature initialization) and invokes lock each time.

Common situations: Idempotency bugs: initialization code invoked on both first load and model reload; multiple CRF classifiers sharing one dictionary but each calling lock during their own setup.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stanfordnlp/CoreNLP@1b7edd19c4 (2026-09-10). Data as JSON: /api/errors/3ce3e8a649f4fd3d. Report an issue: GitHub.

Appendix: source

Thrown at src/edu/stanford/nlp/ie/crf/LabelDictionary.java:94

  /**
   * Get the allowed label set for an observation.
   *
   * @param observation
   * @return The allowed label set, or null if the observation is unconstrained.
   */
  public int[] getConstrainedSet(String observation) {
    int i = observationIndex.indexOf(observation);
    return i >= 0 ? labelDictionary[i] : null;
  }

  /**
   * Setup the constrained label sets and free bookkeeping resources.
   *
   * @param threshold
   * @param labelIndex
   */
  public void lock(int threshold, Index<String> labelIndex) {
    if (labelDictionary != null) throw new RuntimeException("Label dictionary is already locked");
    log.info("Label dictionary enabled");
    System.err.printf("#observations: %d%n", (int) observationCounts.totalCount());
    Counters.retainAbove(observationCounts, threshold);
    Set<String> constrainedObservations = observationCounts.keySet();
    labelDictionary = new int[constrainedObservations.size()][];
    observationIndex = new HashIndex<>(constrainedObservations.size());
    for (String observation : constrainedObservations) {
      int i = observationIndex.addToIndex(observation);
      assert i < labelDictionary.length;
      Set<String> allowedLabels = observedLabels.get(observation);
      labelDictionary[i] = new int[allowedLabels.size()];
      int j = 0;
      for (String label : allowedLabels) {
        labelDictionary[i][j++] = labelIndex.indexOf(label);
      }
      if (DEBUG) {
        System.err.printf("%s : %s%n", observation, allowedLabels.toString());
      }

View on GitHub (pinned to 1b7edd19c4)