A forced index switchover refused by the minimum-runtime guard stays armed and fires later on its own
Nobody has claimed this yet.
- 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
- Start a full reindex on an instance where
reindexTimeElapsedInLong()returns a value belowREINDEX_THREAD_MINIMUM_RUNTIME_IN_SEC(see #37282 for one way this happens naturally). - Click Stop Reindex and Make Active (#37279).
- Observe: nothing happens. The new indices stay in
Building, the active indices stay active, and the log emitsRunning Reindex Switchover/Reindex Time Elapsed not set.every 3 seconds. - 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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