dotCMS / dotCMS/core

A forced index switchover refused by the minimum-runtime guard stays armed and fires later on its own

Open
#37,281 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Team : Maintenance
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Summary

reindexSwitchover(forceSwitch = true) does not bypass the minimum-runtime guard in fullReindexSwitchover(). When the guard refuses the request, the request is not failed and the caller is not told — the reindex slots stay populated, so ReindexThread's own periodic switchOverIfNeeded() keeps retrying every 3 seconds and performs the promotion whenever the guard eventually opens.

The result is a latent, invisible index switch that fires at an arbitrary later time, with nothing tying it to the action that requested it.

Affected version

26.08.03-01 (code path unchanged on main at time of writing)

Steps to reproduce

  1. Start a full reindex on an instance where reindexTimeElapsedInLong() returns a value below REINDEX_THREAD_MINIMUM_RUNTIME_IN_SEC (see #37282 for one way this happens naturally).
  2. Click Stop Reindex and Make Active (#37279).
  3. Observe: nothing happens. The new indices stay in Building, the active indices stay active, and the log emits Running Reindex Switchover / Reindex Time Elapsed not set. every 3 seconds.
  4. Wait. The promotion fires by itself once the guard passes.

Reproduced locally: stop clicked at ~5% progress, promotion fired ~33 minutes later with no further user action.

Expected

A forced switchover either happens or is rejected with a clear error. It should never be deferred silently.

Actual

// ContentletIndexAPIImpl.reindexSwitchover
if (forceSwitch || queueApi.recordsInQueue() == 0) {
    Logger.info(this, "Running Reindex Switchover");
    return this.fullReindexSwitchover(forceSwitch);
}
// ContentletIndexAPIImpl.fullReindexSwitchover — guard runs BEFORE forceSwitch is consulted
if (reindexTimeElapsedInLong()
        < Config.getLongProperty("REINDEX_THREAD_MINIMUM_RUNTIME_IN_SEC", 30) * 1000) {
    ...
    ThreadUtils.sleep(3000);
    return false;          // forceSwitch never examined
}

Because the queue is empty and the reindex slots remain set, the background thread re-enters the same path indefinitely:

// ReindexThread.switchOverIfNeeded
if (ESReindexationProcessStatus.inFullReindexation() && queueApi.recordsInQueue() == 0) {
    if (indexAPI.reindexSwitchover(false)) { ... }
}

Impact

This is what turned a stop-button click into an unexplained outage. In production the switchover fired 52 minutes after the request, promoting a partially-built index. By then there was no plausible connection between the two events for anyone watching, and the investigation initially looked for a cause at the time of the outage rather than an hour earlier.

Suggested fix

  • Honour forceSwitch — skip the minimum-runtime guard when the caller has explicitly forced the switch.
  • Or reject the request outright and surface the refusal to the caller, leaving no pending state.
  • Either way, do not allow a refused explicit request to be silently completed later by a background thread.

Related

  • #37279 — the control that issues the forced switchover
  • #37282 — why the guard can refuse indefinitely

Related Freshdesk ticket: https://helpdesk.dotcms.com/a/tickets/38957

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing ContentletIndexAPIImpl.reindexSwitchover and fullReindexSwitchover, then follow ReindexThread.switchOverIfNeeded and the minimum-runtime guard described in the issue. Reproduce the forced stop while the guard refuses, and verify the completed behavior: the request is clearly accepted or rejected, with no later background promotion from that request.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.