pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / test / org / apache / lucene / index / TestIndexReaderReopen.java
1 package org.apache.lucene.index;
2
3 /**
4  * Licensed to the Apache Software Foundation (ASF) under one or more
5  * contributor license agreements.  See the NOTICE file distributed with
6  * this work for additional information regarding copyright ownership.
7  * The ASF licenses this file to You under the Apache License, Version 2.0
8  * (the "License"); you may not use this file except in compliance with
9  * the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Random;
29 import java.util.Set;
30
31 import org.apache.lucene.analysis.MockAnalyzer;
32 import org.apache.lucene.document.Document;
33 import org.apache.lucene.document.Field.Index;
34 import org.apache.lucene.document.Field.Store;
35 import org.apache.lucene.document.Field;
36 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
37 import org.apache.lucene.search.FieldCache;
38 import org.apache.lucene.search.IndexSearcher;
39 import org.apache.lucene.search.ScoreDoc;
40 import org.apache.lucene.search.TermQuery;
41 import org.apache.lucene.store.AlreadyClosedException;
42 import org.apache.lucene.store.Directory;
43 import org.apache.lucene.util.BitVector;
44 import org.apache.lucene.util.LuceneTestCase;
45 import org.apache.lucene.util._TestUtil;
46
47 public class TestIndexReaderReopen extends LuceneTestCase {
48   
49   public void testReopen() throws Exception {
50     final Directory dir1 = newDirectory();
51     
52     createIndex(random, dir1, false);
53     performDefaultTests(new TestReopen() {
54
55       @Override
56       protected void modifyIndex(int i) throws IOException {
57         TestIndexReaderReopen.modifyIndex(i, dir1);
58       }
59
60       @Override
61       protected IndexReader openReader() throws IOException {
62         return IndexReader.open(dir1, false);
63       }
64       
65     });
66     dir1.close();
67     
68     final Directory dir2 = newDirectory();
69     
70     createIndex(random, dir2, true);
71     performDefaultTests(new TestReopen() {
72
73       @Override
74       protected void modifyIndex(int i) throws IOException {
75         TestIndexReaderReopen.modifyIndex(i, dir2);
76       }
77
78       @Override
79       protected IndexReader openReader() throws IOException {
80         return IndexReader.open(dir2, false);
81       }
82       
83     });
84     dir2.close();
85   }
86   
87   public void testParallelReaderReopen() throws Exception {
88     final Directory dir1 = newDirectory();
89     createIndex(random, dir1, true);
90     final Directory dir2 = newDirectory();
91     createIndex(random, dir2, true);
92     
93     performDefaultTests(new TestReopen() {
94
95       @Override
96       protected void modifyIndex(int i) throws IOException {
97         TestIndexReaderReopen.modifyIndex(i, dir1);
98         TestIndexReaderReopen.modifyIndex(i, dir2);
99       }
100
101       @Override
102       protected IndexReader openReader() throws IOException {
103         ParallelReader pr = new ParallelReader();
104         pr.add(IndexReader.open(dir1, false));
105         pr.add(IndexReader.open(dir2, false));
106         return pr;
107       }
108       
109     });
110     dir1.close();
111     dir2.close();
112     
113     final Directory dir3 = newDirectory();
114     createIndex(random, dir3, true);
115     final Directory dir4 = newDirectory();
116     createIndex(random, dir4, true);
117
118     performTestsWithExceptionInReopen(new TestReopen() {
119
120       @Override
121       protected void modifyIndex(int i) throws IOException {
122         TestIndexReaderReopen.modifyIndex(i, dir3);
123         TestIndexReaderReopen.modifyIndex(i, dir4);
124       }
125
126       @Override
127       protected IndexReader openReader() throws IOException {
128         ParallelReader pr = new ParallelReader();
129         pr.add(IndexReader.open(dir3, false));
130         pr.add(IndexReader.open(dir4, false));
131         // Does not implement reopen, so
132         // hits exception:
133         pr.add(new FilterIndexReader(IndexReader.open(dir3, false)));
134         return pr;
135       }
136       
137     });
138     dir3.close();
139     dir4.close();
140   }
141
142   // LUCENE-1228: IndexWriter.commit() does not update the index version
143   // populate an index in iterations.
144   // at the end of every iteration, commit the index and reopen/recreate the reader.
145   // in each iteration verify the work of previous iteration. 
146   // try this once with reopen once recreate, on both RAMDir and FSDir.
147   public void testCommitReopen () throws IOException {
148     Directory dir = newDirectory();
149     doTestReopenWithCommit(random, dir, true);
150     dir.close();
151   }
152   public void testCommitRecreate () throws IOException {
153     Directory dir = newDirectory();
154     doTestReopenWithCommit(random, dir, false);
155     dir.close();
156   }
157
158   private void doTestReopenWithCommit (Random random, Directory dir, boolean withReopen) throws IOException {
159     IndexWriter iwriter = new IndexWriter(dir, newIndexWriterConfig(
160         TEST_VERSION_CURRENT, new MockAnalyzer(random)).setOpenMode(
161                                                               OpenMode.CREATE).setMergeScheduler(new SerialMergeScheduler()).setMergePolicy(newLogMergePolicy()));
162     iwriter.commit();
163     IndexReader reader = IndexReader.open(dir, false);
164     try {
165       int M = 3;
166       for (int i=0; i<4; i++) {
167         for (int j=0; j<M; j++) {
168           Document doc = new Document();
169           doc.add(newField("id", i+"_"+j, Store.YES, Index.NOT_ANALYZED));
170           doc.add(newField("id2", i+"_"+j, Store.YES, Index.NOT_ANALYZED_NO_NORMS));
171           doc.add(newField("id3", i+"_"+j, Store.YES, Index.NO));
172           iwriter.addDocument(doc);
173           if (i>0) {
174             int k = i-1;
175             int n = j + k*M;
176             Document prevItereationDoc = reader.document(n);
177             assertNotNull(prevItereationDoc);
178             String id = prevItereationDoc.get("id");
179             assertEquals(k+"_"+j, id);
180           }
181         }
182         iwriter.commit();
183         if (withReopen) {
184           // reopen
185           IndexReader r2 = IndexReader.openIfChanged(reader);
186           if (r2 != null) {
187             reader.close();
188             reader = r2;
189           }
190         } else {
191           // recreate
192           reader.close();
193           reader = IndexReader.open(dir, false);
194         }
195       }
196     } finally {
197       iwriter.close();
198       reader.close();
199     }
200   }
201   
202   public void testMultiReaderReopen() throws Exception {
203     final Directory dir1 = newDirectory();
204     createIndex(random, dir1, true);
205
206     final Directory dir2 = newDirectory();
207     createIndex(random, dir2, true);
208
209     performDefaultTests(new TestReopen() {
210
211       @Override
212       protected void modifyIndex(int i) throws IOException {
213         TestIndexReaderReopen.modifyIndex(i, dir1);
214         TestIndexReaderReopen.modifyIndex(i, dir2);
215       }
216
217       @Override
218       protected IndexReader openReader() throws IOException {
219         return new MultiReader(new IndexReader[] 
220                         {IndexReader.open(dir1, false), 
221                          IndexReader.open(dir2, false)});
222       }
223       
224     });
225
226     dir1.close();
227     dir2.close();
228     
229     final Directory dir3 = newDirectory();
230     createIndex(random, dir3, true);
231
232     final Directory dir4 = newDirectory();
233     createIndex(random, dir4, true);
234
235     performTestsWithExceptionInReopen(new TestReopen() {
236
237       @Override
238       protected void modifyIndex(int i) throws IOException {
239         TestIndexReaderReopen.modifyIndex(i, dir3);
240         TestIndexReaderReopen.modifyIndex(i, dir4);
241       }
242
243       @Override
244       protected IndexReader openReader() throws IOException {
245         return new MultiReader(new IndexReader[] 
246                         {IndexReader.open(dir3, false), 
247                          IndexReader.open(dir4, false),
248                          // Does not implement reopen, so
249                          // hits exception:
250                          new FilterIndexReader(IndexReader.open(dir3, false))});
251       }
252       
253     });
254     dir3.close();
255     dir4.close();
256   }
257
258   public void testMixedReaders() throws Exception {
259     final Directory dir1 = newDirectory();
260     createIndex(random, dir1, true);
261     final Directory dir2 = newDirectory();
262     createIndex(random, dir2, true);
263     final Directory dir3 = newDirectory();
264     createIndex(random, dir3, false);
265     final Directory dir4 = newDirectory();
266     createIndex(random, dir4, true);
267     final Directory dir5 = newDirectory();
268     createIndex(random, dir5, false);
269     
270     performDefaultTests(new TestReopen() {
271
272       @Override
273       protected void modifyIndex(int i) throws IOException {
274         // only change norms in this index to maintain the same number of docs for each of ParallelReader's subreaders
275         if (i == 1) TestIndexReaderReopen.modifyIndex(i, dir1);  
276         
277         TestIndexReaderReopen.modifyIndex(i, dir4);
278         TestIndexReaderReopen.modifyIndex(i, dir5);
279       }
280
281       @Override
282       protected IndexReader openReader() throws IOException {
283         ParallelReader pr = new ParallelReader();
284         pr.add(IndexReader.open(dir1, false));
285         pr.add(IndexReader.open(dir2, false));
286         MultiReader mr = new MultiReader(new IndexReader[] {
287             IndexReader.open(dir3, false), IndexReader.open(dir4, false)});
288         return new MultiReader(new IndexReader[] {
289            pr, mr, IndexReader.open(dir5, false)});
290       }
291     });
292     dir1.close();
293     dir2.close();
294     dir3.close();
295     dir4.close();
296     dir5.close();
297   }  
298   
299   private void performDefaultTests(TestReopen test) throws Exception {
300
301     IndexReader index1 = test.openReader();
302     IndexReader index2 = test.openReader();
303         
304     TestIndexReader.assertIndexEquals(index1, index2);
305
306     // verify that reopen() does not return a new reader instance
307     // in case the index has no changes
308     ReaderCouple couple = refreshReader(index2, false);
309     assertTrue(couple.refreshedReader == index2);
310     
311     couple = refreshReader(index2, test, 0, true);
312     index1.close();
313     index1 = couple.newReader;
314
315     IndexReader index2_refreshed = couple.refreshedReader;
316     index2.close();
317     
318     // test if refreshed reader and newly opened reader return equal results
319     TestIndexReader.assertIndexEquals(index1, index2_refreshed);
320
321     index2_refreshed.close();
322     assertReaderClosed(index2, true, true);
323     assertReaderClosed(index2_refreshed, true, true);
324
325     index2 = test.openReader();
326     
327     for (int i = 1; i < 4; i++) {
328       
329       index1.close();
330       couple = refreshReader(index2, test, i, true);
331       // refresh IndexReader
332       index2.close();
333       
334       index2 = couple.refreshedReader;
335       index1 = couple.newReader;
336       TestIndexReader.assertIndexEquals(index1, index2);
337     }
338     
339     index1.close();
340     index2.close();
341     assertReaderClosed(index1, true, true);
342     assertReaderClosed(index2, true, true);
343   }
344   
345   public void testReferenceCounting() throws IOException {
346     for (int mode = 0; mode < 4; mode++) {
347       Directory dir1 = newDirectory();
348       createIndex(random, dir1, true);
349      
350       IndexReader reader0 = IndexReader.open(dir1, false);
351       assertRefCountEquals(1, reader0);
352
353       assertTrue(reader0 instanceof DirectoryReader);
354       IndexReader[] subReaders0 = reader0.getSequentialSubReaders();
355       for (int i = 0; i < subReaders0.length; i++) {
356         assertRefCountEquals(1, subReaders0[i]);
357       }
358       
359       // delete first document, so that only one of the subReaders have to be re-opened
360       IndexReader modifier = IndexReader.open(dir1, false);
361       modifier.deleteDocument(0);
362       modifier.close();
363       
364       IndexReader reader1 = refreshReader(reader0, true).refreshedReader;
365       assertTrue(reader1 instanceof DirectoryReader);
366       IndexReader[] subReaders1 = reader1.getSequentialSubReaders();
367       assertEquals(subReaders0.length, subReaders1.length);
368       
369       for (int i = 0; i < subReaders0.length; i++) {
370         if (subReaders0[i] != subReaders1[i]) {
371           assertRefCountEquals(1, subReaders0[i]);
372           assertRefCountEquals(1, subReaders1[i]);
373         } else {
374           assertRefCountEquals(2, subReaders0[i]);
375         }
376       }
377
378       // delete first document, so that only one of the subReaders have to be re-opened
379       modifier = IndexReader.open(dir1, false);
380       modifier.deleteDocument(1);
381       modifier.close();
382
383       IndexReader reader2 = refreshReader(reader1, true).refreshedReader;
384       assertTrue(reader2 instanceof DirectoryReader);
385       IndexReader[] subReaders2 = reader2.getSequentialSubReaders();
386       assertEquals(subReaders1.length, subReaders2.length);
387       
388       for (int i = 0; i < subReaders2.length; i++) {
389         if (subReaders2[i] == subReaders1[i]) {
390           if (subReaders1[i] == subReaders0[i]) {
391             assertRefCountEquals(3, subReaders2[i]);
392           } else {
393             assertRefCountEquals(2, subReaders2[i]);
394           }
395         } else {
396           assertRefCountEquals(1, subReaders2[i]);
397           if (subReaders0[i] == subReaders1[i]) {
398             assertRefCountEquals(2, subReaders2[i]);
399             assertRefCountEquals(2, subReaders0[i]);
400           } else {
401             assertRefCountEquals(1, subReaders0[i]);
402             assertRefCountEquals(1, subReaders1[i]);
403           }
404         }
405       }
406       
407       IndexReader reader3 = refreshReader(reader0, true).refreshedReader;
408       assertTrue(reader3 instanceof DirectoryReader);
409       IndexReader[] subReaders3 = reader3.getSequentialSubReaders();
410       assertEquals(subReaders3.length, subReaders0.length);
411       
412       // try some permutations
413       switch (mode) {
414       case 0:
415         reader0.close();
416         reader1.close();
417         reader2.close();
418         reader3.close();
419         break;
420       case 1:
421         reader3.close();
422         reader2.close();
423         reader1.close();
424         reader0.close();
425         break;
426       case 2:
427         reader2.close();
428         reader3.close();
429         reader0.close();
430         reader1.close();
431         break;
432       case 3:
433         reader1.close();
434         reader3.close();
435         reader2.close();
436         reader0.close();
437         break;
438       }      
439       
440       assertReaderClosed(reader0, true, true);
441       assertReaderClosed(reader1, true, true);
442       assertReaderClosed(reader2, true, true);
443       assertReaderClosed(reader3, true, true);
444
445       dir1.close();
446     }
447   }
448
449
450   public void testReferenceCountingMultiReader() throws IOException {
451     for (int mode = 0; mode <=1; mode++) {
452       Directory dir1 = newDirectory();
453       createIndex(random, dir1, false);
454       Directory dir2 = newDirectory();
455       createIndex(random, dir2, true);
456       
457       IndexReader reader1 = IndexReader.open(dir1, false);
458       assertRefCountEquals(1, reader1);
459
460       IndexReader initReader2 = IndexReader.open(dir2, false);
461       IndexReader multiReader1 = new MultiReader(new IndexReader[] {reader1, initReader2}, (mode == 0));
462       modifyIndex(0, dir2);
463       assertRefCountEquals(1 + mode, reader1);
464       
465       IndexReader multiReader2 = IndexReader.openIfChanged(multiReader1);
466       assertNotNull(multiReader2);
467       // index1 hasn't changed, so multiReader2 should share reader1 now with multiReader1
468       assertRefCountEquals(2 + mode, reader1);
469       
470       modifyIndex(0, dir1);
471       IndexReader reader2 = IndexReader.openIfChanged(reader1);
472       assertNotNull(reader2);
473       assertNull(IndexReader.openIfChanged(reader2));
474       assertRefCountEquals(2 + mode, reader1);
475
476       if (mode == 1) {
477         initReader2.close();
478       }
479       
480       modifyIndex(1, dir1);
481       IndexReader reader3 = IndexReader.openIfChanged(reader2);
482       assertNotNull(reader3);
483       assertRefCountEquals(2 + mode, reader1);
484       assertRefCountEquals(1, reader2);
485       
486       multiReader1.close();
487       assertRefCountEquals(1 + mode, reader1);
488       
489       multiReader1.close();
490       assertRefCountEquals(1 + mode, reader1);
491
492       if (mode == 1) {
493         initReader2.close();
494       }
495       
496       reader1.close();
497       assertRefCountEquals(1, reader1);
498       
499       multiReader2.close();
500       assertRefCountEquals(0, reader1);
501       
502       multiReader2.close();
503       assertRefCountEquals(0, reader1);
504       
505       reader3.close();
506       assertRefCountEquals(0, reader1);
507       assertReaderClosed(reader1, true, false);
508       
509       reader2.close();
510       assertRefCountEquals(0, reader1);
511       assertReaderClosed(reader1, true, false);
512       
513       reader2.close();
514       assertRefCountEquals(0, reader1);
515       
516       reader3.close();
517       assertRefCountEquals(0, reader1);
518       assertReaderClosed(reader1, true, true);
519       dir1.close();
520       dir2.close();
521     }
522
523   }
524
525   public void testReferenceCountingParallelReader() throws IOException {
526     for (int mode = 0; mode <=1; mode++) {
527       Directory dir1 = newDirectory();
528       createIndex(random, dir1, false);
529       Directory dir2 = newDirectory();
530       createIndex(random, dir2, true);
531       
532       IndexReader reader1 = IndexReader.open(dir1, false);
533       assertRefCountEquals(1, reader1);
534       
535       ParallelReader parallelReader1 = new ParallelReader(mode == 0);
536       parallelReader1.add(reader1);
537       IndexReader initReader2 = IndexReader.open(dir2, false);
538       parallelReader1.add(initReader2);
539       modifyIndex(1, dir2);
540       assertRefCountEquals(1 + mode, reader1);
541       
542       IndexReader parallelReader2 = IndexReader.openIfChanged(parallelReader1);
543       assertNotNull(parallelReader2);
544       assertNull(IndexReader.openIfChanged(parallelReader2));
545       // index1 hasn't changed, so parallelReader2 should share reader1 now with multiReader1
546       assertRefCountEquals(2 + mode, reader1);
547       
548       modifyIndex(0, dir1);
549       modifyIndex(0, dir2);
550       IndexReader reader2 = IndexReader.openIfChanged(reader1);
551       assertNotNull(reader2);
552       assertRefCountEquals(2 + mode, reader1);
553
554       if (mode == 1) {
555         initReader2.close();
556       }
557       
558       modifyIndex(4, dir1);
559       IndexReader reader3 = IndexReader.openIfChanged(reader2);
560       assertNotNull(reader3);
561       assertRefCountEquals(2 + mode, reader1);
562       assertRefCountEquals(1, reader2);
563       
564       parallelReader1.close();
565       assertRefCountEquals(1 + mode, reader1);
566       
567       parallelReader1.close();
568       assertRefCountEquals(1 + mode, reader1);
569
570       if (mode == 1) {
571         initReader2.close();
572       }
573       
574       reader1.close();
575       assertRefCountEquals(1, reader1);
576       
577       parallelReader2.close();
578       assertRefCountEquals(0, reader1);
579       
580       parallelReader2.close();
581       assertRefCountEquals(0, reader1);
582       
583       reader3.close();
584       assertRefCountEquals(0, reader1);
585       assertReaderClosed(reader1, true, false);
586       
587       reader2.close();
588       assertRefCountEquals(0, reader1);
589       assertReaderClosed(reader1, true, false);
590       
591       reader2.close();
592       assertRefCountEquals(0, reader1);
593       
594       reader3.close();
595       assertRefCountEquals(0, reader1);
596       assertReaderClosed(reader1, true, true);
597
598       dir1.close();
599       dir2.close();
600     }
601
602   }
603   
604   public void testNormsRefCounting() throws IOException {
605     Directory dir1 = newDirectory();
606     createIndex(random, dir1, false);
607     
608     IndexReader reader1 = IndexReader.open(dir1, false);
609     SegmentReader segmentReader1 = SegmentReader.getOnlySegmentReader(reader1);
610     IndexReader modifier = IndexReader.open(dir1, false);
611     modifier.deleteDocument(0);
612     modifier.close();
613     
614     IndexReader reader2 = IndexReader.openIfChanged(reader1);
615     assertNotNull(reader2);
616     modifier = IndexReader.open(dir1, false);
617     modifier.setNorm(1, "field1", 50);
618     modifier.setNorm(1, "field2", 50);
619     modifier.close();
620     
621     IndexReader reader3 = IndexReader.openIfChanged(reader2);
622     assertNotNull(reader3);
623     SegmentReader segmentReader3 = SegmentReader.getOnlySegmentReader(reader3);
624     modifier = IndexReader.open(dir1, false);
625     modifier.deleteDocument(2);
626     modifier.close();
627
628     IndexReader reader4 = IndexReader.openIfChanged(reader3);
629     assertNotNull(reader4);
630     modifier = IndexReader.open(dir1, false);
631     modifier.deleteDocument(3);
632     modifier.close();
633
634     IndexReader reader5 = IndexReader.openIfChanged(reader3);
635     assertNotNull(reader5);
636     
637     // Now reader2-reader5 references reader1. reader1 and reader2
638     // share the same norms. reader3, reader4, reader5 also share norms.
639     assertRefCountEquals(1, reader1);
640     assertFalse(segmentReader1.normsClosed());
641
642     reader1.close();
643
644     assertRefCountEquals(0, reader1);
645     assertFalse(segmentReader1.normsClosed());
646
647     reader2.close();
648     assertRefCountEquals(0, reader1);
649
650     // now the norms for field1 and field2 should be closed
651     assertTrue(segmentReader1.normsClosed("field1"));
652     assertTrue(segmentReader1.normsClosed("field2"));
653
654     // but the norms for field3 and field4 should still be open
655     assertFalse(segmentReader1.normsClosed("field3"));
656     assertFalse(segmentReader1.normsClosed("field4"));
657     
658     reader3.close();
659     assertRefCountEquals(0, reader1);
660     assertFalse(segmentReader3.normsClosed());
661     reader5.close();
662     assertRefCountEquals(0, reader1);
663     assertFalse(segmentReader3.normsClosed());
664     reader4.close();
665     assertRefCountEquals(0, reader1);
666     
667     // and now all norms that reader1 used should be closed
668     assertTrue(segmentReader1.normsClosed());
669     
670     // now that reader3, reader4 and reader5 are closed,
671     // the norms that those three readers shared should be
672     // closed as well
673     assertTrue(segmentReader3.normsClosed());
674
675     dir1.close();
676   }
677   
678   private void performTestsWithExceptionInReopen(TestReopen test) throws Exception {
679     IndexReader index1 = test.openReader();
680     IndexReader index2 = test.openReader();
681
682     TestIndexReader.assertIndexEquals(index1, index2);
683     
684     try {
685       refreshReader(index1, test, 0, true);
686       fail("Expected exception not thrown.");
687     } catch (Exception e) {
688       // expected exception
689     }
690     
691     // index2 should still be usable and unaffected by the failed reopen() call
692     TestIndexReader.assertIndexEquals(index1, index2);
693
694     index1.close();
695     index2.close();
696   }
697   
698   public void testThreadSafety() throws Exception {
699     final Directory dir = newDirectory();
700     // NOTE: this also controls the number of threads!
701     final int n = _TestUtil.nextInt(random, 20, 40);
702     IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(
703         TEST_VERSION_CURRENT, new MockAnalyzer(random)));
704     for (int i = 0; i < n; i++) {
705       writer.addDocument(createDocument(i, 3));
706     }
707     writer.forceMerge(1);
708     writer.close();
709
710     final TestReopen test = new TestReopen() {      
711       @Override
712       protected void modifyIndex(int i) throws IOException {
713         if (i % 3 == 0) {
714           IndexReader modifier = IndexReader.open(dir, false);
715           modifier.setNorm(i, "field1", 50);
716           modifier.close();
717         } else if (i % 3 == 1) {
718           IndexReader modifier = IndexReader.open(dir, false);
719           modifier.deleteDocument(i % modifier.maxDoc());
720           modifier.close();
721         } else {
722           IndexWriter modifier = new IndexWriter(dir, new IndexWriterConfig(
723               TEST_VERSION_CURRENT, new MockAnalyzer(random)));
724           modifier.addDocument(createDocument(n + i, 6));
725           modifier.close();
726         }
727       }
728
729       @Override
730       protected IndexReader openReader() throws IOException {
731         return IndexReader.open(dir, false);
732       }      
733     };
734     
735     final List<ReaderCouple> readers = Collections.synchronizedList(new ArrayList<ReaderCouple>());
736     IndexReader firstReader = IndexReader.open(dir, false);
737     IndexReader reader = firstReader;
738     final Random rnd = random;
739     
740     ReaderThread[] threads = new ReaderThread[n];
741     final Set<IndexReader> readersToClose = Collections.synchronizedSet(new HashSet<IndexReader>());
742     
743     for (int i = 0; i < n; i++) {
744       if (i % 2 == 0) {
745         IndexReader refreshed = IndexReader.openIfChanged(reader);
746         if (refreshed != null) {
747           readersToClose.add(reader);
748           reader = refreshed;
749         }
750       }
751       final IndexReader r = reader;
752       
753       final int index = i;    
754       
755       ReaderThreadTask task;
756       
757       if (i < 4 || (i >=10 && i < 14) || i > 18) {
758         task = new ReaderThreadTask() {
759           
760           @Override
761           public void run() throws Exception {
762             while (!stopped) {
763               if (index % 2 == 0) {
764                 // refresh reader synchronized
765                 ReaderCouple c = (refreshReader(r, test, index, true));
766                 readersToClose.add(c.newReader);
767                 readersToClose.add(c.refreshedReader);
768                 readers.add(c);
769                 // prevent too many readers
770                 break;
771               } else {
772                 // not synchronized
773                 IndexReader refreshed = IndexReader.openIfChanged(r);
774                 if (refreshed == null) {
775                   refreshed = r;
776                 }
777                 
778                 IndexSearcher searcher = newSearcher(refreshed);
779                 ScoreDoc[] hits = searcher.search(
780                     new TermQuery(new Term("field1", "a" + rnd.nextInt(refreshed.maxDoc()))),
781                     null, 1000).scoreDocs;
782                 if (hits.length > 0) {
783                   searcher.doc(hits[0].doc);
784                 }
785                 searcher.close();
786                 if (refreshed != r) {
787                   refreshed.close();
788                 }
789               }
790               synchronized(this) {
791                 wait(_TestUtil.nextInt(random, 1, 100));
792               }
793             }
794           }
795           
796         };
797       } else {
798         task = new ReaderThreadTask() {
799           @Override
800           public void run() throws Exception {
801             while (!stopped) {
802               int numReaders = readers.size();
803               if (numReaders > 0) {
804                 ReaderCouple c =  readers.get(rnd.nextInt(numReaders));
805                 TestIndexReader.assertIndexEquals(c.newReader, c.refreshedReader);
806               }
807               
808               synchronized(this) {
809                 wait(_TestUtil.nextInt(random, 1, 100));
810               }
811             }
812           }
813         };
814       }
815       
816       threads[i] = new ReaderThread(task);
817       threads[i].start();
818     }
819     
820     synchronized(this) {
821       wait(1000);
822     }
823     
824     for (int i = 0; i < n; i++) {
825       if (threads[i] != null) {
826         threads[i].stopThread();
827       }
828     }
829     
830     for (int i = 0; i < n; i++) {
831       if (threads[i] != null) {
832         threads[i].join();
833         if (threads[i].error != null) {
834           String msg = "Error occurred in thread " + threads[i].getName() + ":\n" + threads[i].error.getMessage();
835           fail(msg);
836         }
837       }
838       
839     }
840     
841     for (final IndexReader readerToClose : readersToClose) {
842       readerToClose.close();
843     }
844     
845     firstReader.close();
846     reader.close();
847     
848     for (final IndexReader readerToClose : readersToClose) {
849       assertReaderClosed(readerToClose, true, true);
850     }
851
852     assertReaderClosed(reader, true, true);
853     assertReaderClosed(firstReader, true, true);
854
855     dir.close();
856   }
857   
858   private static class ReaderCouple {
859     ReaderCouple(IndexReader r1, IndexReader r2) {
860       newReader = r1;
861       refreshedReader = r2;
862     }
863     
864     IndexReader newReader;
865     IndexReader refreshedReader;
866   }
867   
868   private abstract static class ReaderThreadTask {
869     protected volatile boolean stopped;
870     public void stop() {
871       this.stopped = true;
872     }
873     
874     public abstract void run() throws Exception;
875   }
876   
877   private static class ReaderThread extends Thread {
878     private ReaderThreadTask task;
879     private Throwable error;
880     
881     
882     ReaderThread(ReaderThreadTask task) {
883       this.task = task;
884     }
885     
886     public void stopThread() {
887       this.task.stop();
888     }
889     
890     @Override
891     public void run() {
892       try {
893         this.task.run();
894       } catch (Throwable r) {
895         r.printStackTrace(System.out);
896         this.error = r;
897       }
898     }
899   }
900   
901   private Object createReaderMutex = new Object();
902   
903   private ReaderCouple refreshReader(IndexReader reader, boolean hasChanges) throws IOException {
904     return refreshReader(reader, null, -1, hasChanges);
905   }
906   
907   ReaderCouple refreshReader(IndexReader reader, TestReopen test, int modify, boolean hasChanges) throws IOException {
908     synchronized (createReaderMutex) {
909       IndexReader r = null;
910       if (test != null) {
911         test.modifyIndex(modify);
912         r = test.openReader();
913       }
914       
915       IndexReader refreshed = null;
916       try {
917         refreshed = IndexReader.openIfChanged(reader);
918         if (refreshed == null) {
919           refreshed = reader;
920         }
921       } finally {
922         if (refreshed == null && r != null) {
923           // Hit exception -- close opened reader
924           r.close();
925         }
926       }
927       
928       if (hasChanges) {
929         if (refreshed == reader) {
930           fail("No new IndexReader instance created during refresh.");
931         }
932       } else {
933         if (refreshed != reader) {
934           fail("New IndexReader instance created during refresh even though index had no changes.");
935         }
936       }
937       
938       return new ReaderCouple(r, refreshed);
939     }
940   }
941   
942   public static void createIndex(Random random, Directory dir, boolean multiSegment) throws IOException {
943     IndexWriter.unlock(dir);
944     IndexWriter w = new IndexWriter(dir, LuceneTestCase.newIndexWriterConfig(random,
945         TEST_VERSION_CURRENT, new MockAnalyzer(random))
946         .setMergePolicy(new LogDocMergePolicy()));
947     
948     for (int i = 0; i < 100; i++) {
949       w.addDocument(createDocument(i, 4));
950       if (multiSegment && (i % 10) == 0) {
951         w.commit();
952       }
953     }
954     
955     if (!multiSegment) {
956       w.forceMerge(1);
957     }
958     
959     w.close();
960
961     IndexReader r = IndexReader.open(dir, false);
962     if (multiSegment) {
963       assertTrue(r.getSequentialSubReaders().length > 1);
964     } else {
965       assertTrue(r.getSequentialSubReaders().length == 1);
966     }
967     r.close();
968   }
969
970   public static Document createDocument(int n, int numFields) {
971     StringBuilder sb = new StringBuilder();
972     Document doc = new Document();
973     sb.append("a");
974     sb.append(n);
975     doc.add(new Field("field1", sb.toString(), Store.YES, Index.ANALYZED));
976     doc.add(new Field("fielda", sb.toString(), Store.YES, Index.NOT_ANALYZED_NO_NORMS));
977     doc.add(new Field("fieldb", sb.toString(), Store.YES, Index.NO));
978     sb.append(" b");
979     sb.append(n);
980     for (int i = 1; i < numFields; i++) {
981       doc.add(new Field("field" + (i+1), sb.toString(), Store.YES, Index.ANALYZED));
982     }
983     return doc;
984   }
985
986   static void modifyIndex(int i, Directory dir) throws IOException {
987     switch (i) {
988       case 0: {
989         if (VERBOSE) {
990           System.out.println("TEST: modify index");
991         }
992         IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)));
993         w.setInfoStream(VERBOSE ? System.out : null);
994         w.deleteDocuments(new Term("field2", "a11"));
995         w.deleteDocuments(new Term("field2", "b30"));
996         w.close();
997         break;
998       }
999       case 1: {
1000         IndexReader reader = IndexReader.open(dir, false);
1001         reader.setNorm(4, "field1", 123);
1002         reader.setNorm(44, "field2", 222);
1003         reader.setNorm(44, "field4", 22);
1004         reader.close();
1005         break;
1006       }
1007       case 2: {
1008         IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)));
1009         w.forceMerge(1);
1010         w.close();
1011         break;
1012       }
1013       case 3: {
1014         IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)));
1015         w.addDocument(createDocument(101, 4));
1016         w.forceMerge(1);
1017         w.addDocument(createDocument(102, 4));
1018         w.addDocument(createDocument(103, 4));
1019         w.close();
1020         break;
1021       }
1022       case 4: {
1023         IndexReader reader = IndexReader.open(dir, false);
1024         reader.setNorm(5, "field1", 123);
1025         reader.setNorm(55, "field2", 222);
1026         reader.close();
1027         break;
1028       }
1029       case 5: {
1030         IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)));
1031         w.addDocument(createDocument(101, 4));
1032         w.close();
1033         break;
1034       }
1035     }
1036   }  
1037   
1038   private void assertReaderClosed(IndexReader reader, boolean checkSubReaders, boolean checkNormsClosed) {
1039     assertEquals(0, reader.getRefCount());
1040     
1041     if (checkNormsClosed && reader instanceof SegmentReader) {
1042       assertTrue(((SegmentReader) reader).normsClosed());
1043     }
1044     
1045     if (checkSubReaders) {
1046       if (reader instanceof DirectoryReader) {
1047         IndexReader[] subReaders = reader.getSequentialSubReaders();
1048         for (int i = 0; i < subReaders.length; i++) {
1049           assertReaderClosed(subReaders[i], checkSubReaders, checkNormsClosed);
1050         }
1051       }
1052       
1053       if (reader instanceof MultiReader) {
1054         IndexReader[] subReaders = reader.getSequentialSubReaders();
1055         for (int i = 0; i < subReaders.length; i++) {
1056           assertReaderClosed(subReaders[i], checkSubReaders, checkNormsClosed);
1057         }
1058       }
1059       
1060       if (reader instanceof ParallelReader) {
1061         IndexReader[] subReaders = ((ParallelReader) reader).getSubReaders();
1062         for (int i = 0; i < subReaders.length; i++) {
1063           assertReaderClosed(subReaders[i], checkSubReaders, checkNormsClosed);
1064         }
1065       }
1066     }
1067   }
1068
1069   /*
1070   private void assertReaderOpen(IndexReader reader) {
1071     reader.ensureOpen();
1072     
1073     if (reader instanceof DirectoryReader) {
1074       IndexReader[] subReaders = reader.getSequentialSubReaders();
1075       for (int i = 0; i < subReaders.length; i++) {
1076         assertReaderOpen(subReaders[i]);
1077       }
1078     }
1079   }
1080   */
1081
1082   private void assertRefCountEquals(int refCount, IndexReader reader) {
1083     assertEquals("Reader has wrong refCount value.", refCount, reader.getRefCount());
1084   }
1085
1086
1087   private abstract static class TestReopen {
1088     protected abstract IndexReader openReader() throws IOException;
1089     protected abstract void modifyIndex(int i) throws IOException;
1090   }
1091   
1092   public void testCloseOrig() throws Throwable {
1093     Directory dir = newDirectory();
1094     createIndex(random, dir, false);
1095     IndexReader r1 = IndexReader.open(dir, false);
1096     IndexReader r2 = IndexReader.open(dir, false);
1097     r2.deleteDocument(0);
1098     r2.close();
1099
1100     IndexReader r3 = IndexReader.openIfChanged(r1);
1101     assertNotNull(r3);
1102     assertTrue(r1 != r3);
1103     r1.close();
1104     try {
1105       r1.document(2);
1106       fail("did not hit exception");
1107     } catch (AlreadyClosedException ace) {
1108       // expected
1109     }
1110     r3.close();
1111     dir.close();
1112   }
1113
1114   public void testDeletes() throws Throwable {
1115     Directory dir = newDirectory();
1116     createIndex(random, dir, false); // Create an index with a bunch of docs (1 segment)
1117
1118     modifyIndex(0, dir); // Get delete bitVector on 1st segment
1119     modifyIndex(5, dir); // Add a doc (2 segments)
1120
1121     IndexReader r1 = IndexReader.open(dir, false); // MSR
1122
1123     modifyIndex(5, dir); // Add another doc (3 segments)
1124
1125     IndexReader r2 = IndexReader.openIfChanged(r1); // MSR
1126     assertNotNull(r2);
1127     assertNull(IndexReader.openIfChanged(r2));
1128     assertTrue(r1 != r2);
1129
1130     SegmentReader sr1 = (SegmentReader) r1.getSequentialSubReaders()[0]; // Get SRs for the first segment from original
1131     SegmentReader sr2 = (SegmentReader) r2.getSequentialSubReaders()[0]; // and reopened IRs
1132
1133     // At this point they share the same BitVector
1134     assertTrue(sr1.deletedDocs==sr2.deletedDocs);
1135
1136     r2.deleteDocument(0);
1137
1138     // r1 should not see the delete
1139     assertFalse(r1.isDeleted(0));
1140
1141     // Now r2 should have made a private copy of deleted docs:
1142     assertTrue(sr1.deletedDocs!=sr2.deletedDocs);
1143
1144     r1.close();
1145     r2.close();
1146     dir.close();
1147   }
1148
1149   public void testDeletes2() throws Throwable {
1150     Directory dir = newDirectory();
1151     createIndex(random, dir, false);
1152     // Get delete bitVector
1153     modifyIndex(0, dir);
1154     IndexReader r1 = IndexReader.open(dir, false);
1155
1156     // Add doc:
1157     modifyIndex(5, dir);
1158
1159     IndexReader r2 = IndexReader.openIfChanged(r1);
1160     assertNotNull(r2);
1161     assertTrue(r1 != r2);
1162
1163     IndexReader[] rs2 = r2.getSequentialSubReaders();
1164
1165     SegmentReader sr1 = SegmentReader.getOnlySegmentReader(r1);
1166     SegmentReader sr2 = (SegmentReader) rs2[0];
1167
1168     // At this point they share the same BitVector
1169     assertTrue(sr1.deletedDocs==sr2.deletedDocs);
1170     final BitVector delDocs = sr1.deletedDocs;
1171     r1.close();
1172
1173     r2.deleteDocument(0);
1174     assertTrue(delDocs==sr2.deletedDocs);
1175     r2.close();
1176     dir.close();
1177   }
1178
1179   private static class KeepAllCommits implements IndexDeletionPolicy {
1180     public void onInit(List<? extends IndexCommit> commits) {
1181     }
1182     public void onCommit(List<? extends IndexCommit> commits) {
1183     }
1184   }
1185
1186   public void testReopenOnCommit() throws Throwable {
1187     Directory dir = newDirectory();
1188     IndexWriter writer = new IndexWriter(
1189         dir,
1190         newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).
1191             setIndexDeletionPolicy(new KeepAllCommits()).
1192             setMaxBufferedDocs(-1).
1193             setMergePolicy(newLogMergePolicy(10))
1194     );
1195     for(int i=0;i<4;i++) {
1196       Document doc = new Document();
1197       doc.add(newField("id", ""+i, Field.Store.NO, Field.Index.NOT_ANALYZED));
1198       writer.addDocument(doc);
1199       Map<String,String> data = new HashMap<String,String>();
1200       data.put("index", i+"");
1201       writer.commit(data);
1202     }
1203     for(int i=0;i<4;i++) {
1204       writer.deleteDocuments(new Term("id", ""+i));
1205       Map<String,String> data = new HashMap<String,String>();
1206       data.put("index", (4+i)+"");
1207       writer.commit(data);
1208     }
1209     writer.close();
1210
1211     IndexReader r = IndexReader.open(dir, false);
1212     assertEquals(0, r.numDocs());
1213
1214     Collection<IndexCommit> commits = IndexReader.listCommits(dir);
1215     for (final IndexCommit commit : commits) {
1216       IndexReader r2 = IndexReader.openIfChanged(r, commit);
1217       assertNotNull(r2);
1218       assertTrue(r2 != r);
1219
1220       // Reader should be readOnly
1221       try {
1222         r2.deleteDocument(0);
1223         fail("no exception hit");
1224       } catch (UnsupportedOperationException uoe) {
1225         // expected
1226       }
1227
1228       final Map<String,String> s = commit.getUserData();
1229       final int v;
1230       if (s.size() == 0) {
1231         // First commit created by IW
1232         v = -1;
1233       } else {
1234         v = Integer.parseInt(s.get("index"));
1235       }
1236       if (v < 4) {
1237         assertEquals(1+v, r2.numDocs());
1238       } else {
1239         assertEquals(7-v, r2.numDocs());
1240       }
1241       r.close();
1242       r = r2;
1243     }
1244     r.close();
1245     dir.close();
1246   }
1247   
1248   // LUCENE-1579: Make sure all SegmentReaders are new when
1249   // reopen switches readOnly
1250   public void testReopenChangeReadonly() throws Exception {
1251     Directory dir = newDirectory();
1252     IndexWriter writer = new IndexWriter(
1253         dir,
1254         newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).
1255             setMaxBufferedDocs(-1).
1256             setMergePolicy(newLogMergePolicy(10))
1257     );
1258     Document doc = new Document();
1259     doc.add(newField("number", "17", Field.Store.NO, Field.Index.NOT_ANALYZED));
1260     writer.addDocument(doc);
1261     writer.commit();
1262
1263     // Open reader1
1264     IndexReader r = IndexReader.open(dir, false);
1265     assertTrue(r instanceof DirectoryReader);
1266     IndexReader r1 = SegmentReader.getOnlySegmentReader(r);
1267     final int[] ints = FieldCache.DEFAULT.getInts(r1, "number");
1268     assertEquals(1, ints.length);
1269     assertEquals(17, ints[0]);
1270
1271     // Reopen to readonly w/ no chnages
1272     IndexReader r3 = IndexReader.openIfChanged(r, true);
1273     assertNotNull(r3);
1274     assertTrue(r3 instanceof ReadOnlyDirectoryReader);
1275     r3.close();
1276
1277     // Add new segment
1278     writer.addDocument(doc);
1279     writer.commit();
1280
1281     // Reopen reader1 --> reader2
1282     IndexReader r2 = IndexReader.openIfChanged(r, true);
1283     assertNotNull(r2);
1284     r.close();
1285     assertTrue(r2 instanceof ReadOnlyDirectoryReader);
1286     IndexReader[] subs = r2.getSequentialSubReaders();
1287     final int[] ints2 = FieldCache.DEFAULT.getInts(subs[0], "number");
1288     r2.close();
1289
1290     assertTrue(subs[0] instanceof ReadOnlySegmentReader);
1291     assertTrue(subs[1] instanceof ReadOnlySegmentReader);
1292     assertTrue(ints == ints2);
1293
1294     writer.close();
1295     dir.close();
1296   }
1297 }