danmuzi opened a new pull request, #15864:
URL: https://github.com/apache/lucene/pull/15864
## Related Issues
* #15119
* #12419
## Overview
A deadlock occurred during static initialization due to the JVM Class
Initialization Lock.
This issue occurs through mutual references between
`org.apache.lucene.index.IndexWriter`(CMS, SegmentReader, FilterIndexInput
also) and `org.apache.lucene.internal.tests.TestSecrets`.
## Cause
### Circular Dependency
IndexWriter invokes TestSecrets during its initialization, while TestSecrets
conversely attempts to force-load IndexWriter during its own initialization.
### JVM Class Loading Lock
When the JVM loads a class for the first time, it acquires a global lock on
that class object.
A deadlock occurs when two threads each hold a lock on different classes and
wait for the other thread to release the lock for the class they are trying to
load.
## Code structure
* **IndexWriter.java, ConcurrentMergeScheduler.java, SegmentReader.java,
FilterIndexInput.java**
```java
static {
// Trigger initialization by calling TestSecrets
TestSecrets.set...Access(new ...Access() { ... });
}
```
* **TestSecrets.java**
```java
static {
...
ensureInitialized.accept(ConcurrentMergeScheduler.class);
ensureInitialized.accept(SegmentReader.class);
ensureInitialized.accept(IndexWriter.class); // Deadlock trigger point
ensureInitialized.accept(FilterIndexInput.class);
}
```
## Deadlock Scenario
This deadlock occurs in a multi-threaded environment when two classes are
accessed for the first time almost simultaneously.
| Step | Thread A | Thread B |
| :--- | :--- | :--- |
| **1** | First access to `IndexWriter` class. | First access to
`SegmentReader` class. |
| **2** | Acquires lock on `IndexWriter.class` and starts initialization. |
Acquires lock on `SegmentReader.class` and starts initialization. |
| **3** | Calls `TestSecrets` → Acquires lock on `TestSecrets.class`. |
Attempts to call `TestSecrets`. |
| **4** | (Initialization in progress) | **Blocked**: Waiting for
`TestSecrets` lock (held by Thread A). |
| **5** | `TestSecrets` static block calls `Class.forName("SegmentReader")`.
| (Waiting...) |
| **6** | **Blocked**: Waiting for `SegmentReader` lock (held by Thread B).
| (Waiting...) |
**Result:** A permanent deadlock occurs where Thread A waits for the
`SegmentReader` lock while Thread B waits for the `TestSecrets` lock.
## Steps to Reproduce
Without applying my `TestSecrets` fix:
1) Run the `main()` of the `DeadlockTest` class in `TestTestSecrets`.
2) Run `testDeadlock()` in `TestTestSecrets`.
## Solution - Lazy Initialization
All `Class.forName` calls that forcibly load target classes within the
static initialization block of `TestSecrets` have been removed.
Instead, the logic has been updated to perform initialization individually
within each Getter method at the actual time of invocation.
* TestSecrets.java
```java
public final class TestSecrets {
private static IndexWriterAccess indexWriterAccess;
private static ConcurrentMergeSchedulerAccess cmsAccess;
private static SegmentReaderAccess segmentReaderAccess;
// All ensureInitialized calls within the static initialization block
have been removed.
public static IndexWriterAccess getIndexWriterAccess() {
ensureCallerForGetter();
if (indexWriterAccess == null) {
ensureInitialized.accept(IndexWriter.class);
}
return Objects.requireNonNull(indexWriterAccess);
}
// Same pattern applies to other getters (CMS, SegmentReader, etc.)
}
```
## Diagram
```mermaid
graph TD
subgraph "Thread A (Initializes IndexWriter)"
A1[IndexWriter class load] --> A2["IndexWriter.<clinit> (Lock
IndexWriter.class)"]
A2 --> A3["Call TestSecrets.setIndexWriterAccess()"]
A3 --> A4["Trigger TestSecrets.<clinit> (Lock TestSecrets.class)"]
A4 --> A5["TestSecrets.<clinit> calls
Class.forName('SegmentReader')"]
A5 -- "Wait for SegmentReader.class lock" --> B2
end
subgraph "Thread B (Initializes SegmentReader)"
B1[SegmentReader class load] --> B2["SegmentReader.<clinit> (Lock
SegmentReader.class)"]
B2 --> B3["Call TestSecrets.setSegmentReaderAccess()"]
B3 -- "Wait for TestSecrets.class lock" --> A4
end
A5 -. Deadlock .-> B2
B3 -. Deadlock .-> A4
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]