[ 
https://issues.apache.org/jira/browse/LUCENE-9084?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Michael McCandless updated LUCENE-9084:
---------------------------------------
    Fix Version/s: 8.5
       Resolution: Fixed
           Status: Resolved  (was: Patch Available)

Thanks [~paulward24]!

> circular synchronization wait (potential deadlock) in AnalyzingInfixSuggester
> -----------------------------------------------------------------------------
>
>                 Key: LUCENE-9084
>                 URL: https://issues.apache.org/jira/browse/LUCENE-9084
>             Project: Lucene - Core
>          Issue Type: Bug
>          Components: core/search
>            Reporter: Paul Ward
>            Assignee: Michael McCandless
>            Priority: Major
>             Fix For: 8.5
>
>         Attachments: LUCENE-9084.patch
>
>
> h1. Github Pull Request  
> I created a pull request on github for this:
> [https://github.com/apache/lucene-solr/pull/1064]
> h1. Bug Description
> Detailed code (snippets and links) are in the sections after this overview 
> (section **Detailed Code** and **This Patch's Code**).
> Method {{ensureOpen()}} is {{synchronized}} (acquires {{this}}) and its body 
> contains a {{synchronized (searcherMgrLock)}} block (i.e., then acquires 
> {{searcherMgrLock}}).
> Method {{ensureOpen()}} is called two times from public methods {{add()}} and 
> {{update()}}.
> A thread calling public methods {{add()}} or {{update()}} will acquire locks 
> in order:
> {code:java}
> this -> searcherMgrLock
> {code}
> Public method {{build()}} has a {{synchronized (searcherMgrLock)}} block in 
> which it calls method {{add()}}. Method {{add()}}, as described above, calls 
> method {{ensureOpen()}}.
> Therefore, a thread calling public method {{build()}} will acquire locks in 
> order:
> {code:java}
> searcherMgrLock -> this -> searcherMgrLock
> {code}
> 2 threads can acquire locks in different order which may cause a circular 
> wait (deadlock).
> I do not know which threads call these methods, but there is a lot of 
> synchronization in these methods and in this file, so I think these methods 
> must be called concurrently.
> One thread can acquire:
> {{this -> searcherMgrLock}} (the first order above)
> and the other thread can acquire:
> {{searcherMgrLock -> this}} (the second order above).
> Note how the above 2 orders lead to a circular wait.
> h1. Detailed Code
> Method {{ensureOpen()}} is {{synchronized}} and its body contains a 
> {{synchronized (searcherMgrLock)}}:
> {code:java}
>   private synchronized void ensureOpen() throws IOException { <<<<<<<<<<< see 
> the synchronized keyword
>     if (writer == null) {
>       if (DirectoryReader.indexExists(dir)) {
>         // Already built; open it:
>         writer = new IndexWriter(dir, getIndexWriterConfig(getGramAnalyzer(), 
> IndexWriterConfig.OpenMode.APPEND));
>       } else {
>         writer = new IndexWriter(dir, getIndexWriterConfig(getGramAnalyzer(), 
> IndexWriterConfig.OpenMode.CREATE));
>       }
>       synchronized (searcherMgrLock) { <<<<<<<<<<<<<<<<<<<<<<<
> {code}
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L371-L379]
> Method {{ensureOpen()}} is called two times from public methods {{add()}} and 
> {{update()}}:
> {code:java}
>   public void add(BytesRef text, Set<BytesRef> contexts, long weight, 
> BytesRef payload) throws IOException {
>     ensureOpen(); <<<<<<<<<<<<<<<<<<<<<<
> {code}
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L394-L395]
> {code:java}
>   public void update(BytesRef text, Set<BytesRef> contexts, long weight, 
> BytesRef payload) throws IOException {
>     ensureOpen(); <<<<<<<<<<<<<<<<<<<<
> {code}
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L406-L407]
> Public method {{build()}} has a {{synchronized (searcherMgrLock)}} block in 
> which it calls method {{add()}}:
> {code:java}
>   @Override
>   public void build(InputIterator iter) throws IOException {
>     
>     synchronized (searcherMgrLock) { <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>       if (searcherMgr != null) {
>         searcherMgr.close();
>         searcherMgr = null;
>       }
>       if (writer != null) {
>         writer.close();
>         writer = null;
>       }
>       boolean success = false;
>       try {
>         // First pass: build a temporary normal Lucene index,
>         // just indexing the suggestions as they iterate:
>         writer = new IndexWriter(dir,
>             getIndexWriterConfig(getGramAnalyzer(), 
> IndexWriterConfig.OpenMode.CREATE));
>         //long t0 = System.nanoTime();
>         // TODO: use threads?
>         BytesRef text;
>         while ((text = iter.next()) != null) {
>           BytesRef payload;
>           if (iter.hasPayloads()) {
>             payload = iter.payload();
>           } else {
>             payload = null;
>           }
>           add(text, iter.contexts(), iter.weight(), payload);  
> <<<<<<<<<<<<<<<<<<<<<
> {code}
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L278-L310]
> Method {{add()}} is the same one I linked above.
> h1. This Patch's Code
> Note that method {{ensureOpen()}} (inlined above) is the *only* place (method 
> or synchronization block) that is {{synchronized}} on {{this}}.
> *All* the other synchronizations in this file are on {{searcherMgrLock}}.
> This CR removes the {{synchronized}} on {{this}} (again, being the only 
> {{synchronized}} on {{this}}, we can do this change safely).
> And moves {{synchronized (searcherMgrLock)}} a few lines above, to protect 
> the entire code (that otherwise was protected by {{synchronized}} on 
> {{this}}).
> The above breaks the lock cycle I described earlier.
> The fix looks big because it changes indentation.
> But only one line is moved by a few lines up.
> I.e., from this:
> {code:java}
>   private synchronized void ensureOpen() throws IOException {
>     if (writer == null) {
>       if (DirectoryReader.indexExists(dir)) {
>         // Already built; open it:
>         writer = new IndexWriter(dir, getIndexWriterConfig(getGramAnalyzer(), 
> IndexWriterConfig.OpenMode.APPEND));
>       } else {
>         writer = new IndexWriter(dir, getIndexWriterConfig(getGramAnalyzer(), 
> IndexWriterConfig.OpenMode.CREATE));
>       }
>       synchronized (searcherMgrLock) { <<<<<<<<<<<<<<<<<<<<<< move from here
>         SearcherManager oldSearcherMgr = searcherMgr;
>         searcherMgr = new SearcherManager(writer, null);
>         if (oldSearcherMgr != null) {
>           oldSearcherMgr.close();
>         }
>       }
>     }
>   }
> {code}
> To this:
> {code:java}
>   private void ensureOpen() throws IOException { <<<<<<<<<<<<<<<<<< remove 
> synchronized --- can lead to circular wait --- and legal to remove
>     synchronized (searcherMgrLock) { <<<<<<<<<<<<<<<<<<<<< move to here
>       if (writer == null) {
>         if (DirectoryReader.indexExists(dir)) {
>           // Already built; open it:
>           writer = new IndexWriter(dir, 
> getIndexWriterConfig(getGramAnalyzer(), IndexWriterConfig.OpenMode.APPEND));
>         } else {
>           writer = new IndexWriter(dir, 
> getIndexWriterConfig(getGramAnalyzer(), IndexWriterConfig.OpenMode.CREATE));
>         }
>         SearcherManager oldSearcherMgr = searcherMgr;
>         searcherMgr = new SearcherManager(writer, null);
>         if (oldSearcherMgr != null) {
>           oldSearcherMgr.close();
>         }
>       }
>     }
>   }
> {code}
> Here are all the places where {{synchronized (searcherMgrLock)}} appears in 
> this file (and again, no other {{synchronized}} on other objects is done):
>  - 
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L281-L332]
>  - 
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L379-L385]
>  - 
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L654-L657]
>  - 
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L870-L873]
>  - 
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L898-L901]
>  - 
> [https://github.com/apache/lucene-solr/blob/master/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java#L926-L929]
> I.e., doing the synchronization like above is safe and consistent with the 
> rest of the file.



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org
For additional commands, e-mail: issues-h...@lucene.apache.org

Reply via email to