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]

Reply via email to