1   package com.atlassian.bonnie;
2   
3   import java.io.IOException;
4   import java.io.File;
5   
6   import junit.framework.TestCase;
7   
8   import org.apache.lucene.analysis.standard.StandardAnalyzer;
9   import org.apache.lucene.document.Document;
10  import org.apache.lucene.document.Field;
11  import org.apache.lucene.index.IndexReader;
12  import org.apache.lucene.index.IndexWriter;
13  import org.apache.lucene.index.Term;
14  import org.apache.lucene.search.IndexSearcher;
15  import org.apache.lucene.search.TermQuery;
16  import org.apache.lucene.store.RAMDirectory;
17  
18  import edu.emory.mathcs.backport.java.util.concurrent.*;
19  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
20  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicReference;
21  
22  public final class TestConcurrentLuceneConnection extends TestCase
23  {
24      ConcurrentLuceneConnection lcon;
25      RAMDirectory directory = new RAMDirectory();
26  
27      private static final String FIELD_NAME = "my.field";
28  
29      private static final String FIELD_VALUE = "myvalue";
30      private final TermQuery query = new TermQuery(new Term(FIELD_NAME, FIELD_VALUE));
31  
32      protected void setUp() throws Exception
33      {
34          // Init the directory so we can use it
35          new IndexWriter(directory, null, true).close();
36          lcon = new ConcurrentLuceneConnection(directory, new StandardAnalyzer(), ILuceneConnection.DEFAULT_CONFIGURATION);
37      }
38  
39      public void testIsIndexCreated()
40      {
41          assertTrue(lcon.isIndexCreated());
42          assertFalse(new ConcurrentLuceneConnection(new RAMDirectory(), new StandardAnalyzer(), ILuceneConnection.DEFAULT_CONFIGURATION).isIndexCreated());
43      }
44  
45      public void testSearcherReuse()
46      {
47          final int numDocs = 5;
48          addDocsToIndex(numDocs);
49  
50          final AtomicReference searcherRef = new AtomicReference(null);
51          lcon.withSearch(new LuceneConnection.SearcherAction()
52          {
53              public void perform(IndexSearcher searcher) throws IOException
54              {
55                  searcherRef.set(searcher);
56                  assertEquals(5, searcher.search(query).length());
57              }
58          });
59  
60          lcon.withSearch(new LuceneConnection.SearcherAction()
61          {
62              public void perform(IndexSearcher searcher) throws IOException
63              {
64                  assertEquals(5, searcher.search(query).length());
65                  // should be the same searcher, no updates have been done
66                  assertTrue(searcherRef.get() == searcher);
67                  searcherRef.set(searcher);
68              }
69          });
70  
71          lcon.withWriter(new LuceneConnection.WriterAction()
72          {
73              public void perform(IndexWriter writer)
74              {
75              }
76          });
77  
78          lcon.withSearch(new LuceneConnection.SearcherAction()
79          {
80              public void perform(IndexSearcher searcher) throws IOException
81              {
82                  assertEquals(5, searcher.search(query).length());
83                  // should not be the same searcher, updates have been done
84                  assertFalse(searcherRef.get() == searcher);
85                  searcherRef.set(searcher);
86              }
87          });
88          lcon.withSearch(new LuceneConnection.SearcherAction()
89          {
90              public void perform(IndexSearcher searcher) throws IOException
91              {
92                  assertEquals(5, searcher.search(query).length());
93                  // should be the same searcher, no updates have been done
94                  assertTrue(searcherRef.get() == searcher);
95                  searcherRef.set(searcher);
96              }
97          });
98          lcon.close();
99          lcon.withSearch(new LuceneConnection.SearcherAction()
100         {
101             public void perform(IndexSearcher searcher) throws IOException
102             {
103                 assertEquals(5, searcher.search(query).length());
104                 // should not be the same searcher, updates have been done
105                 assertFalse(searcherRef.get() == searcher);
106                 searcherRef.set(searcher);
107             }
108         });
109         lcon.withSearch(new LuceneConnection.SearcherAction()
110         {
111             public void perform(IndexSearcher searcher) throws IOException
112             {
113                 assertEquals(5, searcher.search(query).length());
114                 // should be the same searcher, no updates have been done
115                 assertTrue(searcherRef.get() == searcher);
116                 searcherRef.set(searcher);
117             }
118         });
119         lcon.withReaderAndDeletes(new ILuceneConnection.ReaderAction()
120         {
121             public Object perform(IndexReader reader)
122             {
123                 return null;
124             }
125         });
126         lcon.withSearch(new LuceneConnection.SearcherAction()
127         {
128             public void perform(IndexSearcher searcher) throws IOException
129             {
130                 assertEquals(5, searcher.search(query).length());
131                 // should not be the same searcher, deletes might have been done
132                 assertFalse(searcherRef.get() == searcher);
133                 searcherRef.set(searcher);
134             }
135         });
136         lcon.withReader(new ILuceneConnection.ReaderAction()
137         {
138             public Object perform(IndexReader reader)
139             {
140                 return null;
141             }
142         });
143         lcon.withSearch(new LuceneConnection.SearcherAction()
144         {
145             public void perform(IndexSearcher searcher) throws IOException
146             {
147                 assertEquals(5, searcher.search(query).length());
148                 // should be the same searcher, only reads have been done
149                 assertTrue(searcherRef.get() == searcher);
150             }
151         });
152     }
153     
154     public void testWriterIsInteractiveModeConfiguration() throws Exception
155     {
156         lcon.withWriter(new LuceneConnection.WriterAction()
157         {
158             public void perform(IndexWriter writer)
159             {
160                 // check that we are using the interactive settings
161                 assertEquals("Should be interactive mode maxMergeSettings", ILuceneConnection.DEFAULT_CONFIGURATION.getInteractiveMaxMergeDocs(), writer.getMaxMergeDocs());
162                 assertEquals("Should be interactive mode maxBufferedSettings", ILuceneConnection.DEFAULT_CONFIGURATION.getInteractiveMaxBufferedDocs(), writer.getMaxBufferedDocs());
163                 assertEquals("Should be interactive mode mergeFactorSettings", ILuceneConnection.DEFAULT_CONFIGURATION.getInteractiveMergeFactor(), writer.getMergeFactor());
164                 assertEquals("Should be default maxField", ILuceneConnection.DEFAULT_CONFIGURATION.getMaxFieldLength(), writer.getMaxFieldLength());
165             }
166         });
167     }
168 
169     public void testReaderReuse()
170     {
171         final int numDocs = 5;
172         addDocsToIndex(numDocs);
173 
174         final AtomicReference readerRef = new AtomicReference(null);
175         lcon.withReader(new ILuceneConnection.ReaderAction()
176         {
177             public Object perform(IndexReader reader)
178             {
179                 readerRef.set(reader);
180                 assertEquals(5, reader.numDocs());
181                 return null;
182             }
183         });
184 
185         lcon.withReader(new ILuceneConnection.ReaderAction()
186         {
187             public Object perform(IndexReader reader)
188             {
189                 assertEquals(5, reader.numDocs());
190                 // should be the same searcher, no updates have been done
191                 assertTrue(readerRef.get() == reader);
192                 readerRef.set(reader);
193                 return null;
194             }
195         });
196 
197         lcon.withWriter(new LuceneConnection.WriterAction()
198         {
199             public void perform(IndexWriter writer)
200             {
201             }
202         });
203 
204         lcon.withReader(new ILuceneConnection.ReaderAction()
205         {
206             public Object perform(IndexReader reader)
207             {
208                 assertEquals(5, reader.numDocs());
209                 // should not be the same searcher, updates have been done
210                 assertFalse(readerRef.get() == reader);
211                 readerRef.set(reader);
212                 return null;
213             }
214         });
215         lcon.withReader(new ILuceneConnection.ReaderAction()
216         {
217             public Object perform(IndexReader reader)
218             {
219                 assertEquals(5, reader.numDocs());
220                 // should be the same searcher, no updates have been done
221                 assertTrue(readerRef.get() == reader);
222                 readerRef.set(reader);
223                 return null;
224             }
225         });
226         lcon.close();
227         lcon.withReader(new ILuceneConnection.ReaderAction()
228         {
229             public Object perform(IndexReader reader)
230             {
231                 assertEquals(5, reader.numDocs());
232                 // should not be the same searcher, updates have been done
233                 assertFalse(readerRef.get() == reader);
234                 readerRef.set(reader);
235                 return null;
236             }
237         });
238         lcon.withReader(new ILuceneConnection.ReaderAction()
239         {
240             public Object perform(IndexReader reader)
241             {
242                 assertEquals(5, reader.numDocs());
243                 // should be the same searcher, no updates have been done
244                 assertTrue(readerRef.get() == reader);
245                 readerRef.set(reader);
246                 return null;
247             }
248         });
249         lcon.withReaderAndDeletes(new ILuceneConnection.ReaderAction()
250         {
251             public Object perform(IndexReader reader)
252             {
253                 return null;
254             }
255         });
256         lcon.withReader(new ILuceneConnection.ReaderAction()
257         {
258             public Object perform(IndexReader reader)
259             {
260                 assertEquals(5, reader.numDocs());
261                 // should not be the same searcher, deletes might have been done
262                 assertFalse(readerRef.get() == reader);
263                 readerRef.set(reader);
264                 return null;
265             }
266         });
267         lcon.withSearch(new ILuceneConnection.SearcherAction()
268         {
269             public void perform(IndexSearcher searcher)
270             {
271                 assertNotNull(searcher.getIndexReader());
272                 assertTrue(readerRef.get() == searcher.getIndexReader());
273             }
274         });
275         lcon.withReader(new ILuceneConnection.ReaderAction()
276         {
277             public Object perform(IndexReader reader)
278             {
279                 assertEquals(5, reader.numDocs());
280                 // should be the same searcher, only reads have been done
281                 assertTrue(readerRef.get() == reader);
282                 return null;
283             }
284         });
285     }
286 
287     public void testRecreateIndexDirectory() throws IOException
288     {
289         final int numDocs = 5;
290         addDocsToIndex(numDocs);
291         
292         lcon.withSearch(new ILuceneConnection.SearcherAction()
293         {
294             public void perform(IndexSearcher searcher) throws IOException
295             {
296                 assertEquals(5, searcher.search(query).length());
297             }
298         });
299         addDocsToIndex(numDocs);
300         lcon.withSearch(new ILuceneConnection.SearcherAction()
301         {
302             public void perform(IndexSearcher searcher) throws IOException
303             {
304                 assertEquals(10, searcher.search(query).length());
305             }
306         });
307         lcon.recreateIndexDirectory();
308         lcon.withSearch(new ILuceneConnection.SearcherAction()
309         {
310             public void perform(IndexSearcher searcher) throws IOException
311             {
312                 assertEquals(0, searcher.search(query).length());
313             }
314         });
315     }
316 
317     public void testFileConstructor() throws IOException
318     {
319         final File tempDir = File.createTempFile(this.getClass().getName(), ".idx");
320         try
321         {
322             tempDir.delete();
323             tempDir.mkdirs();
324             lcon = new ConcurrentLuceneConnection(tempDir, new StandardAnalyzer(), ILuceneConnection.DEFAULT_CONFIGURATION);
325             assertFalse(IndexReader.indexExists(tempDir));
326             lcon.recreateIndexDirectory();
327             assertTrue(IndexReader.indexExists(tempDir));
328         }
329         finally
330         {
331             tempDir.delete();
332         }
333     }
334 
335     public void testOptimizeCallsCorrectly() throws IOException
336     {
337         final AtomicBoolean optimizeCalled = new AtomicBoolean(false);
338         final IndexWriter mockWriter = new IndexWriter(directory, null, false)
339         {
340             public synchronized void optimize()
341             {
342                 optimizeCalled.set(true);
343             }
344         };
345 
346         lcon = new ConcurrentLuceneConnection(directory, new StandardAnalyzer(), ILuceneConnection.DEFAULT_CONFIGURATION)
347         {
348             public void withWriter(ILuceneConnection.WriterAction action) throws LuceneException
349             {
350                 try
351                 {
352                     action.perform(mockWriter);
353                 }
354                 catch (IOException e)
355                 {
356                     throw new LuceneException(e);
357                 }
358             }
359         };
360 
361         lcon.optimize();
362 
363         assertTrue(optimizeCalled.get());
364     }
365 
366     public void testLeakedSearcher() throws Exception
367     {
368         addDocsToIndex(8);
369         IndexSearcher searcher = lcon.leakSearcher();
370         try
371         {
372             assertNotNull(searcher);
373             assertEquals(8, searcher.search(query).length());
374         }
375         finally
376         {
377             if (searcher != null)
378             {
379                 searcher.close();
380             }
381         }
382 
383     }
384 
385     public void testFlipSearcher() throws IOException
386     {
387         // Ensure that calling this method on a fresh connection is OK
388         lcon.flipCurrentSearcher();
389 
390         IndexSearcher searcher = null;
391         IndexSearcher anotherSearcher = null;
392         try
393         {
394             searcher = lcon.leakSearcher();
395             lcon.flipCurrentSearcher();
396             anotherSearcher = lcon.leakSearcher();
397             assertFalse(searcher == anotherSearcher);
398         }
399         finally
400         {
401             if (searcher != null)
402             {
403                 searcher.close();
404             }
405             if (anotherSearcher != null)
406             {
407                 anotherSearcher.close();
408             }
409         }
410     }
411 
412     public void testWriterBlocksDelete() throws InterruptedException
413     {
414         addDocsToIndex(5);
415 
416         final ExecutorService pool = Executors.newSingleThreadExecutor();
417         final AtomicBoolean innerThreadStarted = new AtomicBoolean(false);
418         final CountDownLatch deleteComplete = new CountDownLatch(1);
419         final AtomicReference firstReaderRef = new AtomicReference(null);
420 
421         // test withWrite, then simultaneously, withReadAndDeletes
422         lcon.withWriter(new ILuceneConnection.WriterAction()
423         {
424             public void perform(final IndexWriter writer) throws IOException
425             {
426                 final CountDownLatch latch = new CountDownLatch(1);
427 
428                 // Execute the deleteTask in a different thread
429                 Future deleteTask = pool.submit(new Runnable()
430                 {
431                     public void run()
432                     {
433                         innerThreadStarted.set(true);
434                         // Let the latch know that the deleteTask has began
435                         // running
436                         latch.countDown();
437                         // this should only be run _after_ the WriterAction
438                         // completes
439                         lcon.withReaderAndDeletes(new ILuceneConnection.ReaderAction()
440                         {
441                             public Object perform(IndexReader reader) throws IOException
442                             {
443                                 assertEquals("threaded withWriter: Added 2 documents not detected from thread", 7, reader.numDocs());
444                                 reader.deleteDocument(0);
445                                 firstReaderRef.set(reader);
446                                 deleteComplete.countDown();
447                                 return null;
448                             }
449                         });
450                     }
451                 });
452 
453                 // Wait for the deleteTask to begin executing
454                 try
455                 {
456                     latch.await();
457                 }
458                 catch (InterruptedException e)
459                 {
460                     throw new RuntimeException(e);
461                 }
462 
463                 // Assert that the deleteTask has started executing
464                 assertTrue(innerThreadStarted.get());
465                 // But blocked before deleteing a document. The task is blocked
466                 // as we should hold the lock on the index
467                 // until we leave the withWriter() method.
468                 assertFalse(deleteTask.isDone());
469                 // Therefore we should still have 5 documents in the index
470                 assertEquals(5, writer.docCount());
471                 // Add another document
472                 writer.addDocument(createDocument());
473                 writer.addDocument(createDocument());
474 
475                 assertFalse(deleteTask.isDone());
476             }
477         });
478 
479         // wait for the deleteTask to complete and assert it did...
480         deleteComplete.await();
481 
482         lcon.withReaderAndDeletes(new ILuceneConnection.ReaderAction()
483         {
484             public Object perform(IndexReader reader)
485             {
486                 // 2 docs added, 1 removed - should have 6 docs
487                 assertEquals(6, reader.numDocs());
488                 return null;
489             }
490         });
491         assertEquals("threaded withReaderAndDeletes: Document not added in thread", 6, lcon.getNumDocs());
492     }
493 
494     public void testDeleteBlocksWriter() throws InterruptedException
495     {
496         addDocsToIndex(5);
497 
498         final ExecutorService pool = Executors.newSingleThreadExecutor();
499         final AtomicBoolean innerThreadStarted = new AtomicBoolean(false);
500         final CountDownLatch writeComplete = new CountDownLatch(1);
501         final AtomicReference firstWriterRef = new AtomicReference(null);
502 
503         // test withWrite, then simultaneously, withReadAndDeletes
504         lcon.withReaderAndDeletes(new ILuceneConnection.ReaderAction()
505         {
506             public Object perform(final IndexReader reader) throws IOException
507             {
508                 final CountDownLatch latch = new CountDownLatch(1);
509 
510                 // Execute the deleteTask in a different thread
511                 Future writeTask = pool.submit(new Runnable()
512                 {
513                     public void run()
514                     {
515                         innerThreadStarted.set(true);
516                         // Let the latch know that the deleteTask has began
517                         // running
518                         latch.countDown();
519                         // this should only be run _after_ the WriterAction
520                         // completes
521                         lcon.withWriter(new ILuceneConnection.WriterAction()
522                         {
523                             public void perform(IndexWriter writer) throws IOException
524                             {
525                                 assertEquals("threaded withReaderAndDeletes: deleted documents not detected from thread", 3, reader.numDocs());
526                                 writer.addDocument(createDocument());
527                                 firstWriterRef.set(writer);
528                                 writeComplete.countDown();
529                             }
530                         });
531                     }
532                 });
533 
534                 // Wait for the deleteTask to begin executing
535                 try
536                 {
537                     latch.await();
538                 }
539                 catch (InterruptedException e)
540                 {
541                     throw new RuntimeException(e);
542                 }
543 
544                 // Assert that the deleteTask has started executing
545                 assertTrue(innerThreadStarted.get());
546                 // But blocked before deleteing a document. The task is blocked
547                 // as we should hold the lock on the index
548                 // until we leave the withWriter() method.
549                 assertFalse(writeTask.isDone());
550                 // delete documents
551                 reader.deleteDocument(0);
552                 reader.deleteDocument(1);
553                 return null;
554             }
555         });
556 
557         // wait for the writeTask to complete and assert it did...
558         writeComplete.await();
559 
560         lcon.withReaderAndDeletes(new ILuceneConnection.ReaderAction()
561         {
562             public Object perform(IndexReader reader)
563             {
564                 // 2 docs added, 1 removed - should have 6 docs
565                 assertEquals(4, reader.numDocs());
566                 return null;
567             }
568         });
569         assertEquals("threaded withReaderAndDeletes: Document not added in thread", 4, lcon.getNumDocs());
570     }
571 
572     public void testDeleteClosesReader()
573     {
574         final AtomicReference readerRef = new AtomicReference(null);
575         lcon.withReaderAndDeletes(new ILuceneConnection.ReaderAction()
576         {
577             public Object perform(IndexReader reader)
578             {
579                 assertNotNull(reader);
580                 readerRef.set(reader);
581                 return null;
582             }
583         });
584         lcon.withReaderAndDeletes(new ILuceneConnection.ReaderAction()
585         {
586             public Object perform(IndexReader reader)
587             {
588                 assertNotNull(reader);
589                 assertFalse(readerRef.get() == reader);
590                 return null;
591             }
592         });
593     }
594 
595     public void testWriteClosesWriter()
596     {
597         final AtomicReference writerRef = new AtomicReference(null);
598         lcon.withWriter(new ILuceneConnection.WriterAction()
599         {
600             public void perform(IndexWriter writer)
601             {
602                 assertNotNull(writer);
603                 writerRef.set(writer);
604             }
605         });
606         lcon.withWriter(new ILuceneConnection.WriterAction()
607         {
608             public void perform(IndexWriter writer)
609             {
610                 assertNotNull(writer);
611                 assertFalse(writerRef.get() == writer);
612             }
613         });
614     }
615     
616     public void testDeleteAndWriteAtomicity() throws Exception
617     {
618         final CountDownLatch latch = new CountDownLatch(1);
619         final ExecutorService pool = Executors.newSingleThreadExecutor();
620         final AtomicReference secondUpdateFuture = new AtomicReference(null);
621         final AtomicBoolean complete = new AtomicBoolean(false);
622 
623         lcon.withDeleteAndWrites(new ILuceneConnection.ReaderAction()
624             {
625                 public Object perform(IndexReader reader)
626                 {
627                     secondUpdateFuture.set(
628                         pool.submit(
629                             new Runnable()
630                             {
631                                 public void run()
632                                 {
633                                     latch.countDown();
634                                     lcon.withReaderAndDeletes(
635                                         new ILuceneConnection.ReaderAction()
636                                         {
637                                             public Object perform(IndexReader reader)
638                                             {
639                                                 assertNotNull(reader);
640                                                 assertTrue(complete.get());
641                                                 return null;
642                                             }
643                                         }
644                                     );
645                                 }
646                             }
647                         )
648                     );
649                     try
650                     {
651                         // make sure we are inside the Runnable (before the ReaderAction is called as it should block)
652                         latch.await();
653                     }
654                     catch (InterruptedException e)
655                     {
656                         throw new RuntimeException(e);
657                     }
658                     Thread.yield();
659                     return null;
660                 }
661             }, 
662             new ILuceneConnection.WriterAction()
663             {
664                 public void perform(IndexWriter writer)
665                 {
666                     assertNotNull(writer);
667                     assertEquals("Should be interactive mode maxMergeSettings", ILuceneConnection.DEFAULT_CONFIGURATION.getInteractiveMaxMergeDocs(), writer.getMaxMergeDocs());
668                     assertEquals("Should be interactive mode maxBufferedSettings", ILuceneConnection.DEFAULT_CONFIGURATION.getInteractiveMaxBufferedDocs(), writer.getMaxBufferedDocs());
669                     assertEquals("Should be interactive mode mergeFactorSettings", ILuceneConnection.DEFAULT_CONFIGURATION.getInteractiveMergeFactor(), writer.getMergeFactor());
670     
671                     Future future = ((Future) secondUpdateFuture.get());
672                     assertFalse(future.isDone());
673                     assertFalse(future.isCancelled());
674                     complete.set(true);
675                 }
676             }
677         );
678         assertTrue(complete.get());
679         Future future = ((Future) secondUpdateFuture.get());
680         assertNull(future.get());
681         assertTrue(future.isDone());
682         assertFalse(future.isCancelled());
683     }
684 
685     public void testBatchUpdateHoldsLock() throws Exception
686     {
687         final CountDownLatch latch = new CountDownLatch(1);
688         final ExecutorService pool = Executors.newSingleThreadExecutor();
689         final AtomicReference secondUpdateFuture = new AtomicReference(null);
690 
691         lcon.withBatchUpdate(new ILuceneConnection.BatchUpdateAction()
692         {
693             public void perform() throws Exception
694             {
695                 secondUpdateFuture.set(
696                     pool.submit(
697                         new Runnable()
698                         {
699                             public void run()
700                             {
701                                 latch.countDown();
702                                 lcon.withReaderAndDeletes(
703                                     new ILuceneConnection.ReaderAction()
704                                     {
705                                         public Object perform(IndexReader reader)
706                                         {
707                                             assertNotNull(reader);
708                                             return null;
709                                         }
710                                     }
711                                 );
712                             }
713                         }
714                     )
715                 );
716                 latch.await();
717                 Thread.yield();
718                 lcon.withReaderAndDeletes(
719                     new ILuceneConnection.ReaderAction()
720                     {
721                         public Object perform(IndexReader reader)
722                         {
723                             assertNotNull(reader);
724                             return null;
725                         }
726                     }
727                 );
728                 lcon.withWriter(
729                     new ILuceneConnection.WriterAction()
730                     {
731                         public void perform(IndexWriter writer)
732                         {
733                             assertNotNull(writer);
734                             assertEquals("Should be batch mode maxMergeSettings", ILuceneConnection.DEFAULT_CONFIGURATION.getBatchMaxMergeDocs(), writer.getMaxMergeDocs());
735                             assertEquals("Should be batch mode maxBufferedSettings", ILuceneConnection.DEFAULT_CONFIGURATION.getBatchMaxBufferedDocs(), writer.getMaxBufferedDocs());
736                             assertEquals("Should be batch mode mergeFactorSettings", ILuceneConnection.DEFAULT_CONFIGURATION.getBatchMergeFactor(), writer.getMergeFactor());
737                             assertEquals("Should be default maxField", ILuceneConnection.DEFAULT_CONFIGURATION.getMaxFieldLength(), writer.getMaxFieldLength());
738                         }
739                     }
740                 );
741 
742                 Future future = ((Future) secondUpdateFuture.get());
743                 assertFalse(future.isDone());
744                 assertFalse(future.isCancelled());
745             }
746         });
747         Future future = ((Future) secondUpdateFuture.get());
748         assertNull(future.get());
749         assertTrue(future.isDone());
750         assertFalse(future.isCancelled());
751     }
752 
753     public void testDeleterDoesNotBlockSearches()
754     {
755         addDocsToIndex(5);
756 
757         final int noOfSearches = 1000;
758         final ExecutorService pool = Executors.newFixedThreadPool(4);
759         final CountDownLatch complete = new CountDownLatch(noOfSearches);
760         final AtomicReference readerRef = new AtomicReference(null);
761 
762         lcon.withReader(
763             new ILuceneConnection.ReaderAction()
764             {
765                 public Object perform(IndexReader reader)
766                 {
767                     readerRef.set(reader);
768                     return null;
769                 }
770             }
771         );
772 
773         // test withWrite, then simultaneously, withReadAndDeletes
774         lcon.withReaderAndDeletes(new ILuceneConnection.ReaderAction()
775         {
776             public Object perform(final IndexReader reader)
777             {
778                 for (int i = 0; i < noOfSearches; i++)
779                 {
780 
781                     // Execute the deleteTask in a different thread
782                     pool.submit(
783                         new Runnable()
784                         {
785                             public void run()
786                             {
787                                 // these should not be blocked
788                                 lcon.withSearch(new ILuceneConnection.SearcherAction()
789                                 {
790                                     public void perform(IndexSearcher searcher)
791                                     {
792                                         assertTrue(readerRef.get() == searcher.getIndexReader());
793                                     }
794                                 });
795                                 lcon.withReader(new ILuceneConnection.ReaderAction()
796                                 {
797                                     public Object perform(IndexReader reader)
798                                     {
799                                         assertTrue(readerRef.get() == reader);
800                                         return null;
801                                     }
802                                 });
803                                 // Let the latch know that the searchTask has completed
804                                 complete.countDown();
805                             }
806                         }
807                     );
808                 }
809 
810                 // Wait for the tasks to complete executing,
811                 // failure is an infinite wait... but we wait only for 5 seconds.
812                 try
813                 {
814                     assertTrue("We waited too long, should have passed", complete.await(5, TimeUnit.SECONDS));
815                 }
816                 catch (InterruptedException e)
817                 {
818                     throw new AssertionError("We should not have to wait here, tasks should be reentrant: " + e);
819                 }
820                 return null;
821             }
822         });
823 
824         // Test that the searcher got refreshed
825         lcon.withReader(
826             new ILuceneConnection.ReaderAction()
827             {
828                 public Object perform(IndexReader reader)
829                 {
830                     assertFalse(readerRef.get() == reader);
831                     return null;
832                 }
833             }
834         );
835     }
836 
837     public void testWriterDoesNotBlockSearches()
838     {
839         addDocsToIndex(5);
840 
841         final int noOfSearches = 1000;
842         final ExecutorService pool = Executors.newFixedThreadPool(4);
843         final CountDownLatch complete = new CountDownLatch(noOfSearches);
844         final AtomicReference readerRef = new AtomicReference(null);
845 
846         lcon.withReader(
847             new ILuceneConnection.ReaderAction()
848             {
849                 public Object perform(IndexReader reader)
850                 {
851                     readerRef.set(reader);
852                     return null;
853                 }
854             }
855         );
856 
857         // test withWrite, then simultaneously, withReadAndDeletes
858         lcon.withWriter(new ILuceneConnection.WriterAction()
859         {
860             public void perform(final IndexWriter writer)
861             {
862                 for (int i = 0; i < noOfSearches; i++)
863                 {
864 
865                     // Execute the deleteTask in a different thread
866                     pool.submit(
867                         new Runnable()
868                         {
869                             public void run()
870                             {
871                                 // these should not be blocked
872                                 lcon.withSearch(new ILuceneConnection.SearcherAction()
873                                 {
874                                     public void perform(IndexSearcher searcher)
875                                     {
876                                         assertTrue(readerRef.get() == searcher.getIndexReader());
877                                     }
878                                 });
879                                 lcon.withReader(new ILuceneConnection.ReaderAction()
880                                 {
881                                     public Object perform(IndexReader reader)
882                                     {
883                                         assertTrue(readerRef.get() == reader);
884                                         return null;
885                                     }
886                                 });
887                                 // Let the latch know that the searchTask has completed
888                                 complete.countDown();
889                             }
890                         }
891                     );
892                 }
893 
894                 // Wait for the tasks to complete executing,
895                 // failure is an infinite wait...but we wait for 5 seconds
896                 try
897                 {
898                     assertTrue("We waited too long, should have passed", complete.await(5, TimeUnit.SECONDS));
899                 }
900                 catch (InterruptedException e)
901                 {
902                     throw new AssertionError("We should not have to wait here, tasks should be reentrant: " + e);
903                 }
904             }
905         });
906 
907         // Test that the reader was refreshed
908         lcon.withReader(
909             new ILuceneConnection.ReaderAction()
910             {
911                 public Object perform(IndexReader reader)
912                 {
913                     assertFalse(readerRef.get() == reader);
914                     return null;
915                 }
916             }
917         );
918     }
919 
920     /**
921      * TODO Make this work.
922      * Test that our batch update correctly prevents visibility of the whole operation until it is finished
923      */
924     /*
925     public void testBatchUpdateHasAtomicSearcherVisibility()
926     {
927         addDocsToIndex(5);
928         final ExecutorService pool = Executors.newFixedThreadPool(4);
929         final Callable countDocs = new Callable()
930         {
931             public Object call() throws Exception
932             {
933                 return lcon.withReader(
934                     new ILuceneConnection.ReaderAction()
935                     {
936                         public Object perform(IndexReader reader) throws IOException
937                         {
938                             return new Integer(reader.numDocs());
939                         }
940                     }
941                 );
942             }
943         };
944 
945         // Test that the reader was refreshed and that our countDocs does the business
946         assertNumDocs(5, pool.submit(countDocs));
947 
948         lcon.withBatchUpdate(
949             new ILuceneConnection.BatchUpdateAction()
950             {
951                 public void perform() throws Exception
952                 {
953                     assertNumDocs(5, pool.submit(countDocs));
954                     lcon.withWriter(
955                         new ILuceneConnection.WriterAction(){
956                             public void perform(IndexWriter writer) throws IOException
957                             {
958                                 writer.addDocument(createDocument());
959                                 assertNumDocs(5, pool.submit(countDocs));
960                                 writer.addDocument(createDocument());
961                                 assertNumDocs(5, pool.submit(countDocs));
962                                 writer.addDocument(createDocument());
963                                 assertNumDocs(5, pool.submit(countDocs));
964                             }
965                         }
966                     );
967                     assertNumDocs(5, pool.submit(countDocs));
968                     lcon.withReaderAndDeletes(
969                         new ILuceneConnection.ReaderAction()
970                         {
971                             public Object perform(IndexReader reader) throws IOException
972                             {
973                                 assertNumDocs(5, pool.submit(countDocs));
974                                 reader.deleteDocument(0);
975                                 assertNumDocs(5, pool.submit(countDocs));
976                                 reader.deleteDocument(0);
977                                 assertNumDocs(5, pool.submit(countDocs));
978                                 return null;
979                             }
980                         }
981                     );
982                     assertNumDocs(5, pool.submit(countDocs));
983                 }
984             }
985         );
986         assertNumDocs(6, pool.submit(countDocs));
987     }
988     */
989 
990     private Document createDocument()
991     {
992         Document d = new Document();
993         d.add(new Field(FIELD_NAME, FIELD_VALUE, Field.Store.YES, Field.Index.TOKENIZED));
994         return d;
995     }
996 
997     private void addDocsToIndex(final int numDocs)
998     {
999         lcon.withWriter(new ILuceneConnection.WriterAction()
1000         {
1001             public void perform(IndexWriter writer) throws IOException
1002             {
1003                 for (int i = 0; i < numDocs; ++i)
1004                 {
1005                     writer.addDocument(createDocument());
1006                 }
1007             }
1008         });
1009     }
1010 }