From 862c44215a5c45d096e9dc26334ba46b5900d814 Mon Sep 17 00:00:00 2001 From: Michael McCandless Date: Mon, 11 Feb 2008 18:56:09 +0000 Subject: [PATCH] LUCENE-1044: sync index files in IndexWriter to ensure index is intact if machine or OS crashes git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@620576 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 19 + docs/fileformats.html | 43 +- docs/fileformats.pdf | 322 ++--- .../org/apache/lucene/index/CheckIndex.java | 7 +- .../index/ConcurrentMergeScheduler.java | 12 +- .../lucene/index/DirectoryIndexReader.java | 37 +- .../apache/lucene/index/DocumentsWriter.java | 3 +- .../apache/lucene/index/IndexFileDeleter.java | 64 +- .../org/apache/lucene/index/IndexWriter.java | 1227 +++++++++++------ .../org/apache/lucene/index/SegmentInfos.java | 93 +- .../lucene/store/ChecksumIndexInput.java | 67 + .../lucene/store/ChecksumIndexOutput.java | 68 + .../org/apache/lucene/store/Directory.java | 5 + .../org/apache/lucene/store/FSDirectory.java | 33 + .../content/xdocs/fileformats.xml | 19 +- .../apache/lucene/index/TestAtomicUpdate.java | 7 +- .../index/TestBackwardsCompatibility.java | 6 +- .../org/apache/lucene/index/TestCrash.java | 181 +++ .../lucene/index/TestDeletionPolicy.java | 20 +- .../lucene/index/TestIndexFileDeleter.java | 17 +- .../lucene/index/TestIndexModifier.java | 6 +- .../apache/lucene/index/TestIndexReader.java | 14 +- .../apache/lucene/index/TestIndexWriter.java | 224 ++- .../lucene/index/TestIndexWriterDelete.java | 172 +-- .../lucene/index/TestMultiSegmentReader.java | 8 +- .../lucene/index/TestStressIndexing.java | 26 +- .../lucene/index/TestThreadedOptimize.java | 12 +- .../apache/lucene/store/MockRAMDirectory.java | 105 +- .../lucene/store/MockRAMInputStream.java | 13 +- .../lucene/store/MockRAMOutputStream.java | 5 + .../apache/lucene/util/LuceneTestCase.java | 3 + 31 files changed, 1915 insertions(+), 923 deletions(-) create mode 100644 src/java/org/apache/lucene/store/ChecksumIndexInput.java create mode 100644 src/java/org/apache/lucene/store/ChecksumIndexOutput.java create mode 100644 src/test/org/apache/lucene/index/TestCrash.java diff --git a/CHANGES.txt b/CHANGES.txt index 4221dcdc1c1..15243dcdabb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -18,6 +18,14 @@ Changes in runtime behavior compatibility will be removed in 3.0 (hardwiring the value to true). (Mike McCandless) + 2. LUCENE-1044: IndexWriter with autoCommit=true now commits (such + that a reader can see the changes) far less often than it used to. + Previously, every flush was also a commit. You can always force a + commit by calling IndexWriter.commit(). Furthermore, in 3.0, + autoCommit will be hardwired to false (IndexWriter constructors + that take an autoCommit argument have been deprecated) (Mike + McCandless) + API Changes 1. LUCENE-1084: Changed all IndexWriter constructors to take an @@ -36,6 +44,11 @@ API Changes the Lucene code base will need to be adapted. See also the javadocs of the Filter class. (Paul Elschot, Michael Busch) + 4. LUCENE-1044: Added IndexWriter.commit() which flushes any buffered + adds/deletes and then commits a new segments file so readers will + see the changes. Deprecate IndexWriter.flush() in favor of + IndexWriter.commit(). (Mike McCandless) + Bug fixes 1. LUCENE-1134: Fixed BooleanQuery.rewrite to only optimze a single @@ -75,6 +88,12 @@ New features 5. LUCENE-494: Added QueryAutoStopWordAnalyzer to allow for the automatic removal, from a query of frequently occurring terms. This Analyzer is not intended for use during indexing. (Mark Harwood via Grant Ingersoll) + 6. LUCENE-1044: Change Lucene to properly "sync" files after + committing, to ensure on a machine or OS crash or power cut, even + with cached writes, the index remains consistent. Also added + explicit commit() method to IndexWriter to force a commit without + having to close. (Mike McCandless) + Optimizations 1. LUCENE-705: When building a compound file, use diff --git a/docs/fileformats.html b/docs/fileformats.html index c18726b755d..40152affad2 100644 --- a/docs/fileformats.html +++ b/docs/fileformats.html @@ -1316,17 +1316,24 @@ document.write("Last Published: " + document.lastModified);

-2.3 and above: +2.3: Segments --> Format, Version, NameCounter, SegCount, <SegName, SegSize, DelGen, DocStoreOffset, [DocStoreSegment, DocStoreIsCompoundFile], HasSingleNormFile, NumField, NormGenNumField, IsCompoundFile>SegCount

+

+ +2.4 and above: + Segments --> Format, Version, NameCounter, SegCount, <SegName, SegSize, DelGen, DocStoreOffset, [DocStoreSegment, DocStoreIsCompoundFile], HasSingleNormFile, NumField, + NormGenNumField, + IsCompoundFile>SegCount, Checksum +

Format, NameCounter, SegCount, SegSize, NumField, DocStoreOffset --> Int32

- Version, DelGen, NormGen --> Int64 + Version, DelGen, NormGen, Checksum --> Int64

SegName, DocStoreSegment --> String @@ -1335,7 +1342,7 @@ document.write("Last Published: " + document.lastModified); IsCompoundFile, HasSingleNormFile, DocStoreIsCompoundFile --> Int8

- Format is -1 as of Lucene 1.4, -3 (SegmentInfos.FORMAT_SINGLE_NORM_FILE) as of Lucene 2.1 and 2.2, and -4 (SegmentInfos.FORMAT_SHARED_DOC_STORE) as of Lucene 2.3 + Format is -1 as of Lucene 1.4, -3 (SegmentInfos.FORMAT_SINGLE_NORM_FILE) as of Lucene 2.1 and 2.2, -4 (SegmentInfos.FORMAT_SHARED_DOC_STORE) as of Lucene 2.3 and -5 (SegmentInfos.FORMAT_CHECKSUM) as of Lucene 2.4.

Version counts how often the index has been @@ -1408,7 +1415,13 @@ document.write("Last Published: " + document.lastModified); shares a single set of these files with other segments.

- +

+ Checksum contains the CRC32 checksum of all bytes + in the segments_N file up until the checksum. + This is used to verify integrity of the file on + opening the index. +

+

Lock File

The write lock, which is stored in the index @@ -1426,7 +1439,7 @@ document.write("Last Published: " + document.lastModified); Note that prior to version 2.1, Lucene also used a commit lock. This was removed in 2.1.

- +

Deletable File

Prior to Lucene 2.1 there was a file "deletable" @@ -1435,7 +1448,7 @@ document.write("Last Published: " + document.lastModified); the files that are deletable, instead, so no file is written.

- +

Compound Files

Starting with Lucene 1.4 the compound file format became default. This is simply a container for all files described in the next section @@ -1462,14 +1475,14 @@ document.write("Last Published: " + document.lastModified); - +

Per-Segment Files

The remaining files are all per-segment, and are thus defined by suffix.

- +

Fields

@@ -1688,7 +1701,7 @@ document.write("Last Published: " + document.lastModified); - +

Term Dictionary

The term dictionary is represented as two files: @@ -1874,7 +1887,7 @@ document.write("Last Published: " + document.lastModified); - +

Frequencies

The .frq file contains the lists of documents @@ -1992,7 +2005,7 @@ document.write("Last Published: " + document.lastModified); entry in level-1. In the example has entry 15 on level 1 a pointer to entry 15 on level 0 and entry 31 on level 1 a pointer to entry 31 on level 0.

- +

Positions

The .prx file contains the lists of positions that @@ -2058,7 +2071,7 @@ document.write("Last Published: " + document.lastModified); Payload. If PayloadLength is not stored, then this Payload has the same length as the Payload at the previous position.

- +

Normalization Factors

@@ -2162,7 +2175,7 @@ document.write("Last Published: " + document.lastModified); 2.1 and above: Separate norm files are created (when adequate) for both compound and non compound segments.

- +

Term Vectors

Term Vector support is an optional on a field by @@ -2295,7 +2308,7 @@ document.write("Last Published: " + document.lastModified); - +

Deleted Documents

The .del file is optional, and only exists when a segment contains deletions. @@ -2367,7 +2380,7 @@ document.write("Last Published: " + document.lastModified);

- +

Limitations

There diff --git a/docs/fileformats.pdf b/docs/fileformats.pdf index e588906ba09..2dd3e7f0f62 100644 --- a/docs/fileformats.pdf +++ b/docs/fileformats.pdf @@ -5,10 +5,10 @@ /Producer (FOP 0.20.5) >> endobj 5 0 obj -<< /Length 1115 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1113 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gb!$G9lo#B&;KZO$6@53W]k9ICdOP`P=a5[dnAEt!C8gORi4Z:^TSn%I4u(M/f6Qu5V)`b?+hcW?/#04U4=qR5W\?WoeGhWYioMGj;W_>r>%*jBf#hS$N07??;IG:iWe2$GTd%P5A[5AGK.,clStMnIs*foQHm-?;6D7rjp(_fkuW9P8UVE3V0PI;7%6iam]H;hfIlOSITofT^+bJa!4,V)0b+f8okNaP[D!`crot;@qgDZ/Q,oMcirC.FFE03DgO%D/GNrrb][MfLhU*Qmad9XH*(>sh([0>P%hOHi!(FagE2O5c4Nk&\/+QG3O1@heA$Z`U8iek<0JVAPD"J$fgc$)54&K$fAj``m5pu3!*MF\&CN:;4(+,C4&R4h40sA])K64jDS(%7PP_nM'RnQXB(a.Jf#AaCNo`5O!^bp?PMcD>_SL2%%VC(D/5Z;4dMa/&4Fq'2FFK;?W]H=V`=&E;V$hEk.b``"f*F!Sj\Z$ZMP:Z/R@XRV2+Fc7;Mi!*s&(aTo[7c_T2kLC9M_n<2m5`3+_2P=FK)[4%"$)E_)4k=*U&',7q9]Vn1E!A[W3+jW+MX;6S\O\("a![__jRXr?R2[jbYh&pu/O@)%=J^<.9=q4!1DsBBJ_(UG0;BO)iDb/o@2rIBcrlgO.TJVUm-MSDOD'M@7U[W"H3mW--s=V01%3mi;8("kP*\;A>+,`@Z2W1[)(1EfBBVSAG2fpAX?S[-lm^3@![:km/G'3%+rtpV-Ci<&:!\#9c&LC-UHZ2Wbu+&I3?OoD"F5l[>0QhC@8Pr^AfT,D&a&8#b8<5&Q$<(\?p?;L0p4\W%r9]*TLajjFY2[5ZB5CdbfI1MG29"8,g"a8~> +Gb!$G9lo#B&;KZO$6@53W]k9ICdOP`P=a5[dnAEt!C8gORi4Y_IpYbOI4uP7>VL*sJQDKN]6[Q8S%SK06ig^-JUHXo\)4)`fne(e3=[b6f>EEC"UTpJnTI4b:+&Q\[CnNTGc/7;_)qPA_)lrGchW__JWg47o`BO[p&Um!+.u0W#O_5XQks>]'NNfml7k4h>AP)7<_:=9$tb55Sr>k,OS]7BE[U-Ab\Y@C53O7U[j+kjGtTb7cGJWt4]4q%1?L1!CQQ<5`TI,I2_)adekIJ>*t/^>pAl3uDLFdf5&^rP`F@@)9W(IcTW(NY#\]*sIM'Z\]8oJGjSbj1prR?4Z*aJdu7J43Z2RImnNO,g&5I3M5VH2':-I_Sk%/*h!,Ube%='Nl=)%igfBIK6kB('./d.ond,XEb"Gj0GB>!mi6:P'nJ.nk=omFh!NY##@\@,j[:b1"cq>'#cGHH=j_*[ELH%0iiFuF6Ypa8)d6R)6hg!:TBoHp'bhG-KhP`1"^1W>96'NidM=<:"39-:ZdRbNHV-aWUlu;WS;@ccG>Q&E%qrkRV5YNNK?0HTYmqU0t*ir#5_'Mql>(l\qQ((N0FFA,D72uTGCqlqqeq^]kh-tK]%BZrG5]kQueW@*6=,bdmL:Ahs+\@db%=c0>at7&VLcYb2'f+E?G+`RQ%F6g?W_$D)>,7:$@rrQZf%*]lD&$,O1he6&Y^a7t[t/~> endstream endobj 6 0 obj @@ -267,10 +267,10 @@ endobj >> endobj 52 0 obj -<< /Length 625 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 627 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gaua<9lJN8&;KZL'g?4?*Gic'3DrHp:kad_\,_:4lePae8"7a)jjt/-X4T&'\Y[Sc7X=BH`oEKQ13lf;6ni"J*YOEl#sih4aQ-9`;fDd>1K:gH>:P3lcjs*)S4.N8/HV@ANs2@$g)[c!h!UfkR!LT;RM`W\JuQ9CD,YW9UW02Ts$\0MuAkBsA."'dsSEetGGsr2.6o9j.KBVd^g9lf8,&h+Xu8LOpK$kY^f$L=Xu>fXBtp8];l_F5L^Ar?m,]CTW-pAOphW@B9'G3]U?i%\8Wu;%8d@\%a.h`2!:#c\,p*m6=8In&Y*dmDT!f +Gaua<9lJN8&;KZL'm%S?C2Cpt3DrHp:kad_\,_:4lePae8"7a)jjt/-95GED\Y[Sc7X=BH`oEKQ13lf;+;eN8%gdu#63'e'R=&ai$;Q\u%*B@"V%V/rr.+@u/>BIa&^UsV=A\:Vh8'ifG5q3Se>)9?C3\H`F.gdD%0G]@ZO9YD@Gkc81O#-.k..g@mSH^gqXLZ!g?@#JVua3B$Sbj5-6,lYbgqV<_-MOGp2XN$pVKT[k+LB1bu^Ta/eRg(Hke[TI-QKqC-((aKp7?.ld2=JIa_ZsVQBf];mDG-F`N&NDC3uW6c"b"oVhLM6gO&ZX1N@MHro?KJ^.VU[SWJkhjMteaA*iUIVW+CApDnb/ZVn$Pl\PUMlM6@+!]FVhZm,3qV*f=rP+@@C:pfEP`ehZ`Vc9HN!qc#'<`SdLDYF/=I#jY1@-,bL994*/O8Yk\uNVD?41;XtIn?$2+PUB1'DZ]UuFnSiMiNcMs%Ypf]ahWeDHn2-NmrTNs]D^#'S3Umm(=EpmZ,g.&knsoZT5E*@W+n-3E\_F_O6%Q+O]]%.""h$VZiMYNWT=_jb$G^i~> endstream endobj 53 0 obj @@ -439,10 +439,10 @@ endobj >> endobj 79 0 obj -<< /Length 1891 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1783 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gb!;dgMYb*&:O:Sk\V7];FTETQN1_4VqKL];])l0gVQRW9?Fa2fC)89bb7`g0)DeS'PL0t)P/s1AiSe*l/6;;pc>X]BGfF*;?-:pWW2hg0JX'A2#8:X>T8>K$1C_2S3JYJH"Yi;G&\8A/EB(tciK!#FK71"o\l(R>L(52!V;%Rh'HohD"Y(FPMLst?[0f[,B_A:"1`T2c3G/J3b(@l[W9$ric`Dt(.A+l(OPoT@HR;6aiXW=^O$U;eH9rpT@?.6IL1N;etI.i?6o#%EdSKR&BCmhW(.TPJTH1rQ8$AdG&nGLW?Na^`SD9F(`g*r-Z`%\h#JCI+Dr'4*f("c4!"\Xk0ZJ?%p>]<[XVhaJJA&KPcK=B.jl]sc#!oV5h.$ueZ05)Ji+YGUibf3.fG+Ya@jESY9fb5$s4j+R.cY9fP=dN!jA6A\$<\Qhilpsc$)c-m9NfE=.Jl'6F^3a0@A=R9='ApckU6;olV&K9s-a\'B@_+"Vir(AGZ$ae"'krCL@p0SZd^nf!O?4?],V3F?92;`!d"%imLpY%uVT/gh/U5K3[$!'R>b:+m_lkOf#&9fpKk0!df\92+[sn=^7W!3pWJ5@6s9$%CCmYeOtUH"YOpDcdANXBVKVg%^'*fMR-:L_kfDR%GRa0s8.Af>QTG?]'JJ,FN`(3^G"U&n/@&mBIi-aH+%/$#liLZci:1L.QM!iWVq0t&b$,u@Gq;Pg9D45*X0S0siapS\Bihf7]j(I7[StLhjZ/#?uoGZ=C#2]irM%9k7&[Zoi@s_8a78\AJj;ru+:Lc(IRp:>Q:bbM'RG\OiuuA_lJj"/>C-.T#s@T5%MJPFq:Z4'O7c'jRN=_(hF8edNRO!rYEr>*Run0bM,44i_ldoM9O(eKHL'<[b]&IBV@d,JeHl+N4Ll>:hd4RI0Udu=DQe##]"b/_PlMQJI45&,K4?oi?SJAO-K:*L%p,+(bI`\cA:"">]nh*4j?pkgp)Osko$`'2'A)K)p0I3$$ps>#^2Z*.HtgkDIP"mR@bK?7d3MS8?JM]l?^:D#_W+Afn[.u]rQ$];bJVqO*or.kq*kYQ%<7-WO1TPdJqfsV$j-`MD`;5WUYU@cDPg&C3RR%t\](]>KNm=#cjKa=73ZiEIXR3ckcPN>5&;>!\rgb:4Y$lKW:/tKh(M*4PM0o&$#N++kkXEl\lQp,n;YKk!_@+lK!/NM\m.,7)Ff]O*M:E8A(uPSD/V,_!@"]b:I#`BgM<[0AlKS1VK]GpCKVthc/'`bZ]^.mQD!R(If\rR^Us~> +Gb!;dD/\/e&H;*)Tl3SSQk^oEP4oT^:$XE9k%(m)023&*12RDa7$j).Z-4RH;`7PE;:q6a&4cE[0*'Z<2oQ4Z,tj;4]h@SCqgBfX.pW^'3I#,S%^%#'4RM^4CnseD&q90mJocd14$0/bFe@h1j`>/bJdPSY@L.8?,X`a5Sg*.m9,N(iI-_[!%75(VZ/[Q]b0,?.6IL1N;eeHtFTq=;5tl;oJT5XE%0;V+H%fmk"0(8Gs#b&<'/)%-oHbM+;EiF!1'\@!;8FR?8'UJY1<3fAq1kT06JC`"7_RH?q]RN6mO`.&T.a?=N_M^`]o$6*)*2QK)-Z4C@>\$/BX8f+`u$_Z-:=01":!qHUSb6ZBH*((B'mgF$"_?=VqOa;8'MI`cPTa4kf\i&BE$]'LuE28C-c8T$QDGTXV*L>_lkO<[d$`!c*i(AX@Ms9;*2Wr1pHY#bC?9LB5-OUeCeE*d4HFe.VJe"(i="neg2PK]K^u"$(*;"um\/#Xa$;"Kc-$3[]Ed=#1+"k"J9BS?BuB?HGj[*W/[F!Q$J6*@ZUVbc`l`sZ4qPMO56#N1Mj[O@P[]BKpWk^nRC!Q*Yli\qJ^i+]J.iUo18@8b8-/4rpGSYq/>;-_;hc)PH/OE$?KrqN1OrRT4^2YHD6OkEAq(Y[-gJ),8.9TH4G6] endstream endobj 80 0 obj @@ -454,10 +454,10 @@ endobj >> endobj 81 0 obj -<< /Length 2255 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 2177 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -GatU5D/\/e&H88.+ll:c+^l["ZE3^t?fnN/OD[^A'psc.[O>@L/>f.P-iN]_ALkEg8O_k^1_KCIhE?)Wg`?4ap?\^3Z`e"m\!I%YS>AF>^$0VgIn-cT^5BfGM;j@n1N.R4qq9^O5D[eoi:U1CfF(%Aj>K6o]P=)dqTKk?uSmmI``Kop/SeGdSrf+;`nlhX)tN[$-#.]sK_7XTZ(*g2%0poSD[jf8bGfl8uAq`W/;1$S+qV@bfKb0CBh\dBX`8E!MUBVdN!2*iEU;!OsYo^#O\R6gt.*<)aaI[ot"O'B0+0g_AX8=eje*Tn>Z(1;7Lg!!3L4270;)hF0:Qr[?`hR*9m8hm;kUX%(0ZFRt)c:r!i!3T+4f'Sf6!9%KXXOTm+Xp^p4(Kn@oX_'N9i"tNM0W45P=$T(et#tGjR;AG0s-NA+Fj:k&#6,al_cHdU1%HG*[WR6AZ4X]%jK$hRTF2,s;6$&Jb@`#-!YE&E*i.B;)G%X(4##nX3C@<_Y]YetUqSJSALn/QUZ']2YMVfb]U*+3.FH7L3LGR&VB"mfUWirFX*I@M>!0,e0'b`78cf^&=/rifub?>%TXJS2oL-!)mshA'G9k':"kT0a*c'?u)n"$B'Vb1.OQMkT3#Koh8]W#Cu[.plZO2f(=gitT+\WH_bd9`o$S,T-t(1LG2E;Y9jnJZepX3l7^(1cD'u<%KG8!WI0;PcH]5TZp((@Y&0\BY"kUN`"kkW=DUg)2WKU&Cs-@;)HhAoMN8"V+Os'Es0TVA4h*3l]3Y)L-1.CZd\1fM9ASk[cPct'Q/J01=<16LA+6@U]f#jigH55qa>aj'L.ke4<:FU/FWhBB!hN6=DaR2Fq5?+;):&-G_,tuct"<(b/#!%3FQi;-L"2;%KucD.W/cZPt^;8\W[I241GPCa2u6?4G^Gd#(u-8e)Pp;Y0M$#/7.b&An+8;(2HZRpMsu!=Gg)NG&d'+NjR7e>c9=`IA);TKbt'=8KNOFng(c0r7[^qDjZer-V:7nmp$a%QGke8[HDWX"VKoOiKP<=d\tU:R-^86L[W35-COUUFe]ZKPimY7k-ohZF4(iu:G>6;BI=ETs/7uu:5tH/U1=(RD,:uu?q07)!2=9`%DtM62(dXp$(,Htb8-Dt\0>B>i/8WAXL5PlSAG!QU1P6V@PDWV=P7R&LGu!pn5pbs>-9Uin]6hl\8",nZ-Ek3M?RDZ`eq,Bkf(^e8=mBa=3p:XdSJU$:YkC.9/p4`F-XH()-Uc^5U;X2g,MMF9L/c(TLV*+7kY,@q4pWRI3Ft>;!fG"5;pI<;sF]M8)Y:WP(08GZ(]8&6c%FNeE^$Z>s=9hUHN6tkm-MhdllZMP:g5ol6_)E.%CQa`TgjAKe'p\'&jtmJ?kh*nqY:$S$\l3K?Ek9bK[1@rn(gI$OAI8D/#epA5+<'=C?qMI50d59Y$2oZJWI\,@EQ/KU]"$0'mI39n4X9auiR'Er`Sd@cFE+;C$q%QA@/U@&X6fUH#ql\sr4/R^5d)Fo\Y2U4DKM%][?g!X&,5FD6lm/\XJrY>O.#[*;QOqQ+O:X-B+Xo(G"A?jCH[!i/"U@^8c8[%d^BDM!'!9iK+oqEH7&C#&lHu0)uO"s`P4&f"d6-EK2g)WWD#9QFhU3.H<\C^T)!5Mn>1b8VaFDl=o"bgq3B3e]\7WlQ^>YoWLT%f?FtbCgTW;%/K4K/b4B2"kN:#D>TcGfT7ol=.Hkcq@uA=+tXe8"oBIp#qbu%Kh5d2nn6rZTiVPLHBQWIC`seVL#-N66pOM!J?6JIK~> +GatU5;01_T&:WeD0_]TZ-h5m02UHfnOcZB`OZ(Q]B1dpk[L@&59e?Gj^V7"YdB5&5PKlm?,\0KYhsPs*P.jXoGE^Um/Yg#;J+p(ZY3BfDEVTR"S[GZgNhJK#KXU'G?X)F5rqssdDAsoERJ8?sT;D./7f+mg2Dn"6g65P6c\l5%X@RZj^,GSJ/cmBRf7mrPn9aM[p=%$X&J]^]Z`_(N*RUHbN9Oe?U\$;3A#9^-?bYDkB&gqZrR9S=`-\iLj3uiV;SD7#mE&-=QQ.TNI*@VSa=aFb]a'[h;/aG67O]A)$+o3aJJ4MRC#;?`#YFefqCNb4Rq+;P)c[_La`[ZeTd=V-o'i]S"t8'RZB&_0/-*d8r"4ME.SRR_'imJcZqSH_?qm"jmBfR>A:A6^j`H06T8lBM>G*hZ-6%KKb"uJNbRe!C;9A;%3Y*D4G=7&n@J/5Bp0.7)H0Z^c>4hS1MG]kn6m`)m@gIUSf:1$l$jT<3:/EcPUJt5L-&TZi]Fg^bL-Z&O;YQeUN9j\>=,KgA2PRAUs"naHFfF0&Z_AHp-7Fa'sZZOUM/X7WHZrd;a@4L+[6&LQ),KWKL0FYdC8CUOHh;ba;ZLcdc0Kd8-(U"VD")lIW]"HFd1W>aV&[GhghTuEFfrF7%B,K3pobL9B)L'0]p&<+&%#4>BWATqOG"uSc*MC0ep!0%crXq16Ib6duAVIT<'iBZGfiL.XA&fKhq[5#*/ab_'T@BeZbr3mmsh`mm'ZNTnD78=*3W$qG7DIH=\nA%!.!X6J_I3RUO/:n^2^oS"9Nc`A!<[6+_9A1b?-r^`U6a⋙H:JP(Vp+WU%,NJ84X;WrKRJFY-\PP35/%<\Vl!;YHY1S<,]ap:j%R:%he*WEI`m:^m9!VR@#Fq\:*b)_7WjZ[)GR+_@_9W<.#S6_UW,@q,!TI9$k$@KC?aMJ^f"ALDIBH'.jCD^^=^lh>4>Rm33K$u&iFN1&4=D@MOil&:j=e=%`eG@BXt'VLa,D&VQ0/9MWK:8;9T*.2c?I%5Yj%^d!SWcIl@"Xmf2.O$XPmMqH%XHFK6m&:Wk;b&8FoeB'5a@,Ws0rVmUI5i;MSdat^s+K9&nLW5_3k,Lf=I+p+%Nk=ShmVC-gR%tHbp^L;8>`Pj[=RY.%CSAarA5tQ!\eCnVVcKQO/)DdcO?E3,t?;+4E:h@mD[ZSAP"nbUM,ZFeX^-RM42(A&F#&.,7+nqp'4?Hso`(i$NBU,JsBouin4E`!\5uqCbUGIk'r*!1`Vh:=%`roc$k!Kk[17I[ZD:j2Z`GAR(f0jP`#43fJ_TGI`SLmZ?Pqo+u\`tYDrN``*8`9'267H40;LP`*Vb/%Q>IC@'e6[X'.]rPYBXW8VQ+`n9A\1E%1X,hafpak&YO`M()8>!N9_d7`r95-m*m<`Nj"D^TM]Zuf/Aiufs34kj%q+^/X_S`6^Ue%2g!d-355cBtRJW<$lP:]E65FgP5#BnQ^>[qE1R;Xgn/e_#4LfCkee~> endstream endobj 82 0 obj @@ -469,10 +469,10 @@ endobj >> endobj 83 0 obj -<< /Length 1688 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1887 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gatmj1M8m&/t3T,I7Op@EEm:dViL%.4'[[*NeXi^D/?X-L/_rAqQ>F[Nr9!*4SS#`j!&@[YPU[u&g'k4KYs-Qrr3A`$=cr:slgcU]akqHb%^l-4KtfS/!L1QHXdY%mI3)P7P?DKJm=':c;-EV++?WY3C7cTY5.O_;SmlV59%CBXGA>S5+iaK2;\'>t49KBYD?.$j*m->0>MCV=a7CkP-S*eB<18e!74pD<*L``n917I0`nuZZ+59Y^b@uLh>4(R-7>#.f]@t1b:Z/K\5[PBFJa`.ra4q[ufigi4ikN:4,=?X@bbqR]:NIWr)cY8mH*=L'P9F_[R5JQS"[,RY)(EW^agj8eD$,Cc<)5Hu!cRf_PX.lKAf.Zs@GTh:]Bs4Aa(ppK/nH)A`moYX:>$.YtG+;bC%!AB4:%iVg0T+.i..N[BMd0hK5i.Q=BEp'3"NS1k9BWi.Z)=UU7`!;gG#&W%`IO8=YP?Pun;Kk^7+Y2@i\4l?WT#`\W;5=VP>5Ak]9fF#:+bVY?2F"Lp1)ok*+Bs(5;I23k_Gn2_K\NbV["KkPq.6/:P-+#b@7d40d2@6ChTq>Q)WU:6KcpGj/dIjVV?R]l=B.e2U1mf8aZG+0Vi"G`d[:7JpXc9MZ>sHpdXFQ#C`sW[0;OA1OUp]9TG-:ho[f?O&9:DUHQG13:\I/i&@jjgIWeU)\n= +Gatm<=`<%a&:W67+T1:#d>#9onc4OK3U._Ui4?>C(9%,7,+&"t&ukA^m=4":.>fq2dS\^XE6MVU1%WXK(Z2>15HP].(>QHUhiE]JAZc[L!W;E0p3Gm-&X+tn9'r\r^Fn@`2Rk;q,M."j^HUiJ?g5Rmj-9;`r6*t]3eo5]5@21b=aK:d2cl]j4a=BW(2bLf$id`cDtQ7/F5a4/c$C5]7G?i8FS$Ws:dopK3k&%6#EQC\7>:,S/q^<9&%"(SsmS]6o"/nI6g@p!Ylo9rSfYq`IWUd;uIR5Y,O+U5.VLdaj/QK_<1of/5%`V)MH&'O-tZe,Jq1n+r+:nN8[WjI]e`Wr=nre-mi!YAQV'HDnq69>rtHu*ipV*USoV9TM#>aK&`D>+*F=bB`/W>\XSLYi)o(N=OOf^+j?EQ9H8+JSbFhH8bTtGq?P=<,"s*(St.'+D0T*H)\9lSnq#WUtL>Fe_Ma/`ertU3ChKDH.;m#=D!9"U<'Ts76W$k$p:llSlNO9Oh.7R$r_6((K"RsFU#m0>jOi1-)dP$*"IRbfdbPe"e([Ha=ZJff10mK(.\V%Zq.a@'so77B\M3A3(`c]G8&U,Fu3!9JE!9pO(<.]CLh^b*:1SB.a7Wt`fXFqc4kB0[Unk%mT$&!X,;r]U5&pVQX[\,2FD@&"YLIcVfL]a&e?-:^2.l4KTY.iu%:>^bPIG'Nt>H>%V;>no#]KZ!@cMSRO-R[nPMe#8e`Jdpb":Cle$?J+[Os8LoGF4dD0)(rOc+IBL0eoVuiJ=4IBes1X'AP.k#.'lFFOV:PmrY"I>IN(q9m;W@J6*>(pAH6*!p"h-I<3K48;[=Y>3",^U%`E67ai#?@g3ZE2K![U".f2R@6AV^E\Z?1r:dAbIHfe@bfT\jOPsH[E.JI"*EW[mh^\4@D=\U6-D'4n5UuG:,OpB(Q9:pM/18%17FuUeM!:^?]6aZR#tS5>Kt`K.?6tU%%iu@2s$D8Lf5pgQc^tkPU1F,i3>D/s*?c?)09ToR5JWb][W+$iZN+3c@q]cLaB#b5++`E9)`U?8IJ`2r;oHdFaH%`tolsRSlBj&f%8]:=3NiY;%W[\2L/,T(t8gTHl`k'1aq+kE4O$;?#g,ei5dD(1=3sTTe<*5HSpQ97aoHT?/eR"Uu&VDKNG%m?r'6Oi&?^H1%qgnK<=R;r_gG=NkV(etDnlnpnZD$'@cX9'V!KaFE[^>Xi4X.-aV<.3gV%1#VM9)-<_du0Ck/[aJn>Y%Q&h/Y'`":V>K@DjFT&.LnUc3LEHNG?Z#]q.QfQ&ED*(U;Q6SA.1md-N>L*)d+r!$0gr endstream endobj 84 0 obj @@ -484,10 +484,10 @@ endobj >> endobj 85 0 obj -<< /Length 1332 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1375 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gat%#?#SIU'Rf_Zd+b!tAjf5?a3YQggNc=EA0'\&A_dG?e$`(UoH^G.qWgXMP'JL=:5lsh01?pghL"Z6\QI+*>L!/:@;dNC^X"YWq-G&]npa&&m5YIT'R;S-f!E,'MlVWj/r>>cq.6t)q.c3Ks01,",QWf$aBK[tJ)2^aM()7flJ0hWmacJD^qptNjrR'bO>6;*&g+V*o&C<\OK;30L8:aH\9^k3112qI]CE.\qX64VafIrYU>MS%S?k@2c#XC0I#%!(1fn_De7Erc7;2=b$Y3SK$es1,nM?$DB='H@gXOj=F.hiU`1d3KS1Rq=n9USr($WV6Hq7(Pe0l$o^"4\6sjgO!r#a?q+Stt24o+lLk8])7bR4'pcb"ZId)\1RkTj;s-=7S,X$U\#gSl=+=a$OO@>Lk.c0kgjU\@mRCfEXagcrq-/>LM4H'V+GD);#24Do$HD,6DR;jYh5GM/DB$X*.t@Uu1gKdFNjMR95(PN&F*2^=?,oq;Kh5gQ`<7OleZ>A5\C#%3Q`?N.Rb18@i^J?Lu%5";CU:d(WNq_ha9>GTVlT.o@!u;>e6$o;l#n@2MDD9]:B>)X@3HhAR@"eG-a@JA0M"'UVL&XZ1L5r(tu_*,B]^o:5t18Fbo2O\T3l')S+dJ$_4g\%oJfkdESWm!0&W9]u)[CO6Ql`DhQ>JREmjF;Qu1F@cG5b%CtP,sCFd$iLuY1ShaqQgnJhFR`,YLD`$cj8'tp=&qoD:5[D@br4Ge$Hj3='O3aM?Ho&,fHg^AJX[):$o2@:1>iFF98;24i2-"`3S*$iLBn,f#PIC[lB$$58Pk_kJHu*7GKgT_I-E]d/Ghe5GOYGo`Ggl>&*)+P=07P^"=9jn(&KC%V`4-hGS=SfcWPqLUj)VJh3j10%!IU.melQO)C,mqmqPN%b$sA,Ok`.gYho!3F'\/+MJ,S]j>%i[>Cl7`g?MJ*c2aP:p&j30a8Y&Fodls9Ct(l^JSA\p%CL?]D#$i*^SrCo,54;[Qea@~> +Gat%#>Ar7S'Roe[i7j]oAk?8ZC$5cFm8B_sMJdG2ddBD`.@Z\UH5+mhrUlp<2@jNEFV#hG+aL6gc:;ocRf0L[rAUKbUOV1G14fg9//J)#'R/I+3(Pc\6R3OqR/JmHNX>oe=\;$1rJrAorl69W&X"(i4o+h61qJJfj+9a\LKCqkU7n/^Pr$iY7e@3[K5)+[N!*SNQ-ic%5^c?H?E@P4&\)C6X2CO'd2eJ9\eD4A2!_b=+tV%Ff[g&`(-=n73-EEVb708DM5Qs)Llfn.;[p:=ULbfG22S%B\Z6sD/Qbj0`QK:DQd:1np(I-Dr!*)]L].SQ=LRR0;;h-3<%t'aLVA;&<%jWFc0U<:a'?M$ud@bpLZbli82rf-#ePU`jK7rWf,JHT^[/=<.4[MeFW;I0oeHc`9mnScY+^JU@9B\tni@WA6H7T0D#=09,g9$-di2*1.s?p6Usebh0lnEQ>9aYtD)h/J=-im[I>Y,3K3f)t#od<_0&J'Z@]3VVOM[uWuRR].n*Hpa&Dr-]["%[Y,dl5r9M6Go+)i@3NlW;Hk[#k>h"j)g#elUG:@KQIE'=9R0eqHFG1Kmh]ek.H^!B3gHa7<$`K.?/7PSHOkW`UBC3*^G.0UmbQ&X+9/[^g6AV%7.UKJ^0)\3^TkT]8q>g=C=cr$-5a\*P)[:FmlZpJ7%D6C7n2ijNN3pb/9g!q4Q8cGXF&J42fQAF1XF^lQ@pTkNd@4mABYa3<$rO6n*_JA`5@&0;/'c*Y?;EWf*W;Klj0ZNoT0]4?Y"/bs52/UB)\i8(((B%$?iOlT.?;Vh,=!]-!HK<=&*)EGL5,f*Wh#S8\>)OamYl'OSmgl=4(#)(dnb'Kn-Dqs26-/IN=5#;OqipM(q*hT@[)fleR1`8[[Qu?eRP_+iC)*2(ga+GXp_8D_]qIRSonq6=u?"/GSUE<~> endstream endobj 86 0 obj @@ -499,10 +499,10 @@ endobj >> endobj 87 0 obj -<< /Length 1440 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1291 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gatm;D/\/e&H;*)Tl3SSKFoY201-=ZDW\f'2Pc8_%pLqW[V8sE/@cIc35^2m:Qf9126%P"+E6[/na;uqj_6grrAYaE),Y;]T+@^8-b^CO+oLMtrd?3s8sflH-,'-2O_q^nd-_q&uAc(A]T.o%?XZS>TjQ].ZoN>5Kb8J>;"=p9]etZrmRA8e-?a7KF\'`jJ"2krsMM5\YDHa:9u,pelZdOWb4[P;k:M)ZB29kXEp)0[tX[Ckm[j2h'lK,=]lkY4)NBN8EVuCr52[GH9hc?GEfl+HdCug$Y_m(c?m5jDP[*><.K.-=SiHC^c$YVSW4f;SadH"+E;&Q;k!0;NQ]L\6elHgAagf5F5/%I-i@qlPWN#!/Kl*eEMfc[:a%cgj4%s4CS'?Lq-Ouq]CE4nf70>4\aAn%1]f`'[PBH,;+i\lHkIg3+*dH;VmQF=IM."!>'n-tJ,S<5K13"pKVuZ'M/NitX%DC&\Z[ARLUe-%Y+VkM*"bXK^mS0#)K)iL?)UJa):6rN'>>T@7idC.!gEf9c,.707];t3L)I-dD*_8J#d1I4Z>L+SBO&"q3hW*;goIue^-=Z"25(-),#2OK\(E)h*WNT!D$e_TPQ0Q6TC66k?o=NU^[WDnpmNqi@@ZQ3MeOE$nK%b]1mTpE3RO#!nb->UQf4,).:"=N@n_um.6)o)cI;brLnW4"`?^S*%9DEUj1nnLVU'psY.M8R_gKrTY67/@Y0?k`bBH5b6'XugUi-mGR8Ap*oHqHMZNXk]/!M3;3WV^?=mY#E9N;rs_!Wrpf>Y2,aY7O8o$&>2*2=4uP5(2LV"lLuf:@pJ[2nB68B@#cT_o8W)Ef:ttFMbVJ%E4nQQgpX192)99Efci93Ge3W^RsUB$V6Zunr7N^/hDL!4;h\1/S$9TFWXcVtUaVJT^5J&N4rZ(s`EWS#ap,kg`cn9+_NqjR51p0rCVgDl5'G';?#"iGmM?M>kEPH:^B;tSG3@Jn1=WJk0KE*AeHX)@Y".h;DGR_dpnbrQnB]"LeMk?:3V:/,aQ@OHq\R)\HCHThbq,bB$$-[OXb`'!!(CU?[@)(%3R4rV~> +Gatm;gMZ%0&:O:Sn1arQL)3^R@V_Xne-d(,Rl9[uZu,o`VJT`Va\1$l/Gel_A]l'nFLIYjK#F5mkO_;-m/=4V8F6"'\qFR]uLD@m^a:_1X/6$aL4Lkt4a!Uqd?2XA5+lNGkhn&7jBA\@S7.bV2&=bg:bS_'@L=5,DJ`OOaMdL`9@:BWnX@K@SM]1I'/')foGI@P%Gu:tqGHb)MJ?kNPcGNlncp<_nhA#8TV>=ic+94_jAj>9eXEEo,HS*;t#-']+U6Xq8Csn1N0^+&L[b6ZAKjKHp@3;//_Os%pAF/n9UL@Hh3S?b<>">eE:B2Saf>SRVse@*L>Bl?o?1r_XMgoQGDU[YAW3@##GK'kYCTIR?>@0n?8A>u@V%9[DDgDhckO)Ru2V/3_DI6!uK\-[^H5B*Cm\GY5Sfh9,`:]A77MJ?//OlgdnGUQ3@4H_[bYW-N3d&.(h@[=p+_$4_)mKmeiI4pk&+;b#_^TD^F#/4fZPYXcE;Ea(enDA[C[_Nnm`[KsiZQJcd)f[GOAD-U`[#DmYKbK(&nSfN/';b>jTu1"ELF"Cr2L`%8l/?e?A86/DNnB@:Y2mUlOAO)Xc+4iZ>T_-mI!Kb:;KbAg)t&C&Rh''![X]e;jQhFrbe2Yp>$oaE0eENJ[`juCZnp'$p@BfrQ\9@qcl):S1uV-D/GM>8KYOOH%Qd==0K2A:&D;O8XQ31N(VOZ_"oBbqtj4pc:uEp6^hH&P"QN0hkgI*XRN@&H=BR\[h7E]r)("RArGSQ+hEP\T endstream endobj 88 0 obj @@ -514,10 +514,10 @@ endobj >> endobj 89 0 obj -<< /Length 1623 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1768 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gb"/'?#SIU'Rf_Z&H'rZL_Y>2#)31p3tE47+[`rW/:4$9asJHt%S=Y5If1\JZ7]_2ES;SOL7q.H9"JAbLPM5o0H^6I+$GTgCT7;^*).I.Z(`XErs*!2HP61[m9(E5Mp\`'&$Q!1Fr"8aVr#YhOjdDmlCVk&:/[R9SfKhB2(u)hYN-$t>8+41F1(0*RBT;*:F,(62qk?(]'>#]-"Jr=02:YoEanN6rE&+flgjl]AJQ[R%$8$$mquSeOK>ORfClm_OoPu&IQ19G%W^?J]n2Wo`2CBq!j,g1#E*>XkcQnHN?upc=K)E$L4KB<)1Niu>@4m>H_f%oOCbSbpnL[VT(3T4pQl&"7.ehmquf)lG*J^cf$[XDqG590#CB).m6p!PI#\`7jro0:F#+A"Usq_,50bjcg54-BF<-X"21a.%;l.f[#7#@RU#l`98EHT_S7ENt]=*L'4lV.$:M3NC%Y779\3[-/6N9LB\=F5;E%C"Ggs^Z)XI(Pg$!7HV&ol:e[67Hq12;&Ls/Yhi\^"i?kuLa\I!,eUW#3)QEEcjg+-7.j7BW1(J6<4(uQG30iA7'5Y7*#5uDm3W(Y?MA_io(aF@p&.`jk%>TSRPQqGn.s4gElfO"'@=kF[3^+^rkK3FN;3t39uV$*J[b^g.\T;Vg!G":,qS\A6"R!&FK%n6Ub"2N=Gh4F3RJ6K$8LaO`[[2B$$Jg2CDanF)&?aSLCU6O+NlL^1lM^\OQ_?ab7^-MJuJ':Po'k<6H"5(ERt>MOB=+cmmZk'lE#[NoSWOVUY?8FS&GDK9BoP-*4jpYtiTG8&k6H5_Qo96&#G_YQkeDm.)bA'R^+P5(.0"'Y!+>TZr3et:P1l?;1+5mX[E_pFuETp5b\I!>&F,?sUr=frZZj.qfX-"5WNF.B0J*Oa]#!'o.p5I4U4&-l!CC)pq]pLFB4U?DeUC]VOX-rIjLh')MOXh!KI~> +Gb"/(997gc&AJ$C#ei,'X;/E#?OAV]?#6bp[TGJTD@a$@fgI.792]Q;^V5qKR7!D7HBB>*U'MC!'Sl;<2]mg=%u\i]Mp_.;G?4;'0;FruH;9VKo%tj5Io#'3jou;_c>rRSkeimV^RrQ,+36*nEo_@@T)@V1^6c:&ESr0Mj=G'/cZbFE=lS7s1C:XphG#=]WkkH:'9*k&Z9K_+ZrgKIW+8\HbTJ_n!h&nYVd3lh479k]LYFJ%6f@-kak'Y1JR_esItU&m%V`i"o.V5_U+0pZhJSZ.d``5VchW9?IU[i'&H!>c;(bFuZBMQ>P24c`1_uegT*W:EfE0TK.S'_X)Od\nUUna_CsqS.T/Z(5k$s:g`pUN>-%R>dKP!JE5_X$Xt)S?qY8o[qL%)/uGIVAP$#6IF8qc0qj:-/t2uO*a!C=jG8ZH/VaMJ,P2qPVqDY69VeYSLmc/6#o5M$e3koHB=U-=(d1?(Zb$6/>YV-gmfh0plehg%Pc+\mf*tEHV7t>fK1H%79i)BZ4$<=h$K6hb3GdQW7F`mp?j_T0.mc9DL,uu%K.Ou"4I^s]bW$0P%"&sH.7%GOaNqLQ_aa(!+5^8?FOZ^Fi;&7u1c0+720PO](US!C2J69ZHAZG!/Sk>DJ9uiNLG?%HNfeZS'Xhf"@6.!JZJ7*bCA3>*^-Kak$N]p#aPpGe;fm&mIBQ3:S2S;PMZq@p)A0fZHWho]P_dOu\N!iX-]Pstq!j'NBW8Gj$/IYSrj2c8cY!L0&+>>-_#S$@-=[,?9pIH#f]%e+.<&G4nq#k0DMc:I+e,`D':9trB4*@t/mOL//Z*df+PgD2nP(R4)uV&(>)iMIN.R.Xn=*`(kXF0ToPCdMj7%/#7X-lp<-,'[^E^T;Nq-8HI)C-=CTjKqS5'g\r/YkG9Hlo@s!aS/C3n/M_M'h8%ida#+bc?;Da.+S[?]8qJ#gcCi438bId`tjFEcZ-%P+KC')3;,X/UX'M\Qhtk0?9VKo4f5/YBc26XZGsh9nA/cgN0\qL!QQpJ8JiIcmkd';Vlp9og4uC3b38r*W'W-AkqA(CG7^;E-/Q%\+PM#$52[9p,`QChoU^X$8:jjEl7bi?n6l0nF^?_.*11LWtd8[_4.]"V@t)YbHE?F3YY9R%b.EqIM30)5"n9"DKGWn99FC?4krP'L$%FapK^iQlu`W9V>pUC'oTlh,Oon.GQWRJd\MW$JLoGLP`XueinV2GG%B`#RC;iU+ec`A$eFC5d9:9dG&H4S*L@bt=;(EE*aAi=N\X<].uF]n)hn56/.d@0`kohQq%WoaPY5&c]tM%ZlT!?5Cd/*TG"]VpOu,3C=CJT7n8tE_=AQW@gOZYRPE5)mEql?SrBYWD9UkBA^pX&"gU.[gP$'8Z%3=dfE/r-o(-bDE2AJ*irf@><_23gI-Zu1?b+AI'lNac6N^%Bdr"*pP!md`8!;h5CWJNMoL%mJ\5N5[;FA9p7UTX.E[dZBW^,L24nZhEK\O#$Er6)h;mi-,87;b.LRf&8LFCfn,4SHJ+[H"f30TBQ:rs!K0cB?gO,O)plf]sdg'(qcV$ endstream endobj 90 0 obj @@ -529,10 +529,10 @@ endobj >> endobj 91 0 obj -<< /Length 1817 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1637 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -GatU4D/\/e&H;*)Tl3RXLCki[P(oVuS'FHWD!4o0q_2pg/^lU4=`jY:hTnYUc:2/1fNZPVO/(2p^,MHRjPPclk8P$H1a/Po12eoDFnG[m"WR[o!QT,V`5@.R1L#k:p6I@]Y_W0F>pTN?plRMiH@K8g7)`poMJMZ-BSg#OQ%#kp9J:Q8HfCd!^hZtg^B87PeE%(LpAXVp57O$8(e$U:ci*][6q5Oc#\,t(JFJGWj1D`QV?Bi/`6)\-5I5lM[I(OWn[0k'ZWS'0(^4;<$9D@?Ep6Hf1NPJ>b(O!:-FL.omNE&Lu%Xaar[T5SF\pQ\\33BY4OHsUn_N=nJm_NC[N/2bI!ATQ0]?J;FEUEiS3cr9rhN7EF;VhsT%CX;tM[uXisLC(sKcug3$7bteL`Z>W%6JR4[Y5&oC*-<$-'EW,H9D52j7E1Tu1eshB7qmcu'_d!mM7`a+CnisY0u8W[F?ZckOqC,WH/Zm\3No)c6A$p[eneo!!+*h]!2KNYqcm$LnWJf>%;>9X1B;"]6RT5,PXmJ!lCcq!=7eT;&eaj+?H?Onm^PaiKfsWkg8Ue"H)l0l!H)f3kf'+X(t:->el3jn5\g_PWFP[s.>@lN5mSZfO%/RE?K)7aABU]jXL.p+V*irHdZVfZQIg4pqYFD8h(Z\!;1!k"ORJ4+m?&.LH8EoJqZ`SjV:k-;G_7Z>^,p:P[Vr64-bf;Wj*__A88FmSgUY]Is#n+8;#4K_iq^!&)%2lee_YI[k-g%+1B'[KE^?%_mbWs=Qu9YQD::1>J*pn2[VQP(&mSkPRn\(<7oqe+>>ZAE\XbKs@/^&?mb:#gdJddtkp#oTAeGKjR",lr)D)Y(-;cC`dGHK?@PRJpr+pD)KmgSVH,aXXQ`F#7_e(u0_n.C:[f_=Q/eVkg4cNDu\oIF\:Xe^]0%.#QJ]k>P;47N7LOn3[Z^J&Kn"Zs)@=XgH3qB-pT)`N0=[*Qq<;I/[T(q@[BVcHlg-Up59kc"8@s/ABooD]ocPZm4**-gg4a,MU!XL3MkLm,qjL5q"%PoDPR8N$c(,`C0eJZs.WBj[fqSA%I"D&A_@%7?^LPMq>e%eUQ"G[*0`WZ\=@QCOJ+RKHJgWN@0:$p"3fV0XkqYeUnbgNLdf@eJmm,B?W:!)eCCQ+3SmhN&3,,^,5d4Pu^-C#G_a)a(JG.^iB5&?&>I;$nY\'/:%bVI'Yl=`)#BG(~> +GatU4gMZ%0&:O:SkcG0V0V[t:Jh2)/HBCX"UlDu>FUQP/jUft]d3VQe^8,;-6\Vnr(-uc#,%o10cWmD%bLo\7g%,VV)ufk[(>Hp=Jfs!DShGP"po]T=hmI36X_5Do^7=!:KKaFlo#l`*@>@'MAi%'VAb/-&@65*+I0TqaB.XMWO0cGRV6q[c^mr0CnJhi`Zh@^FS'S41G3rQJ\RV>`:d6(gfr%(Qol&jZ7]jfLF.7lg\o%rU6KY-q8QWfmXI@>ChO%N9\)_ij$/D@U*J>h=4OOTlB`aZ8.6:##qEU'3R81Q]*9$ttT)o>dWTaek\l5Piqoq9_*H>P6SCYtPi^7@15GcTX+hAmu,jE]YUA%Vr^W[DD"En+l&E_c-D7?$j!Ga?(Vo?t<98B+$P_[Eqm/(46Y/G3#GF`r+R@13V;+%n=\;310ja^"f1&UWfrcX8\Z`r\Do[uOeJl^0n@,UPclfr/KVG_;`ZDFNLAGp&>1c8lLHL/.[KR6MYqobrOUJh4W9JBDq[=gs7H.;#E]:4"k_4f3gbCbMW`kJLo'sF4*=jZ^A?)p]X>K$rS2HCqnA*L&kmH8^JB,;*^o6/65*cmj]]=J[$[]j&rQ\fWlQJCESF)mEVT5aiFY"\%L6"f!e*s$$T0k_U(Iu)6>eB8A%kGHm$\R(8bF1u_[m[b[R'$cZ"=M2;4<42Gn&N&lM6Hb'@/V`a+`$$?5-as!YIuHh[6U9RZKnaPKG;RtGkZ6T[)INsc6e^'PX"^'>YmnB`XX9K%a*#Y+dU=oaBMu5FR\+a\H"G0f2^]bbf.U3n(c>4d6kkU7d[HhhX)A_FuXC`EN[jc?C:i[pH(OIESsFfBlTREC+dGW7*c#cnpRkV=IH0MV+Q6D#)Pdn+020Y"W$f@S$RdUJ;*j#:PjZ0f:)29OL",C5O\qHC7e:f[Ch\7Ip@q(W8IF2)QR!;7\GhEQe;>0[@?UoNA]s=nhi@/U#(?NuP@Lkod*q]WgW;<_'m)J`qrclD*6I1F*VgKT*JT!ui/3*NJ^8\PZ2=I78X<'k0Gr\'S,KI4p3SIQ[@0!slNutF(h endstream endobj 92 0 obj @@ -544,10 +544,10 @@ endobj >> endobj 93 0 obj -<< /Length 1950 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 2091 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gb!#]>Ar7S'Roe[d+]0a@79BE/1oG2M[\j3/]M,Z%:Ca0(++s>,j4ae7nT(h.$8]$G$>Qc0kp1uINQmq-Z9d,pP[_&)7J,UhhP?F,MVd;?i9A=o(0C^6KEHQ'P3!EQ@=$enMiVi6Om0G[aiIX*VX@(3o$RZ"k=o'Tk)r]I^_Nn1(P=Slak(AB7Vn8V6%R?kElJ"[N;@L6L)3p?c$uYX7R[1@QT0>&u5]qiDH\S,kcV)"0d<`:P1MXL.2])T0#R?f``#Ds-%YH*TUt\5HN"j&C%PL[d3h?@21mCZ")ZtU-_+RXCW.*Y<-E(Zn.?h\bE+)R[XE\(#1cIo5m(k=`L`?g4icoH@l)tM]')`e49Tk]B!4:V+R,)ukO3Ug/k8J.NDAPmE>44mIHD*]ki6,?T^$Fn$=%1JM9rn"4;$!N8rr)cO6a@e.!9$6CU#^3NZ(SDOX.BQh'nYoLh0#RF*m9]WH\@B)Y8DU.p?@U#6kr>@H:pN-T,7b5u)BLRKr%>@r-]?BKHJ-l+/SqZ,97B5VT)VssiDFhl1n\D&1P+s?dYQ4W5\!;\m4VDisI/KAobEJ"ZEEK)49!1]WI7-[_cHG<@`n/BdRpIoRQoUM2m>i\]*&:T*_E?+tL-_)n.5j\l)]Lp.)a#U;^Qp8!Wi5AP#`p`k>lFK!XQrV]hijco8^(XsBX_H7h)l7c+j7$U/>;54Im?^GKrHB[@P"m+J*+l9JTGG=O*@Y,nA#C4[hG!SRh"_F`(Z3)S8`V;`H,RVA06o.*iXsd[s)b9I#>%?G'Vi>K_0Q/ma(,p\f_p_j^i?`7Y1fMg[LbU;,`1pPN<(0r`Ct3#S4H:A&k86*o9F[Tle')35,9?8j_oMV\AdcmgN%'ocq:c.TD6=QB@L9[/3d-Xf1ug7OJL)7qZFd9D:BDUAR'c2ba96]QpZ*fl3L&X4,)X(Lgj`jD=jnU)L^NO3WHOiBJhii"[kN(:kD8u:.8N"9GQIkUS%kb>iG-R1H@M$;aErRAA5]#nOVW6BA'1/.1X95DL.jWDcO9QhOrAac!E,+`o"UiT214`f\Y0ShcEf;^SDM0m1nDOjs!+VM:+phLlcSfRWu'adS]HV32dii,W'S&TAuYVC:[iF,,jgE/o^GgHp#Fj9]I-hYBkce$IdL#p?S?5Wh!g.J"Y1qG1kAsK=4=U_%n5:Vci +Gb!#]?#SIU'Rf_Zd+]0a;+,i55R+;3/lJKt[i4#=]E,B-gVdXMV;sVhY9,&nk+tK":=Br3O]\EubBj`f4hR"c:IjlNUX(/q/]HWE;<$5g'o&FWmg&OYFl<(=*-`5HepR,Ukk40@NYhb6^DR7lElB]\`Q2a_3T>[mG%BObU#TA+[Ir_%Pjn@5PL#TH[G0?F+@X6u.:ibH6oGn.#Uc&,Qp8].>,7\HOqp^DQa(YE(,f`"Z!GZ^[kFkuXQbM3CEBe[cF(,4I7s,<"'_Q6ma;/_2eUZ5[]1'&4Z.ajA;7>s_9%uWl'!^rmL"6Q_7ZN,2TP_fG'4qa\6?T&U0'+N5/n,'"8H4,,1iLoc/#qYOPql2?V^.2HF'mc4t`2`WVgG$"n*/fYAZek%Gt]+n%+7^Fr-EeRng))),HnPRlu)7+i,@&Tgc>#dB^"HeqEj3s,qSSuQW3E/U[>"4h@Co=4Dfe+fihM&OEjX[IB?77Z8"^l=ZE:%@p,*8uP-9qGr2_^Oq+oOmX3iO!<,$#eE&57'T[e4/4']BQ^LNAuTrJ,(8#I+Aomg]o>_,!)/R;'"NBSkn4h!L=;<[ao!p]hZ&PiOt>;00ItY>F4Vi-Wb`Yi'?R]6stAO(4To6b+"dD[d#Lbhki7g^h_DOqdpLOnp*MNrk`]?]/@acMf8LQFAPEuS+Bgl0%Jn:u/8**`ZIDX[4:.A%BX;=FDg?^3<'D?5cXm>j34j4hiR-*H8SGMD!Lo9$i[%\'VLR#/[U][:G?[W`\7[-1$,fd<(!/$[pWY4F'Sf^ZIU4V[#]/jGn@F!JP^W"`HX_6.j^.ol+$-9sP:+lg,L(j4r"gCG4f;NW$^jm@.at3s[!`7L*0HcV(g9CYl=H,.WP.]6&MEnS3ZD,4C0Im>7i"$#[*eFE4\i;/39*S%G6f4#9a\W'H]I*SO7`.IsMNWG!"=&0j<%5lX77BmskF=);U%3FP(TJ4Q9VdGidPW3*j<)U=iIOopJ.E!N;@'&fOSTT5<-Um:SdVF=qT-qjKq[@q)%%F`;nbsS9Va"b&#%p5_WAB?"c`QAJ8nDlqe\q4?r'%?8lK_qM*"C2W&U1ap\IdEXnoDm_pEddiPAY[[b.CY.&[F2fQYu1!`h6Li+2(6,RKH/IYjhc901IZ^qfF!q_iH\dl[UStTA6q2lH!Mi[X1hPT+ngrPh>[)a-FZ\t>*BAIHOdkp".3P0)l-@n,lY/UqB1`%&7&2UJ/Tb*lWmd4?rId&Ce(-RSohKSDB-0/ZruPfTGDk*Q9VSF'ob*YI,M^O%,/=!otdg-O(45-0DA0K9:?Fm0'H\m`DgThpS4p[iRn'T5$fX52C+..k;@H8c;M4Z`$ctf@+MX2Sq>mo/PLTYEhF]W,7;:dDug;f=3:F^9$E;\J*XnY_VZ!DG]A"FL[IakJXjiB^bKp0@e20W&l+k:a/UsG[ZS`^eVb^ML>LI[C4qrbnI"ugkZR"!h*Rj+O_G=1bmAW,(+ZfV7*eT4tYq@o9^I\6]gf$u8kHD)?=#TM8$UF-$eug4.P5ZI/~> endstream endobj 94 0 obj @@ -559,10 +559,10 @@ endobj >> endobj 95 0 obj -<< /Length 1757 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1690 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gatm<>Ar7S'Roe[cs)[LAjfoUW@.s%leH(8MQS\FLUn#'7+h]RdA.l4Q1`C*s"tGM@cWLO]d"gXLtOtXFEF'C-FT&cb?Rtcpr!P@17f;7+egiiN5_lg%pQ$ki+5=e.cK'FATW!IFLM!RK^9@YkB>7c'TBcK:mT.RP(Mf-@5$SZFeH/.qOP8[CoK)io(%@c:lSb3O2BbV*kQ.5V;hA/k^3n[[Y*ugbok\A!W9pbiUa<#SI*r%'kEe"A5b)h$J1fY:qTN/A8ZWP@;.2sQH*BO9?RQ4o?dl(_&eE7@qRJ<6Ya+eY(TVVaQ%FO.`'.!Ri+K^`:31ld!Nd5iq]W5QOq=f.eq(V)Uk6r8-.gY$-^8Z9Zfcn;as,G^"!SdqT[dZ"JY_lae)gr$22\D8&P@BB*(3ER:t[/cOcf5l\f^s%$d+EPY7T<=jb_'Z6V5)GJPlp_Emr+-nTf!Xh[,W5l/:+XCh`)m@aY`(`<>GLXN!KXd:IoJG\W:Fj^_I^.*u#>,p49j0FBeWqI.E08o4>Ou1#E4n=k=2V3329+Ai#pNL/dG5gide_XZ@>arOj@;ST5dpT^:1Si;i^7m^g`-bDF-JS5A=4?.KcVHT`92&AFq0_Kf*G7Fp7IQp<6X5X$i$qeXD6W,s2p/`jD#C5UEN#p\%gK7=E`E4,.#hI+Sk$W`Ve`%r;HM%re66(8W4ReZ@>'cl1CQJi_:)8)A1hHi/(JhV4r6M5f:g-&S2@TKmD0eRj[I3&-*UB@uEgomJ+:HTM3tBADhg7"n'RlouaO+=ilTr`.t>d*Lq]"f.@)DdYqtqYfj4>"tm40(rX6nXbOt'ZkSV9AZpT$CK5.h#AA%O=LZEQEAkCYF$Z1"H$l!'i/&a0Mj4$Pn9[bE`9$,S%g*$5\Aa0q>PC>GT6/-("@qMqK86T8&T4"FY!#s#E%h6Ak2[#=)7!j$*%`B?p\6h;CY5CBF~> +Gatm<>Ar7S'Roe[d+a`"AjfeOV&3Aec!7%mL28U<;YPa..%>`<3g:=+q=UL.b@V"_?mhjt'#PNNV.an@])ri:3icENl?9k1RgmWEf+OSW8kX-uaTI^2^kobh)40R<\*V(MG=kL/l67E0crDcC!C;Mism%J.g;tGdZ\QYYIVbDcfl%kgK:cbeG3u4iPM2e\77,Br:.(BXM2P%Qflra'nMm>`!UTCB>!7rQh2j.O.aU,4AoSG_;!8M8bHFAs,Cq@7E9WE3>XON"J2j'#so=HfQ;49-Je2Nf.,bN-QdVGi?.IfoV]"TIC7YBHSF))gS&?09M>R?/28)pJ5SH(3p[f^a"(ZV7p28AO)FO;$P\*!jL(&oY1$Bn2];0\Nj>5V`QB:.k0,[,3[*[(*"IFF\j772RG-*LNUPK91+!%?8o9-;7"AIt#kPu-]]U:3[=67XYNlE/pjD8HSQB`Qj=h6=ru"758YpQf.?J3OJ?IF*;b+EH-K->/H?T[jF2KJrC8\Ei7D`aB;b$@&oUeLlPg[k&+%Asr1dp9t1#@h!9kpm=gF"L`"10LYRB,FUeXncg+cL=QSkk(h4@uumAk%W3NO:W]9=fC;Be.jC#HO1t/V^CUm=`>F;&m,FVtp$@DSCrH7uefqNrAJ902=ITnC/tYf?Z9QVs%kOOK2OJW$lP=)S1b[5o=s>M*j[]&-!>a6Z`5nofdFd'BP&`MQRCi4?#[\%sq.TqfG7?"NOFT'&8NbfYg/P"/mbdeZ?PEM*gjMYgc/r>3XFi,ms\=8aFcmcHDLk!5IZuodgT$\ji,0-D]\C7ASNVA`)<8r8UkJ:Am.dr%9/4BBX@m>:L8J1:,4IBjP!M[4GSG'j'jRd*Sd2$DS>f&5@L%kWtXCBs[W(CX]W,-<9A<@n"7`J8iiDJ\>+BH?\$)>M%#R@"$@%p\^l^o@;'+,32,,qlY^eLcD(88aOD:hqtocD68h,LuL,XS;V<._?t^,U9/;P#\uO#?uFV1U:AXg[@(cQHE0jOaZ`U&b=A!dc2']Ea'o]@6:tc9p/)ODI2-)Qr,;Z.`blUW.U@=n:V\e%'8#4o30CBqYdaOCZ$hpc[;LC.7]l>HJqoWS]"tW)O'fL&Y[4rU(Kn0#^FXIg1Jnr;4V[6G=e6.^Q?NYdXCUmaV'&M4$T`BKh>~> endstream endobj 96 0 obj @@ -574,10 +574,10 @@ endobj >> endobj 97 0 obj -<< /Length 1845 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1852 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gb!#\gMZ%0&:O:SkcGdHAjhNfm1oj`?$kO!S"tn1(9!-X[O>@1/:'b<8c&+VAB>h6U?r"fTElO,'JF+EbT%+83us9WKTl[?%%(n]&>Zm9@UR)%M3?@SZ\=[lU1p*/DdY+A,phqTg@uDXQ,$Fl!POI7)H9h^PMUnm[Gpp7#5`5D'LuQCr#6ggcZR9m@YM]Okn_R8IR08JX$:AJ$X_/?Z%aHoO/cHd5b.\^d1_2$9<#s2*P\J\X3)LQW,X=:9`/ST1GaihhhgAVcJ)(r'XkI!$Z)^`iRcJs!2pX!$g:]hbLM[j0e7e.Qt5E&$pY##"1D'EYB0"e:a_1Q1@j"s>1ge.VVIAK`SBi*o]9LXe^lujk"h^/+3GGjH=p#Q0'H4.@I7$ErJdj>k/;\+0-6J'bLhP(Xls!UKlXXoJZ`dRnA9MOgER)bX.>p(I3R<$]-_\tao;'oJ]T%LsYT9e@VANClXrR_4<@f`n_oL*S^@8%DLe(bF@u>*iU2Vp^/aW*L&Pa/eN6*-?b\^1F:DkGVR4V$;#`)TpGW6j9R/2T=LM+>IkQ#uPq(?QBj.7h'D;3bkAe?]Q]>g(p&s[p@1^U)+H)G8c`hLW'_7:E[2(=O5:XP\medO\'OEd.mV1kE"*,Sk"i;PP[FN'\]1f$^LlZ&k!0s'9B'tn(M]qr2Ttf;2Fj(U^R9(Ko;N/-T8:V!cnl;B89mKY0l)9VdelS0IdTh;n%kAX)c7!mIZ&Lu*^=Hd$D\:3tf$7N,*"mnig"f5-\H@b[Jh23PLg!:&kC'2Qc;k,u#Y)-=;!3mA5'Q=['nmk$eo$!-EBHN;1C5:N&$!<~> +Gatm<>Ar7S'Roe[cs'ZrAjfPHAISj>>MQ&WP:Z7P!R!1<2J!8X79>^pl)u0T9B)Whg)a')WHBKWn2n+?L\s+P>u!oI7bCkD^JCWG7BY\J^A?_seao0U&:'3q"a=RR5=Q_*:7qVb5C3+XKK6Pg2,-PX4j-k*Yd9h@#kD9IRBE*qh[tP!4(D0hGo,WO+O%b;s%1D_'ef?="9\:k9_2i8[>aZcXed:shrZ+9j"sgnJ>t0L68"/'<"&`d9L&95;iJ@)q*gTT=!RCVa*QR^p\N-Zr3I1(dD,3r_2He6>(@^(lUf7*L&@C*X%)OX!:1oX!pHb(XEFO4[H'TekKEbp4!2nm_?d],c5fB4k*,+pduuldpZ:K.JJU'Vq4LbQ5[g_$)`O_#Ji]jIo6$W5!aEXZISW2RPX]qujXc^oQ:.I>T3(9)V8X/4.p!j-'"*)qF?K4J;?PiVW2NY)Tf]*Bb!joZRnZi*,,^9\RY3-F=]VMj&l`]Y2d/gV2Tui7X`UAFm(e)FFdn`0&cOXcckMHb=:R8c\%h^,-5il&W6d(poYgE&rRRN`.D_$SJM@GEbkHo*7cnb*AiKG#`%5rE7bj/R,LNn0:l)KCJ=\\WMN^]O2Mi)*=2cQ*h\DR]!UCW&9VW@31DMh8]nbHX8Z?_gpKR^8:7V^mRk2>Nr?^!8MqU2%JA90FD"<]d%>@T]"Gl`WE(bQQt:j8scT`.#a.<9iA\BG!Ou#IemeE9r]PD$bQEslrDNPLoKB[?_4Je:%6C6d!%'>\.AVsQrrH7s9:D9rspiVcY&2*(]Nc3)UjSCoB__J%&-E>gPO4?f+S6#'=n>iqWesgIl3U@N@5SWHE6:j57G*cd5Rp":U00HJdi8:XQjaUY;MD:"_I3HY*"T9l:6&@$4A-U't](N]p^_KqM(\d:W]B+-`Dn\C='D;3<)'VJckD?@aYCeAhTMDLXKK7)2`1OA0&dKHq6;\l!UJPiiEi/^jsJX6!DhWEFkWrFN7djD"T\taEG\BqOf0(BZ/4V:+9$#X29oD0[mHrlR\/.i3NZgunu/hlS"0bH3n*;ml]bUS^OjaZK`17I?*Xnugsm2fBQA1gj^S)T_r]_Qe]ftV'(,/B3R9u2$1_nmVG(WIhgPsq58@khK1OmsPU3$'F>5;>_f[j\:8iuN?aG;-=%o?c$/MOHW:d.*7tFI/Xkg%_+a*%r]pU_UWI@_%$5j7Wh0.B8A%,(8L4!8p=@Knp9>,Q]F6'#KBHZ3P962g-!/n+oiuPPnOMGAaMA&q1N0.&_H]`e1fHK!3WBaH]aQ=bm(q01T@h2\-b>"drm[M4/#tb!gu\f4R)!2H1!mBSj`h~> endstream endobj 98 0 obj @@ -589,10 +589,10 @@ endobj >> endobj 99 0 obj -<< /Length 1472 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1313 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gat%$=`<%S&:XAW&G"6RL`'&&FY&Ca9t+H)8<[j6pgpc[A=Kk-aJ,`QNrOO,_racBh&&G0N&7(#jrX%N*.82b^IHjXo&"pdJVd(!u+jL$s@f+5*$>RdSs,M!CB"soK"'b[t(luo7Pf\EZJPEm>8hB%lNY/FQs#rWrX%>pi7Qbe%<9(2#]GNmmAN^7a>=4.tW]-8bo`+pmYkq"C;*$.NR/0ekejei`rk/&!/53H=CGhV(=h?H-`>+4iV5bY(qD$^6A'T=h[kYF'K<`0VRJ7W\t5N`G#P5V80UPUk#XXir<#c('LZg^]bIlK/BQj#d>Z'#4P[#=:&_0k7oM_d?9\)9p>`hS'Z`s0W7*CgEQZ$Cr#I$B#Q67goY`hRVRlChMc#aU0d`i"dA;r+K(jVSg+=&&:38kUT:JL%[AE[T#8*I,*^.p/"E/<204%?aO$/(YIm4c'&P)tDNQ9qqulP:8(U0)?7M7,bolqc7e:(rmr)^mg("_Taakh1?Y/&7m3ja6+OO_Oo:qcS'L'%i2NQb/O7(:3-tj$TS["$8(s?D='K-m]Zn!$S+,!jinG`TkB_e!`&$B$_W%#kYn[TnL.CMIt\A\MR=E-;u6gVt*[=E1!V?6uj8Xcg0,?9$VsIi*M\_Er.aDX0o4f-7N1<<](kP1Ki(L:%O'V,98g\%%IS2i*s]HHD+J$NmOfTLRc^m4Q/8o'>,P/\*Q8fNoC5)m4L4!BS1K1BhI)/)h(uk`eF?X`HOQoj^?P<9.@jlmYXJ1lP:m[H+"YWd.h+5nhm]1Rd&cu_(pE!_!_EHj="j9!me:4??CEKQDclO@W0\Y8cKAF*jdM?h\Y!nL??LKrNa8:LHO!l1@R"aROa,;J&QqnYR-@2LMR)'o"?+;;m_e^@3=-])o9c>"nF7k+n2@\&/;Xmn/^otgr]E8f"3,3->g#4I_/T`":N@-9ik/>GO5[o]p=+UEOG3*VL((,FhZLHl\AtQ-AY]H$#!Mj@O$U5GM[8Y9LF=~> +Gb!#[gMYb*&:O:SkV3*QQD?o?#)X,>VI!eqD6f#L0?mt"g6=2)Q<]QpjmSPI:`RW@';I4=^k%7^cC>ctS09P\ak(4sF$cD)qYWOF@>#ZYpXObSf>C7--lKgdLNQ5VOe+#iQqc1?]G<>smo>8AQZE\+N4oYiF@JD%f_A=,n)KPOrRlPXFt3UG5HS0idID1$4;;4o4.=C:l8OpfC:p1oC4B3_q4IR%?gFcWInqX$%IWaiZL406ZH\fROoWs>E=eO$*$"o%7c\XW>oTjCP[*3ADU(;Q6a;uX`i8e3gYkB$jW[2jhQcY/M4ER3Y[0@9Xjrm*7F^Pf;i&(e;JW0K8C<&`p%BAfj*\TP"$6VB^\G'Or+];X*e=i%8?n1KnL<^(5O/&%:RSQ5'+^kW_h?J0!*VkWj34@7rG+`"2Wgf#upc^gJ]34(LHs!H(C_tHV,l.#.C*[-/*#/QRV"$O^9]6Fj"V$+b_PR6Fu#-+0f$0Y!>(`NAU>D3\5)N+Zh2n\'3$ptoKbgbO^=A;&7GE6)&f&Urg\%-hU\o1AN(NQ3lP]JpDhUijbeM@^2dN6#DBq.)7Hhm%N4n'(+07nW^r6fDa*!lFJO:KSYQart6PCifcH+$DWYY#I/b4ep'<0J.MmLLpt`b##Y304>sPC.&\Z#H*#WR60\E++*]2Dp]WH+Bi0R&WY/2"DVI5a@Mi;ZQ594StS=/6^R5oCj:d6d"eB?s0Xijd+bYlpu^s,,WIr+1F$5%b*3jF7r'q`DZ5QDTsiSZV[F[5)Y,h^m9)V!F_'*`im.HAW:STRaFg@uMh:?H.hN)3[7#VD=kg4#7D$6&YRl?JGHM#i[2@)28u^2"2?0?o*OL5''%dAUlVGI`qJh7Cq@*,oM@nK2>$r6o"Cpe;hpG!84F7LHL^FE<0,&H'tck1O%T^>9hY3"d$:Q`=lT%PJ$nqSmh0s~> endstream endobj 100 0 obj @@ -604,10 +604,10 @@ endobj >> endobj 101 0 obj -<< /Length 1726 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1805 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -Gatmdu-q)f;o[4UZ+up11'7:[-#?Gm6#*a=$JdIT/@T..6*oW]G$emV7sT48:U.sRPmhr>#J6#-&ph?gO]sP_(Ei!U6ht:n1?d"a*fBTUt7,Q50[gJ?_,`JXI6<'&P4R#`:KrQCr=H`fF<7.>`@mg%?ihT\a;8I5tFKSN1r;KY+pGmKW*clN[)TDGL#i9^YLmPQ"1lI':"B/YX,slA`s]7-drBBGKl0nj:^/k%T*MEq2XQ15^1!'S$nE#C*7>FcbB>AHJo7,>r"uc'1Eb2fI&fh,Do$4!6-j.p)rouEAP^&d#o#Lk@OW$,&kaEaN18B""BAJ=RQSAJRmt5P*I@cRF9oa&LDbHHln%ibC7[ST!ur:L.Ths_&.7Lo,qk4!ZLeA`YRfD'p4@G(]&+fB>\]PhLRP0=H%q"@Pq#uHX[:i/1M5\d;l]\5rX1BoYaUV0JDFg@'CdqEPctH_I2b/\n&CrZ"OMCE\Q1F.*QQ0k\fZZQ"mYIVO?/Sifd,#(&[RSXQ:8o=M9$/,jWV`MG#=9Wm8T>bH\g:.hdWl5CZ;SY-?!IV[aku@NAMRSD_?PtqI\CrNsl^\3;,Hu)u$Yd1`6#TIVK:9Y,(b2[Ya\-"5Bf^\r#"ih\!It,h0aocb=>'aiHe)2T?._N-PuGS1ZF?j4k1a#mpKSD=$9)^/_nY&5RKkp3NG'E7(of^a:.QQceTOFGnrlN)j;j"La/n&I"2]0J4XV!UHAl;.hW@6dfYi&QZB%ZU_('Ba7'!m1K;F=1CF6N(YbP:U^en`*ohkg,jF7$aVsEdQt;+;`mI74+2ViR?bu=].b<5qj&db'+SonaJ`EAA@Q,&r%eE""GV))ciD3$Y+=_We7R_*8[`BURNKfD[k24>.k5E&Uc>R,O-eke!&V+MHPu);)gi?R-[LX,R,"tgj;clS;*UG^I=@Tni6-08<3XjT\!CS4kJA)hkb!'Fb-GSd4:)oTN=Lsknh-Xh>2]#ib:rCIV5Y:h-E\c\.0[lTIY]fgqT5NfN\~> +Gau0D>Ar7S'Roe[&G!+0M]%?aFX`%H903q-MDe\Om@ics,#bp(=`ee+ls`2.Ua2GmaHW0f(kUBe7@s+A.mNPg%M/6X*^@06@O/_#8k'A:`Y#TN.C%C$>p7!"kcmBf9IPYK"6fjMW3jIFF[et1t`t$X#7Yu&1epM=_])7qm'sFYgCJ%f6Qdb274o"9f];!$j@l8WskN."jnh4(qB5@M4:q5g&mi_O\W-S'Y][$$Q4fj"+PY!0]dqQ?/4o'NWi$:+Al^)fleIe$*n`Q=iD9_YGjcUB'Fecg/'JB+tS8&-W0@@i0[t.U'=+3__Up%f7Roe:O&T$">@j9BCQU>-h2f0p(nqWkV?GC,>DmZb1a]:&&H*dcYJVH]LW1J6.K!d'OqDmc"!^F)(64g^)oTWn])hH;dN)@,=.u_DEVG[VimpA,%0RoBp<8Q30,TM<`:.%V`tfMP8!83$j.'2AgN,6f"Re_#5?,@T8+V%$C]_RlV]S>j'/!=Kre#@r'&]sP[,KpN*0O3n&dN"4_UVpS9LB9o,a;f^bc+,ij@\[s"c9]fRYJGq5G?IpU-3?d`L9Y'f;$"ecf5XjL,irm8)7HKh12<$:@+ikO]b8R&1s-7\VnES)DU>b1UXp-863DiccNmih@cVc,`-NXHka3"kPrc6'DA85k@#Jk!;]2rDK7(KEpT]_8j-?3%D"UiW_`&3'I=7U8`ud\:dHM--_@F/RhN9M4:L_M^$e)"Vb7-[C)o)L8),-ZPJAQO#"t:-hcj11cWYL0'&ZI0@)l>4AB$`bY[uD4EQ_X;hu0YqB1_+C5_1K?:F@*tsFuP.XqfhNtg=[]?_8jNSRH0>>T-Rq;gm@=s-gm`&qUINu8`n($E6UM1qD=r^9sET]%Eh3@(-A5Fr1^V+@Aa,_dP5.S^Z]XB/m&'=Th/!3*KTfObnl4+S)+F7+e1detOo=mt0Q$Z=T/p10oeShI"$_-0XXG<[\Mimqd?H%EV*SZgZnDX.u#.^%K1cN*;o#VF)+;8;?KXW97n7RCd>0"TaG]T3@TMRtMT&C5PK).\4u2XQOJIU@"q96)tfN>qg(d^faI)D*jYbcKTm@4%@GV8&CpSHlk#H1+*,=4)2#62Oo3,<#ES>HV-g#@Z?r.6)=0phls\[RGsn,$TJ3rjgcG>\goJs'ed&m@$.&X(SEf<_>~> endstream endobj 102 0 obj @@ -619,10 +619,10 @@ endobj >> endobj 103 0 obj -<< /Length 697 /Filter [ /ASCII85Decode /FlateDecode ] +<< /Length 1120 /Filter [ /ASCII85Decode /FlateDecode ] >> stream -GarnU995Pr&AI`dI*>3>f/T/gIp:R#praU4%i8Qq=ca"GoS,fM#7_+j81qp07!%5&)MpM*UNATa9X+@Naqr.Z/N?P#u4>F:_EU6?Lm$"a&#=oMnBDP)Qnf:ipSBLBg"/+*F^XMi)/A&J'#jRC-L@;9c`Y`]G`\nehWcAcY:nXLV5q1ie`eeqHE(8>kP=27aV/VqSMq1Sa@r7_\VD2+g>I&Zjg.hjPTke$c%FAot1hg;Mb1tOU`V0`d8GjMg!_2W-2c5aB)+e(r9Kq0Nn-)_-AQ%ic4P$d\/CJu".B\.GSTJ<=%"@[5/#H8d"7&/;KQ[8$Hirp]PZ&AZIiDK3Xj&G?Y.D;gRYY?=7]/t4GCq^OZ1:9J9%KRPuHW;DQm]$h90j.I8o;3>%#FAR!@pmN]J\9k!"Ra?B=.k%=^5p1>`B>k[cLU(g,8/2%DU68;1P8h>g\AeU"s8%tV`/4jfH`"]>fpNrT]e&FbYWY?PHAN,[N1Fs2$(,PBP@ASo,L*XSAS!O]&PRT^Q>'<_e>;d]T[O%\"_J(c,rIZ3iI$=Wf+dnZ6:2(=qYf];PE#(6o7TP(ZMY1*oYFRf*itWj(T]BJ&6AHs4Ys\>ged-eP,Q=MiF.(Y=AD-3RQ/!pS3kJ+G5(PH_$"2NQh-#upM>&*u#@5C~> +Gat%b99Z,/&:j6K'fsW>figri*kB7ZG\U-U<$"deR2gkl2H>90&GA)u52tO$Ma2Ga;]`"$^t?rj_d2U$-;V0:S>:Jq>+!s*`b-GEc"L:a8iCq.6o4hs)9n*)Uc#51_BpiiBNR*82,VYcBOkSjff^`E[,1fd#+]9'*[$Qd&O1qHGibUj:Qg2kS('[A'TtR&0_,UIMe1AT=!>'M$21=!k6K6HBO['9:0Zmk83\V73@p7[*`K`(/?XqX&S(?7H+2X>R\qdVM&:E:aFsTKjjp?'KIT5ooR@W=3Sk!JQ=@ij<4-EYka-rT>Y.ZDDC'Yn\@_e0)&"YME/Z!&k3DfuPQk)@l@r:L5QnDZXLd:[]D9Kh:,=,d`X5%!E/7f@qd,@#s[Q`Y(b/$e\W^T?ukC<"3lVQ^dPRs9\1XM#34?u@oe&0uqPGM!l%`ML[A^d^3I'e`SDuTe..U(9k\0:WWp`Z2^'n'S7K`/uVcI8["BLuJ1e%LFDifHIRD)BH*-VnTKchCTN5;d(cUVF5h#c8OH?6Kt#,ZYF!6;")$i~> endstream endobj 104 0 obj @@ -1038,73 +1038,73 @@ endobj 43 0 obj << /S /GoTo -/D [82 0 R /XYZ 85.0 230.6 null] +/D [84 0 R /XYZ 85.0 659.0 null] >> endobj 45 0 obj << /S /GoTo -/D [84 0 R /XYZ 85.0 616.6 null] +/D [84 0 R /XYZ 85.0 520.547 null] >> endobj 47 0 obj << /S /GoTo -/D [84 0 R /XYZ 85.0 538.947 null] +/D [84 0 R /XYZ 85.0 442.894 null] >> endobj 49 0 obj << /S /GoTo -/D [84 0 R /XYZ 85.0 286.494 null] +/D [84 0 R /XYZ 85.0 190.441 null] >> endobj 51 0 obj << /S /GoTo -/D [84 0 R /XYZ 85.0 234.16 null] +/D [86 0 R /XYZ 85.0 659.0 null] >> endobj 56 0 obj << /S /GoTo -/D [88 0 R /XYZ 85.0 528.6 null] +/D [88 0 R /XYZ 85.0 409.4 null] >> endobj 58 0 obj << /S /GoTo -/D [92 0 R /XYZ 85.0 516.2 null] +/D [92 0 R /XYZ 85.0 383.8 null] >> endobj 60 0 obj << /S /GoTo -/D [94 0 R /XYZ 85.0 291.8 null] +/D [94 0 R /XYZ 85.0 154.2 null] >> endobj 62 0 obj << /S /GoTo -/D [96 0 R /XYZ 85.0 357.4 null] +/D [96 0 R /XYZ 85.0 230.2 null] >> endobj 64 0 obj << /S /GoTo -/D [98 0 R /XYZ 85.0 414.2 null] +/D [98 0 R /XYZ 85.0 287.0 null] >> endobj 66 0 obj << /S /GoTo -/D [102 0 R /XYZ 85.0 613.4 null] +/D [102 0 R /XYZ 85.0 478.6 null] >> endobj 68 0 obj << /S /GoTo -/D [102 0 R /XYZ 85.0 151.747 null] +/D [104 0 R /XYZ 85.0 547.8 null] >> endobj 105 0 obj @@ -1115,74 +1115,74 @@ endobj xref 0 142 0000000000 65535 f -0000048388 00000 n -0000048583 00000 n -0000048676 00000 n +0000048685 00000 n +0000048880 00000 n +0000048973 00000 n 0000000015 00000 n 0000000071 00000 n -0000001278 00000 n -0000001398 00000 n -0000001570 00000 n -0000048828 00000 n -0000001705 00000 n -0000048891 00000 n -0000001840 00000 n -0000048957 00000 n -0000001977 00000 n -0000049021 00000 n -0000002114 00000 n -0000049087 00000 n -0000002251 00000 n -0000049153 00000 n -0000002388 00000 n -0000049219 00000 n -0000002525 00000 n -0000049283 00000 n -0000002662 00000 n -0000049349 00000 n -0000002799 00000 n -0000049413 00000 n -0000002936 00000 n -0000049479 00000 n -0000003073 00000 n -0000049545 00000 n -0000003210 00000 n -0000049610 00000 n -0000003347 00000 n -0000049676 00000 n -0000003484 00000 n -0000049740 00000 n -0000003620 00000 n -0000049806 00000 n -0000003757 00000 n -0000049870 00000 n -0000003893 00000 n -0000049936 00000 n -0000004030 00000 n -0000050000 00000 n -0000004167 00000 n -0000050064 00000 n -0000004303 00000 n -0000050130 00000 n -0000004440 00000 n -0000050196 00000 n -0000004576 00000 n +0000001276 00000 n +0000001396 00000 n +0000001568 00000 n +0000049125 00000 n +0000001703 00000 n +0000049188 00000 n +0000001838 00000 n +0000049254 00000 n +0000001975 00000 n +0000049318 00000 n +0000002112 00000 n +0000049384 00000 n +0000002249 00000 n +0000049450 00000 n +0000002386 00000 n +0000049516 00000 n +0000002523 00000 n +0000049580 00000 n +0000002660 00000 n +0000049646 00000 n +0000002797 00000 n +0000049710 00000 n +0000002934 00000 n +0000049776 00000 n +0000003071 00000 n +0000049842 00000 n +0000003208 00000 n +0000049907 00000 n +0000003345 00000 n +0000049973 00000 n +0000003482 00000 n +0000050037 00000 n +0000003618 00000 n +0000050103 00000 n +0000003755 00000 n +0000050167 00000 n +0000003891 00000 n +0000050233 00000 n +0000004028 00000 n +0000050297 00000 n +0000004165 00000 n +0000050363 00000 n +0000004301 00000 n +0000050429 00000 n +0000004438 00000 n +0000050495 00000 n +0000004574 00000 n 0000005293 00000 n 0000005416 00000 n 0000005485 00000 n -0000050261 00000 n +0000050559 00000 n 0000005618 00000 n -0000050325 00000 n +0000050623 00000 n 0000005751 00000 n -0000050389 00000 n +0000050687 00000 n 0000005884 00000 n -0000050453 00000 n +0000050751 00000 n 0000006017 00000 n -0000050517 00000 n +0000050815 00000 n 0000006150 00000 n -0000050581 00000 n +0000050879 00000 n 0000006282 00000 n -0000050646 00000 n +0000050944 00000 n 0000006415 00000 n 0000008563 00000 n 0000008671 00000 n @@ -1194,68 +1194,68 @@ xref 0000015503 00000 n 0000017991 00000 n 0000018099 00000 n +0000019975 00000 n 0000020083 00000 n -0000020191 00000 n -0000022539 00000 n -0000022647 00000 n -0000024428 00000 n -0000024536 00000 n -0000025961 00000 n -0000026069 00000 n -0000027602 00000 n -0000027710 00000 n -0000029426 00000 n -0000029534 00000 n -0000031444 00000 n -0000031552 00000 n -0000033595 00000 n -0000033703 00000 n -0000035553 00000 n -0000035661 00000 n -0000037599 00000 n -0000037707 00000 n -0000039272 00000 n -0000039381 00000 n -0000041201 00000 n -0000041311 00000 n -0000042101 00000 n -0000050713 00000 n -0000042211 00000 n -0000042411 00000 n -0000042629 00000 n -0000042835 00000 n -0000043043 00000 n -0000043211 00000 n -0000043411 00000 n -0000043569 00000 n -0000043744 00000 n -0000043985 00000 n -0000044114 00000 n -0000044268 00000 n -0000044422 00000 n -0000044566 00000 n -0000044716 00000 n -0000044857 00000 n -0000045097 00000 n -0000045279 00000 n -0000045452 00000 n -0000045655 00000 n -0000045843 00000 n -0000046095 00000 n -0000046236 00000 n -0000046445 00000 n -0000046631 00000 n -0000046805 00000 n -0000047050 00000 n -0000047241 00000 n -0000047447 00000 n -0000047608 00000 n -0000047722 00000 n -0000047833 00000 n -0000047945 00000 n -0000048054 00000 n -0000048161 00000 n -0000048278 00000 n +0000022353 00000 n +0000022461 00000 n +0000024441 00000 n +0000024549 00000 n +0000026017 00000 n +0000026125 00000 n +0000027509 00000 n +0000027617 00000 n +0000029478 00000 n +0000029586 00000 n +0000031316 00000 n +0000031424 00000 n +0000033608 00000 n +0000033716 00000 n +0000035499 00000 n +0000035607 00000 n +0000037552 00000 n +0000037660 00000 n +0000039066 00000 n +0000039175 00000 n +0000041074 00000 n +0000041184 00000 n +0000042398 00000 n +0000051009 00000 n +0000042508 00000 n +0000042708 00000 n +0000042926 00000 n +0000043132 00000 n +0000043340 00000 n +0000043508 00000 n +0000043708 00000 n +0000043866 00000 n +0000044041 00000 n +0000044282 00000 n +0000044411 00000 n +0000044565 00000 n +0000044719 00000 n +0000044863 00000 n +0000045013 00000 n +0000045154 00000 n +0000045394 00000 n +0000045576 00000 n +0000045749 00000 n +0000045952 00000 n +0000046140 00000 n +0000046392 00000 n +0000046533 00000 n +0000046742 00000 n +0000046928 00000 n +0000047102 00000 n +0000047347 00000 n +0000047538 00000 n +0000047744 00000 n +0000047905 00000 n +0000048019 00000 n +0000048130 00000 n +0000048242 00000 n +0000048351 00000 n +0000048458 00000 n +0000048575 00000 n trailer << /Size 142 @@ -1263,5 +1263,5 @@ trailer /Info 4 0 R >> startxref -50767 +51063 %%EOF diff --git a/src/java/org/apache/lucene/index/CheckIndex.java b/src/java/org/apache/lucene/index/CheckIndex.java index 795042514fb..b11f799accb 100644 --- a/src/java/org/apache/lucene/index/CheckIndex.java +++ b/src/java/org/apache/lucene/index/CheckIndex.java @@ -113,7 +113,10 @@ public class CheckIndex { sFormat = "FORMAT_SINGLE_NORM_FILE [Lucene 2.2]"; else if (format == SegmentInfos.FORMAT_SHARED_DOC_STORE) sFormat = "FORMAT_SHARED_DOC_STORE [Lucene 2.3]"; - else if (format < SegmentInfos.FORMAT_SHARED_DOC_STORE) { + else if (format < SegmentInfos.FORMAT_CHECKSUM) { + sFormat = "FORMAT_CHECKSUM [Lucene 2.4]"; + skip = true; + } else if (format < SegmentInfos.FORMAT_CHECKSUM) { sFormat = "int=" + format + " [newer version of Lucene than this tool]"; skip = true; } else { @@ -320,7 +323,7 @@ public class CheckIndex { } out.print("Writing..."); try { - newSIS.write(dir); + newSIS.commit(dir); } catch (Throwable t) { out.println("FAILED; exiting"); t.printStackTrace(out); diff --git a/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java b/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java index b954f965713..9ea7903b120 100644 --- a/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java +++ b/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java @@ -46,6 +46,7 @@ public class ConcurrentMergeScheduler extends MergeScheduler { private boolean closed; protected IndexWriter writer; + protected int mergeThreadCount; public ConcurrentMergeScheduler() { if (allInstances != null) { @@ -211,10 +212,11 @@ public class ConcurrentMergeScheduler extends MergeScheduler { } /** Create and return a new MergeThread */ - protected MergeThread getMergeThread(IndexWriter writer, MergePolicy.OneMerge merge) throws IOException { + protected synchronized MergeThread getMergeThread(IndexWriter writer, MergePolicy.OneMerge merge) throws IOException { final MergeThread thread = new MergeThread(writer, merge); thread.setThreadPriority(mergeThreadPriority); thread.setDaemon(true); + thread.setName("Lucene Merge Thread #" + mergeThreadCount++); return thread; } @@ -297,9 +299,9 @@ public class ConcurrentMergeScheduler extends MergeScheduler { } } finally { synchronized(ConcurrentMergeScheduler.this) { + ConcurrentMergeScheduler.this.notifyAll(); boolean removed = mergeThreads.remove(this); assert removed; - ConcurrentMergeScheduler.this.notifyAll(); } } } @@ -334,6 +336,12 @@ public class ConcurrentMergeScheduler extends MergeScheduler { } } + public static void clearUnhandledExceptions() { + synchronized(allInstances) { + anyExceptions = false; + } + } + /** Used for testing */ private void addMyself() { synchronized(allInstances) { diff --git a/src/java/org/apache/lucene/index/DirectoryIndexReader.java b/src/java/org/apache/lucene/index/DirectoryIndexReader.java index 658656e3e91..a1726e8a9d2 100644 --- a/src/java/org/apache/lucene/index/DirectoryIndexReader.java +++ b/src/java/org/apache/lucene/index/DirectoryIndexReader.java @@ -19,6 +19,9 @@ package org.apache.lucene.index; import java.io.IOException; +import java.util.HashSet; +import java.util.List; + import org.apache.lucene.store.Directory; import org.apache.lucene.store.Lock; import org.apache.lucene.store.LockObtainFailedException; @@ -37,6 +40,7 @@ abstract class DirectoryIndexReader extends IndexReader { private SegmentInfos segmentInfos; private Lock writeLock; private boolean stale; + private HashSet synced = new HashSet(); /** Used by commit() to record pre-commit state in case * rollback is necessary */ @@ -44,16 +48,28 @@ abstract class DirectoryIndexReader extends IndexReader { private SegmentInfos rollbackSegmentInfos; - void init(Directory directory, SegmentInfos segmentInfos, boolean closeDirectory) { + void init(Directory directory, SegmentInfos segmentInfos, boolean closeDirectory) + throws IOException { this.directory = directory; this.segmentInfos = segmentInfos; this.closeDirectory = closeDirectory; + + if (segmentInfos != null) { + // We assume that this segments_N was previously + // properly sync'd: + for(int i=0;i 0) { - for(int i=0;i 0) { + for(int i=0;i -

The optional autoCommit argument to the - constructors - controls visibility of the changes to {@link IndexReader} instances reading the same index. - When this is false, changes are not - visible until {@link #close()} is called. - Note that changes will still be flushed to the - {@link org.apache.lucene.store.Directory} as new files, - but are not committed (no new segments_N file - is written referencing the new files) until {@link #close} is - called. If something goes terribly wrong (for example the - JVM crashes) before {@link #close()}, then - the index will reflect none of the changes made (it will - remain in its starting state). - You can also call {@link #abort()}, which closes the writer without committing any - changes, and removes any index +

[Deprecated: Note that in 3.0, IndexWriter will + no longer accept autoCommit=true (it will be hardwired to + false). You can always call {@link IndexWriter#commit()} yourself + when needed]. The optional autoCommit argument to the constructors + controls visibility of the changes to {@link IndexReader} + instances reading the same index. When this is + false, changes are not visible until {@link + #close()} is called. Note that changes will still be + flushed to the {@link org.apache.lucene.store.Directory} + as new files, but are not committed (no new + segments_N file is written referencing the + new files, nor are the files sync'd to stable storage) + until {@link #commit} or {@link #close} is called. If something + goes terribly wrong (for example the JVM crashes), then + the index will reflect none of the changes made since the + last commit, or the starting state if commit was not called. + You can also call {@link #abort}, which closes the writer + without committing any changes, and removes any index files that had been flushed but are now unreferenced. This mode is useful for preventing readers from refreshing at a bad time (for example after you've done all your - deletes but before you've done your adds). - It can also be used to implement simple single-writer - transactional semantics ("all or none").

+ deletes but before you've done your adds). It can also be + used to implement simple single-writer transactional + semantics ("all or none").

When autoCommit is true then - every flush is also a commit ({@link IndexReader} - instances will see each flush as changes to the index). - This is the default, to match the behavior before 2.2. - When running in this mode, be careful not to refresh your + the writer will periodically commit on its own. This is + the default, to match the behavior before 2.2. However, + in 3.0, autoCommit will be hardwired to false. There is + no guarantee when exactly an auto commit will occur (it + used to be after every flush, but it is now after every + completed merge, as of 2.4). If you want to force a + commit, call {@link #commit}, or, close the writer. Once + a commit has finished, ({@link IndexReader} instances will + see the changes to the index as of that commit. When + running in this mode, be careful not to refresh your readers while optimize or segment merges are taking place as this can tie up substantial disk space.

@@ -250,7 +264,20 @@ public class IndexWriter { * set (see {@link #setInfoStream}). */ public final static int MAX_TERM_LENGTH = DocumentsWriter.MAX_TERM_LENGTH; - + + /** + * Default for {@link #getMaxSyncPauseSeconds}. On + * Windows this defaults to 10.0 seconds; elsewhere it's + * 0. + */ + public final static double DEFAULT_MAX_SYNC_PAUSE_SECONDS; + static { + if (Constants.WINDOWS) + DEFAULT_MAX_SYNC_PAUSE_SECONDS = 10.0; + else + DEFAULT_MAX_SYNC_PAUSE_SECONDS = 0.0; + } + // The normal read buffer size defaults to 1024, but // increasing this during merging seems to yield // performance gains. However we don't want to increase @@ -269,14 +296,18 @@ public class IndexWriter { private Similarity similarity = Similarity.getDefault(); // how to normalize - private boolean commitPending; // true if segmentInfos has changes not yet committed + private volatile boolean commitPending; // true if segmentInfos has changes not yet committed private SegmentInfos rollbackSegmentInfos; // segmentInfos we will fallback to if the commit fails + private HashMap rollbackSegments; private SegmentInfos localRollbackSegmentInfos; // segmentInfos we will fallback to if the commit fails private boolean localAutoCommit; // saved autoCommit during local transaction private boolean autoCommit = true; // false if we should commit only on close private SegmentInfos segmentInfos = new SegmentInfos(); // the segments + private int syncCount; + private int syncCountSaved = -1; + private DocumentsWriter docWriter; private IndexFileDeleter deleter; @@ -302,6 +333,12 @@ public class IndexWriter { private long mergeGen; private boolean stopMerges; + private int flushCount; + private double maxSyncPauseSeconds = DEFAULT_MAX_SYNC_PAUSE_SECONDS; + + // Last (right most) SegmentInfo created by a merge + private SegmentInfo lastMergeInfo; + /** * Used internally to throw an {@link * AlreadyClosedException} if this IndexWriter has been @@ -432,7 +469,9 @@ public class IndexWriter { * Constructs an IndexWriter for the index in path. * Text will be analyzed with a. If create * is true, then a new, empty index will be created in - * path, replacing the index already there, if any. + * path, replacing the index already there, + * if any. Note that autoCommit defaults to true, but + * starting in 3.0 it will be hardwired to false. * * @param path the path to the index directory * @param a the analyzer to use @@ -487,6 +526,8 @@ public class IndexWriter { * Text will be analyzed with a. If create * is true, then a new, empty index will be created in * path, replacing the index already there, if any. + * Note that autoCommit defaults to true, but starting in 3.0 + * it will be hardwired to false. * * @param path the path to the index directory * @param a the analyzer to use @@ -541,6 +582,8 @@ public class IndexWriter { * Text will be analyzed with a. If create * is true, then a new, empty index will be created in * d, replacing the index already there, if any. + * Note that autoCommit defaults to true, but starting in 3.0 + * it will be hardwired to false. * * @param d the index directory * @param a the analyzer to use @@ -595,6 +638,8 @@ public class IndexWriter { * path, first creating it if it does not * already exist. Text will be analyzed with * a. + * Note that autoCommit defaults to true, but starting in 3.0 + * it will be hardwired to false. * * @param path the path to the index directory * @param a the analyzer to use @@ -641,6 +686,8 @@ public class IndexWriter { * path, first creating it if it does not * already exist. Text will be analyzed with * a. + * Note that autoCommit defaults to true, but starting in 3.0 + * it will be hardwired to false. * * @param path the path to the index directory * @param a the analyzer to use @@ -687,6 +734,8 @@ public class IndexWriter { * d, first creating it if it does not * already exist. Text will be analyzed with * a. + * Note that autoCommit defaults to true, but starting in 3.0 + * it will be hardwired to false. * * @param d the index directory * @param a the analyzer to use @@ -746,6 +795,10 @@ public class IndexWriter { * @throws IOException if the directory cannot be * read/written to or if there is any other low-level * IO error + * @deprecated This will be removed in 3.0, when + * autoCommit will be hardwired to false. Use {@link + * #IndexWriter(Directory,Analyzer,MaxFieldLength)} + * instead, and call {@link #commit} when needed. */ public IndexWriter(Directory d, boolean autoCommit, Analyzer a, MaxFieldLength mfl) throws CorruptIndexException, LockObtainFailedException, IOException { @@ -798,6 +851,10 @@ public class IndexWriter { * if it does not exist and create is * false or if there is any other low-level * IO error + * @deprecated This will be removed in 3.0, when + * autoCommit will be hardwired to false. Use {@link + * #IndexWriter(Directory,Analyzer,boolean,MaxFieldLength)} + * instead, and call {@link #commit} when needed. */ public IndexWriter(Directory d, boolean autoCommit, Analyzer a, boolean create, MaxFieldLength mfl) throws CorruptIndexException, LockObtainFailedException, IOException { @@ -832,6 +889,31 @@ public class IndexWriter { init(d, a, create, false, null, autoCommit, DEFAULT_MAX_FIELD_LENGTH); } + /** + * Expert: constructs an IndexWriter with a custom {@link + * IndexDeletionPolicy}, for the index in d, + * first creating it if it does not already exist. Text + * will be analyzed with a. + * Note that autoCommit defaults to true, but starting in 3.0 + * it will be hardwired to false. + * + * @param d the index directory + * @param a the analyzer to use + * @param deletionPolicy see above + * @param mfl whether or not to limit field lengths + * @throws CorruptIndexException if the index is corrupt + * @throws LockObtainFailedException if another writer + * has this index open (write.lock could not + * be obtained) + * @throws IOException if the directory cannot be + * read/written to or if there is any other low-level + * IO error + */ + public IndexWriter(Directory d, Analyzer a, IndexDeletionPolicy deletionPolicy, MaxFieldLength mfl) + throws CorruptIndexException, LockObtainFailedException, IOException { + init(d, a, false, deletionPolicy, true, mfl.getLimit()); + } + /** * Expert: constructs an IndexWriter with a custom {@link * IndexDeletionPolicy}, for the index in d, @@ -851,6 +933,10 @@ public class IndexWriter { * @throws IOException if the directory cannot be * read/written to or if there is any other low-level * IO error + * @deprecated This will be removed in 3.0, when + * autoCommit will be hardwired to false. Use {@link + * #IndexWriter(Directory,Analyzer,IndexDeletionPolicy,MaxFieldLength)} + * instead, and call {@link #commit} when needed. */ public IndexWriter(Directory d, boolean autoCommit, Analyzer a, IndexDeletionPolicy deletionPolicy, MaxFieldLength mfl) throws CorruptIndexException, LockObtainFailedException, IOException { @@ -882,6 +968,37 @@ public class IndexWriter { init(d, a, false, deletionPolicy, autoCommit, DEFAULT_MAX_FIELD_LENGTH); } + /** + * Expert: constructs an IndexWriter with a custom {@link + * IndexDeletionPolicy}, for the index in d. + * Text will be analyzed with a. If + * create is true, then a new, empty index + * will be created in d, replacing the index + * already there, if any. + * Note that autoCommit defaults to true, but starting in 3.0 + * it will be hardwired to false. + * + * @param d the index directory + * @param a the analyzer to use + * @param create true to create the index or overwrite + * the existing one; false to append to the existing + * index + * @param deletionPolicy see above + * @param mfl whether or not to limit field lengths + * @throws CorruptIndexException if the index is corrupt + * @throws LockObtainFailedException if another writer + * has this index open (write.lock could not + * be obtained) + * @throws IOException if the directory cannot be read/written to, or + * if it does not exist and create is + * false or if there is any other low-level + * IO error + */ + public IndexWriter(Directory d, Analyzer a, boolean create, IndexDeletionPolicy deletionPolicy, MaxFieldLength mfl) + throws CorruptIndexException, LockObtainFailedException, IOException { + init(d, a, create, false, deletionPolicy, true, mfl.getLimit()); + } + /** * Expert: constructs an IndexWriter with a custom {@link * IndexDeletionPolicy}, for the index in d. @@ -907,6 +1024,10 @@ public class IndexWriter { * if it does not exist and create is * false or if there is any other low-level * IO error + * @deprecated This will be removed in 3.0, when + * autoCommit will be hardwired to false. Use {@link + * #IndexWriter(Directory,Analyzer,boolean,IndexDeletionPolicy,MaxFieldLength)} + * instead, and call {@link #commit} when needed. */ public IndexWriter(Directory d, boolean autoCommit, Analyzer a, boolean create, IndexDeletionPolicy deletionPolicy, MaxFieldLength mfl) throws CorruptIndexException, LockObtainFailedException, IOException { @@ -984,15 +1105,22 @@ public class IndexWriter { } catch (IOException e) { // Likely this means it's a fresh directory } - segmentInfos.write(directory); + segmentInfos.commit(directory); } else { segmentInfos.read(directory); + + // We assume that this segments_N was previously + // properly sync'd: + for(int i=0;i If an Exception is hit during close, eg due to disk * full or some other reason, then both the on-disk index @@ -1490,33 +1654,16 @@ public class IndexWriter { mergeScheduler.close(); + if (infoStream != null) + message("now call final sync()"); + + sync(true, 0); + + if (infoStream != null) + message("at close: " + segString()); + synchronized(this) { - if (commitPending) { - boolean success = false; - try { - segmentInfos.write(directory); // now commit changes - success = true; - } finally { - if (!success) { - if (infoStream != null) - message("hit exception committing segments file during close"); - deletePartialSegmentsFile(); - } - } - if (infoStream != null) - message("close: wrote segments file \"" + segmentInfos.getCurrentSegmentFileName() + "\""); - - deleter.checkpoint(segmentInfos, true); - - commitPending = false; - rollbackSegmentInfos = null; - } - - if (infoStream != null) - message("at close: " + segString()); - docWriter = null; - deleter.close(); } @@ -1527,7 +1674,9 @@ public class IndexWriter { writeLock.release(); // release write lock writeLock = null; } - closed = true; + synchronized(this) { + closed = true; + } } finally { synchronized(this) { @@ -1581,34 +1730,24 @@ public class IndexWriter { // Perform the merge cfsWriter.close(); - - for(int i=0;iNote: if autoCommit=false, flushed data would still - * not be visible to readers, until {@link #close} is called. + *

Note: while this will force buffered docs to be + * pushed into the index, it will not make these docs + * visible to a reader. Use {@link #commit} instead * @throws CorruptIndexException if the index is corrupt * @throws IOException if there is a low-level IO error + * @deprecated please call {@link #commit}) instead */ public final void flush() throws CorruptIndexException, IOException { flush(true, false); } + /** + *

Commits all pending updates (added & deleted documents) + * to the index, and syncs all referenced index files, + * such that a reader will see the changes. Note that + * this does not wait for any running background merges to + * finish. This may be a costly operation, so you should + * test the cost in your application and do it only when + * really necessary.

+ * + *

Note that this operation calls Directory.sync on + * the index files. That call should not return until the + * file contents & metadata are on stable storage. For + * FSDirectory, this calls the OS's fsync. But, beware: + * some hardware devices may in fact cache writes even + * during fsync, and return before the bits are actually + * on stable storage, to give the appearance of faster + * performance. If you have such a device, and it does + * not have a battery backup (for example) then on power + * loss it may still lose data. Lucene cannot guarantee + * consistency on such devices.

+ */ + public final void commit() throws CorruptIndexException, IOException { + commit(true); + } + + private final void commit(boolean triggerMerges) throws CorruptIndexException, IOException { + flush(triggerMerges, true); + sync(true, 0); + } + /** * Flush all in-memory buffered udpates (adds and deletes) * to the Directory. @@ -2681,10 +2852,15 @@ public class IndexWriter { maybeMerge(); } + // TODO: this method should not have to be entirely + // synchronized, ie, merges should be allowed to commit + // even while a flush is happening private synchronized final boolean doFlush(boolean flushDocStores) throws CorruptIndexException, IOException { // Make sure no threads are actively adding a document + flushCount++; + // Returns true if docWriter is currently aborting, in // which case we skip flushing this segment if (docWriter.pauseAllThreads()) { @@ -2717,18 +2893,6 @@ public class IndexWriter { // apply to more than just the last flushed segment boolean flushDeletes = docWriter.hasDeletes(); - if (infoStream != null) { - message(" flush: segment=" + docWriter.getSegment() + - " docStoreSegment=" + docWriter.getDocStoreSegment() + - " docStoreOffset=" + docWriter.getDocStoreOffset() + - " flushDocs=" + flushDocs + - " flushDeletes=" + flushDeletes + - " flushDocStores=" + flushDocStores + - " numDocs=" + numDocs + - " numBufDelTerms=" + docWriter.getNumBufferedDeleteTerms()); - message(" index before flush " + segString()); - } - int docStoreOffset = docWriter.getDocStoreOffset(); // docStoreOffset should only be non-zero when @@ -2737,6 +2901,18 @@ public class IndexWriter { boolean docStoreIsCompoundFile = false; + if (infoStream != null) { + message(" flush: segment=" + docWriter.getSegment() + + " docStoreSegment=" + docWriter.getDocStoreSegment() + + " docStoreOffset=" + docStoreOffset + + " flushDocs=" + flushDocs + + " flushDeletes=" + flushDeletes + + " flushDocStores=" + flushDocStores + + " numDocs=" + numDocs + + " numBufDelTerms=" + docWriter.getNumBufferedDeleteTerms()); + message(" index before flush " + segString()); + } + // Check if the doc stores must be separately flushed // because other segments, besides the one we are about // to flush, reference it @@ -2754,60 +2930,63 @@ public class IndexWriter { // If we are flushing docs, segment must not be null: assert segment != null || !flushDocs; - if (flushDocs || flushDeletes) { - - SegmentInfos rollback = null; - - if (flushDeletes) - rollback = (SegmentInfos) segmentInfos.clone(); + if (flushDocs) { boolean success = false; + final int flushedDocCount; try { - if (flushDocs) { - - if (0 == docStoreOffset && flushDocStores) { - // This means we are flushing private doc stores - // with this segment, so it will not be shared - // with other segments - assert docStoreSegment != null; - assert docStoreSegment.equals(segment); - docStoreOffset = -1; - docStoreIsCompoundFile = false; - docStoreSegment = null; - } - - int flushedDocCount = docWriter.flush(flushDocStores); - - newSegment = new SegmentInfo(segment, - flushedDocCount, - directory, false, true, - docStoreOffset, docStoreSegment, - docStoreIsCompoundFile); - segmentInfos.addElement(newSegment); - } - - if (flushDeletes) { - // we should be able to change this so we can - // buffer deletes longer and then flush them to - // multiple flushed segments, when - // autoCommit=false - applyDeletes(flushDocs); - doAfterFlush(); - } - - checkpoint(); + flushedDocCount = docWriter.flush(flushDocStores); success = true; } finally { if (!success) { - if (infoStream != null) message("hit exception flushing segment " + segment); - - if (flushDeletes) { + docWriter.abort(null); + deleter.refresh(segment); + } + } + + if (0 == docStoreOffset && flushDocStores) { + // This means we are flushing private doc stores + // with this segment, so it will not be shared + // with other segments + assert docStoreSegment != null; + assert docStoreSegment.equals(segment); + docStoreOffset = -1; + docStoreIsCompoundFile = false; + docStoreSegment = null; + } - // Carefully check if any partial .del files - // should be removed: + // Create new SegmentInfo, but do not add to our + // segmentInfos until deletes are flushed + // successfully. + newSegment = new SegmentInfo(segment, + flushedDocCount, + directory, false, true, + docStoreOffset, docStoreSegment, + docStoreIsCompoundFile); + } + + if (flushDeletes) { + try { + SegmentInfos rollback = (SegmentInfos) segmentInfos.clone(); + + boolean success = false; + try { + // we should be able to change this so we can + // buffer deletes longer and then flush them to + // multiple flushed segments only when a commit() + // finally happens + applyDeletes(newSegment); + success = true; + } finally { + if (!success) { + if (infoStream != null) + message("hit exception flushing deletes"); + + // Carefully remove any partially written .del + // files final int size = rollback.size(); for(int i=0;i 0 && - segmentInfos.info(segmentInfos.size()-1) == newSegment) - segmentInfos.remove(segmentInfos.size()-1); - } - if (flushDocs) - docWriter.abort(null); - deletePartialSegmentsFile(); - deleter.checkpoint(segmentInfos, false); - - if (segment != null) - deleter.refresh(segment); + } } + } finally { + // Regardless of success of failure in flushing + // deletes, we must clear them from our buffer: + docWriter.clearBufferedDeletes(); } - - deleter.checkpoint(segmentInfos, autoCommit); - - if (flushDocs && mergePolicy.useCompoundFile(segmentInfos, - newSegment)) { - success = false; - try { - docWriter.createCompoundFile(segment); - newSegment.setUseCompoundFile(true); - checkpoint(); - success = true; - } finally { - if (!success) { - if (infoStream != null) - message("hit exception creating compound file for newly flushed segment " + segment); - newSegment.setUseCompoundFile(false); - deleter.deleteFile(segment + "." + IndexFileNames.COMPOUND_FILE_EXTENSION); - deletePartialSegmentsFile(); - } - } - - deleter.checkpoint(segmentInfos, autoCommit); - } - - return true; - } else { - return false; } + if (flushDocs) + segmentInfos.addElement(newSegment); + + if (flushDocs || flushDeletes) + checkpoint(); + + doAfterFlush(); + + if (flushDocs && mergePolicy.useCompoundFile(segmentInfos, newSegment)) { + // Now build compound file + boolean success = false; + try { + docWriter.createCompoundFile(segment); + success = true; + } finally { + if (!success) { + if (infoStream != null) + message("hit exception creating compound file for newly flushed segment " + segment); + deleter.deleteFile(segment + "." + IndexFileNames.COMPOUND_FILE_EXTENSION); + } + } + + newSegment.setUseCompoundFile(true); + checkpoint(); + } + + return flushDocs || flushDeletes; + } finally { docWriter.clearFlushPending(); docWriter.resumeAllThreads(); @@ -2913,9 +3086,101 @@ public class IndexWriter { return first; } + /** Carefully merges deletes for the segments we just + * merged. This is tricky because, although merging will + * clear all deletes (compacts the documents), new + * deletes may have been flushed to the segments since + * the merge was started. This method "carries over" + * such new deletes onto the newly merged segment, and + * saves the results deletes file (incrementing the + * delete generation for merge.info). If no deletes were + * flushed, no new deletes file is saved. */ + synchronized private void commitMergedDeletes(MergePolicy.OneMerge merge) throws IOException { + final SegmentInfos sourceSegmentsClone = merge.segmentsClone; + final SegmentInfos sourceSegments = merge.segments; + + if (infoStream != null) + message("commitMerge " + merge.segString(directory)); + + // Carefully merge deletes that occurred after we + // started merging: + + BitVector deletes = null; + int docUpto = 0; + + final int numSegmentsToMerge = sourceSegments.size(); + for(int i=0;i 0; - boolean success = false; try { try { - if (merge.info == null) - mergeInit(merge); + mergeInit(merge); if (infoStream != null) message("now merge\n merge=" + merge.segString(directory) + "\n index=" + segString()); @@ -3131,11 +3286,17 @@ public class IndexWriter { } finally { synchronized(this) { try { - if (!success && infoStream != null) - message("hit exception during merge"); mergeFinish(merge); + if (!success) { + if (infoStream != null) + message("hit exception during merge"); + addMergeException(merge); + if (merge.info != null && !segmentInfos.contains(merge.info)) + deleter.refresh(merge.info.name); + } + // This merge (and, generally, any change to the // segments) may now enable new merges, so we call // merge policy & update pending merges. @@ -3200,6 +3361,11 @@ public class IndexWriter { final synchronized void mergeInit(MergePolicy.OneMerge merge) throws IOException { assert merge.registerDone; + assert !merge.optimize || merge.maxNumSegmentsOptimize > 0; + + if (merge.info != null) + // mergeInit already done + return; if (merge.isAborted()) return; @@ -3323,6 +3489,50 @@ public class IndexWriter { docStoreOffset, docStoreSegment, docStoreIsCompoundFile); + + // Also enroll the merged segment into mergingSegments; + // this prevents it from getting selected for a merge + // after our merge is done but while we are building the + // CFS: + mergingSegments.add(merge.info); + } + + /** This is called after merging a segment and before + * building its CFS. Return true if the files should be + * sync'd. If you return false, then the source segment + * files that were merged cannot be deleted until the CFS + * file is built & sync'd. So, returning false consumes + * more transient disk space, but saves performance of + * not having to sync files which will shortly be deleted + * anyway. + * @deprecated -- this will be removed in 3.0 when + * autoCommit is hardwired to false */ + private synchronized boolean doCommitBeforeMergeCFS(MergePolicy.OneMerge merge) throws IOException { + long freeableBytes = 0; + final int size = merge.segments.size(); + for(int i=0;i totalBytes) + return true; + else + return false; } /** Does fininishing for a merge, which is fast but holds @@ -3338,6 +3548,7 @@ public class IndexWriter { final int end = sourceSegments.size(); for(int i=0;i X minutes or + // more than Y bytes have been written, etc. + if (autoCommit) + sync(false, merge.info.sizeInBytes()); + return mergedDocCount; } @@ -3495,23 +3676,11 @@ public class IndexWriter { mergeExceptions.add(merge); } - private void deletePartialSegmentsFile() throws IOException { - if (segmentInfos.getLastGeneration() != segmentInfos.getGeneration()) { - String segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, - "", - segmentInfos.getGeneration()); - if (infoStream != null) - message("now delete partial segments file \"" + segmentFileName + "\""); - - deleter.deleteFile(segmentFileName); - } - } - // Called during flush to apply any buffered deletes. If // flushedNewSegment is true then a new segment was just // created and flushed from the ram segments, so we will // selectively apply the deletes to that new segment. - private final void applyDeletes(boolean flushedNewSegment) throws CorruptIndexException, IOException { + private final void applyDeletes(SegmentInfo newSegment) throws CorruptIndexException, IOException { final HashMap bufferedDeleteTerms = docWriter.getBufferedDeleteTerms(); final List bufferedDeleteDocIDs = docWriter.getBufferedDeleteDocIDs(); @@ -3521,13 +3690,13 @@ public class IndexWriter { bufferedDeleteDocIDs.size() + " deleted docIDs on " + segmentInfos.size() + " segments."); - if (flushedNewSegment) { + if (newSegment != null) { IndexReader reader = null; try { // Open readers w/o opening the stored fields / // vectors because these files may still be held // open for writing by docWriter - reader = SegmentReader.get(segmentInfos.info(segmentInfos.size() - 1), false); + reader = SegmentReader.get(newSegment, false); // Apply delete terms to the segment just flushed from ram // apply appropriately so that a delete term is only applied to @@ -3544,10 +3713,7 @@ public class IndexWriter { } } - int infosEnd = segmentInfos.size(); - if (flushedNewSegment) { - infosEnd--; - } + final int infosEnd = segmentInfos.size(); for (int i = 0; i < infosEnd; i++) { IndexReader reader = null; @@ -3567,9 +3733,6 @@ public class IndexWriter { } } } - - // Clean up bufferedDeleteTerms. - docWriter.clearBufferedDeletes(); } // For test purposes. @@ -3644,6 +3807,236 @@ public class IndexWriter { return buffer.toString(); } + // Files that have been sync'd already + private HashSet synced = new HashSet(); + + // Files that are now being sync'd + private HashSet syncing = new HashSet(); + + private boolean startSync(String fileName, Collection pending) { + synchronized(synced) { + if (!synced.contains(fileName)) { + if (!syncing.contains(fileName)) { + syncing.add(fileName); + return true; + } else { + pending.add(fileName); + return false; + } + } else + return false; + } + } + + private void finishSync(String fileName, boolean success) { + synchronized(synced) { + assert syncing.contains(fileName); + syncing.remove(fileName); + if (success) + synced.add(fileName); + synced.notifyAll(); + } + } + + /** Blocks until all files in syncing are sync'd */ + private boolean waitForAllSynced(Collection syncing) throws IOException { + synchronized(synced) { + Iterator it = syncing.iterator(); + while(it.hasNext()) { + final String fileName = (String) it.next(); + while(!synced.contains(fileName)) { + if (!syncing.contains(fileName)) + // There was an error because a file that was + // previously syncing failed to appear in synced + return false; + else + try { + synced.wait(); + } catch (InterruptedException ie) { + continue; + } + } + } + return true; + } + } + + /** Pauses before syncing. On Windows, at least, it's + * best (performance-wise) to pause in order to let OS + * flush writes to disk on its own, before forcing a + * sync. + * @deprecated -- this will be removed in 3.0 when + * autoCommit is hardwired to false */ + private void syncPause(long sizeInBytes) { + if (mergeScheduler instanceof ConcurrentMergeScheduler && maxSyncPauseSeconds > 0) { + // Rough heuristic: for every 10 MB, we pause for 1 + // second, up until the max + long pauseTime = (long) (1000*sizeInBytes/10/1024/1024); + final long maxPauseTime = (long) (maxSyncPauseSeconds*1000); + if (pauseTime > maxPauseTime) + pauseTime = maxPauseTime; + final int sleepCount = (int) (pauseTime / 100); + for(int i=0;i 0) + // Force all subsequent syncs to include up through + // the final info in the current segments. This + // ensure that a call to commit() will force another + // sync (due to merge finishing) to sync all flushed + // segments as well: + lastMergeInfo = toSync.info(numSegmentsToSync-1); + + mySyncCount = syncCount++; + deleter.incRef(toSync, false); + + commitPending = newCommitPending; + } + + boolean success0 = false; + + try { + + // Loop until all files toSync references are sync'd: + while(true) { + + final Collection pending = new ArrayList(); + + for(int i=0;i syncCountSaved) { + + if (segmentInfos.getGeneration() > toSync.getGeneration()) + toSync.updateGeneration(segmentInfos); + + boolean success = false; + try { + toSync.commit(directory); + success = true; + } finally { + // Have our master segmentInfos record the + // generations we just sync'd + segmentInfos.updateGeneration(toSync); + if (!success) { + commitPending = true; + message("hit exception committing segments file"); + } + } + message("commit complete"); + + syncCountSaved = mySyncCount; + + deleter.checkpoint(toSync, true); + setRollbackSegmentInfos(); + } else + message("sync superseded by newer infos"); + } + + message("done all syncs"); + + success0 = true; + + } finally { + synchronized(this) { + deleter.decRef(toSync); + if (!success0) + commitPending = true; + } + } + } + /** * Specifies maximum field length in {@link IndexWriter} constructors. * {@link IndexWriter#setMaxFieldLength(int)} overrides the value set by diff --git a/src/java/org/apache/lucene/index/SegmentInfos.java b/src/java/org/apache/lucene/index/SegmentInfos.java index c5fade9dc29..21f12326c03 100644 --- a/src/java/org/apache/lucene/index/SegmentInfos.java +++ b/src/java/org/apache/lucene/index/SegmentInfos.java @@ -20,6 +20,8 @@ package org.apache.lucene.index; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; +import org.apache.lucene.store.ChecksumIndexOutput; +import org.apache.lucene.store.ChecksumIndexInput; import java.io.File; import java.io.FileNotFoundException; @@ -55,8 +57,12 @@ final class SegmentInfos extends Vector { * vectors and stored fields file. */ public static final int FORMAT_SHARED_DOC_STORE = -4; + /** This format adds a checksum at the end of the file to + * ensure all bytes were successfully written. */ + public static final int FORMAT_CHECKSUM = -5; + /* This must always point to the most recent file format. */ - private static final int CURRENT_FORMAT = FORMAT_SHARED_DOC_STORE; + private static final int CURRENT_FORMAT = FORMAT_CHECKSUM; public int counter = 0; // used to name new segments /** @@ -197,7 +203,7 @@ final class SegmentInfos extends Vector { // Clear any previous segments: clear(); - IndexInput input = directory.openInput(segmentFileName); + ChecksumIndexInput input = new ChecksumIndexInput(directory.openInput(segmentFileName)); generation = generationFromSegmentsFileName(segmentFileName); @@ -226,6 +232,13 @@ final class SegmentInfos extends Vector { else version = input.readLong(); // read version } + + if (format <= FORMAT_CHECKSUM) { + final long checksumNow = input.getChecksum(); + final long checksumThen = input.readLong(); + if (checksumNow != checksumThen) + throw new CorruptIndexException("checksum mismatch in segments file"); + } success = true; } finally { @@ -257,7 +270,7 @@ final class SegmentInfos extends Vector { }.run(); } - public final void write(Directory directory) throws IOException { + private final void write(Directory directory) throws IOException { String segmentFileName = getNextSegmentFileName(); @@ -268,7 +281,7 @@ final class SegmentInfos extends Vector { generation++; } - IndexOutput output = directory.createOutput(segmentFileName); + ChecksumIndexOutput output = new ChecksumIndexOutput(directory.createOutput(segmentFileName)); boolean success = false; @@ -280,29 +293,31 @@ final class SegmentInfos extends Vector { output.writeInt(size()); // write infos for (int i = 0; i < size(); i++) { info(i).write(output); - } - } - finally { + } + final long checksum = output.getChecksum(); + output.writeLong(checksum); + success = true; + } finally { + boolean success2 = false; try { output.close(); - success = true; + success2 = true; } finally { - if (!success) { + if (!success || !success2) // Try not to leave a truncated segments_N file in // the index: directory.deleteFile(segmentFileName); - } } } try { - output = directory.createOutput(IndexFileNames.SEGMENTS_GEN); + IndexOutput genOutput = directory.createOutput(IndexFileNames.SEGMENTS_GEN); try { - output.writeInt(FORMAT_LOCKLESS); - output.writeLong(generation); - output.writeLong(generation); + genOutput.writeInt(FORMAT_LOCKLESS); + genOutput.writeLong(generation); + genOutput.writeLong(generation); } finally { - output.close(); + genOutput.close(); } } catch (IOException e) { // It's OK if we fail to write this file since it's @@ -620,7 +635,7 @@ final class SegmentInfos extends Vector { retry = true; } - } else { + } else if (0 == method) { // Segment file has advanced since our last loop, so // reset retry: retry = false; @@ -701,4 +716,50 @@ final class SegmentInfos extends Vector { infos.addAll(super.subList(first, last)); return infos; } + + // Carry over generation numbers from another SegmentInfos + void updateGeneration(SegmentInfos other) { + assert other.generation > generation; + lastGeneration = other.lastGeneration; + generation = other.generation; + } + + /** Writes & syncs to the Directory dir, taking care to + * remove the segments file on exception */ + public final void commit(Directory dir) throws IOException { + boolean success = false; + try { + write(dir); + success = true; + } finally { + if (!success) { + // Must carefully compute fileName from "generation" + // since lastGeneration isn't incremented: + final String segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, + "", + generation); + dir.deleteFile(segmentFileName); + } + } + + // NOTE: if we crash here, we have left a segments_N + // file in the directory in a possibly corrupt state (if + // some bytes made it to stable storage and others + // didn't). But, the segments_N file now includes + // checksum at the end, which should catch this case. + // So when a reader tries to read it, it will throw a + // CorruptIndexException, which should cause the retry + // logic in SegmentInfos to kick in and load the last + // good (previous) segments_N-1 file. + + final String fileName = getCurrentSegmentFileName(); + success = false; + try { + dir.sync(fileName); + success = true; + } finally { + if (!success) + dir.deleteFile(fileName); + } + } } diff --git a/src/java/org/apache/lucene/store/ChecksumIndexInput.java b/src/java/org/apache/lucene/store/ChecksumIndexInput.java new file mode 100644 index 00000000000..e90f6a6d50a --- /dev/null +++ b/src/java/org/apache/lucene/store/ChecksumIndexInput.java @@ -0,0 +1,67 @@ +package org.apache.lucene.store; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.IOException; +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +/** Writes bytes through to a primary IndexOutput, computing + * checksum as it goes. Note that you cannot use seek(). */ +public class ChecksumIndexInput extends IndexInput { + IndexInput main; + Checksum digest; + + public ChecksumIndexInput(IndexInput main) { + this.main = main; + digest = new CRC32(); + } + + public byte readByte() throws IOException { + final byte b = main.readByte(); + digest.update(b); + return b; + } + + public void readBytes(byte[] b, int offset, int len) + throws IOException { + main.readBytes(b, offset, len); + digest.update(b, offset, len); + } + + + public long getChecksum() { + return digest.getValue(); + } + + public void close() throws IOException { + main.close(); + } + + public long getFilePointer() { + return main.getFilePointer(); + } + + public void seek(long pos) { + throw new RuntimeException("not allowed"); + } + + public long length() { + return main.length(); + } +} diff --git a/src/java/org/apache/lucene/store/ChecksumIndexOutput.java b/src/java/org/apache/lucene/store/ChecksumIndexOutput.java new file mode 100644 index 00000000000..9b2562b8699 --- /dev/null +++ b/src/java/org/apache/lucene/store/ChecksumIndexOutput.java @@ -0,0 +1,68 @@ +package org.apache.lucene.store; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.IOException; +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +/** Writes bytes through to a primary IndexOutput, computing + * checksum. Note that you cannot use seek().*/ +public class ChecksumIndexOutput extends IndexOutput { + IndexOutput main; + Checksum digest; + + public ChecksumIndexOutput(IndexOutput main) { + this.main = main; + digest = new CRC32(); + } + + public void writeByte(byte b) throws IOException { + digest.update(b); + main.writeByte(b); + } + + public void writeBytes(byte[] b, int offset, int length) throws IOException { + digest.update(b, offset, length); + main.writeBytes(b, offset, length); + } + + public long getChecksum() { + return digest.getValue(); + } + + public void flush() throws IOException { + main.flush(); + } + + public void close() throws IOException { + main.close(); + } + + public long getFilePointer() { + return main.getFilePointer(); + } + + public void seek(long pos) { + throw new RuntimeException("not allowed"); + } + + public long length() throws IOException { + return main.length(); + } +} diff --git a/src/java/org/apache/lucene/store/Directory.java b/src/java/org/apache/lucene/store/Directory.java index fd715e5a48a..d28151bb6c7 100644 --- a/src/java/org/apache/lucene/store/Directory.java +++ b/src/java/org/apache/lucene/store/Directory.java @@ -83,6 +83,11 @@ public abstract class Directory { Returns a stream writing this file. */ public abstract IndexOutput createOutput(String name) throws IOException; + /** Ensure that any writes to this file are moved to + * stable storage. Lucene uses this to properly commit + * changes to the index, to prevent a machine/OS crash + * from corrupting the index. */ + public void sync(String name) throws IOException {} /** Returns a stream reading an existing file. */ public abstract IndexInput openInput(String name) diff --git a/src/java/org/apache/lucene/store/FSDirectory.java b/src/java/org/apache/lucene/store/FSDirectory.java index e06216cf4a7..dc44c2020ae 100644 --- a/src/java/org/apache/lucene/store/FSDirectory.java +++ b/src/java/org/apache/lucene/store/FSDirectory.java @@ -435,6 +435,39 @@ public class FSDirectory extends Directory { return new FSIndexOutput(file); } + public void sync(String name) throws IOException { + File fullFile = new File(directory, name); + boolean success = false; + int retryCount = 0; + IOException exc = null; + while(!success && retryCount < 5) { + retryCount++; + RandomAccessFile file = null; + try { + try { + file = new RandomAccessFile(fullFile, "rw"); + file.getFD().sync(); + success = true; + } finally { + if (file != null) + file.close(); + } + } catch (IOException ioe) { + if (exc == null) + exc = ioe; + try { + // Pause 5 msec + Thread.sleep(5); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + } + if (!success) + // Throw original exception + throw exc; + } + // Inherit javadoc public IndexInput openInput(String name) throws IOException { return openInput(name, BufferedIndexInput.BUFFER_SIZE); diff --git a/src/site/src/documentation/content/xdocs/fileformats.xml b/src/site/src/documentation/content/xdocs/fileformats.xml index 2fadcb7ddf9..a776abf36eb 100644 --- a/src/site/src/documentation/content/xdocs/fileformats.xml +++ b/src/site/src/documentation/content/xdocs/fileformats.xml @@ -819,18 +819,24 @@ IsCompoundFile>SegCount

- 2.3 and above: + 2.3: Segments --> Format, Version, NameCounter, SegCount, <SegName, SegSize, DelGen, DocStoreOffset, [DocStoreSegment, DocStoreIsCompoundFile], HasSingleNormFile, NumField, NormGenNumField, IsCompoundFile>SegCount

+

+ 2.4 and above: + Segments --> Format, Version, NameCounter, SegCount, <SegName, SegSize, DelGen, DocStoreOffset, [DocStoreSegment, DocStoreIsCompoundFile], HasSingleNormFile, NumField, + NormGenNumField, + IsCompoundFile>SegCount, Checksum +

Format, NameCounter, SegCount, SegSize, NumField, DocStoreOffset --> Int32

- Version, DelGen, NormGen --> Int64 + Version, DelGen, NormGen, Checksum --> Int64

@@ -842,7 +848,7 @@

- Format is -1 as of Lucene 1.4, -3 (SegmentInfos.FORMAT_SINGLE_NORM_FILE) as of Lucene 2.1 and 2.2, and -4 (SegmentInfos.FORMAT_SHARED_DOC_STORE) as of Lucene 2.3 + Format is -1 as of Lucene 1.4, -3 (SegmentInfos.FORMAT_SINGLE_NORM_FILE) as of Lucene 2.1 and 2.2, -4 (SegmentInfos.FORMAT_SHARED_DOC_STORE) as of Lucene 2.3 and -5 (SegmentInfos.FORMAT_CHECKSUM) as of Lucene 2.4.

@@ -925,6 +931,13 @@ shares a single set of these files with other segments.

+ +

+ Checksum contains the CRC32 checksum of all bytes + in the segments_N file up until the checksum. + This is used to verify integrity of the file on + opening the index. +

diff --git a/src/test/org/apache/lucene/index/TestAtomicUpdate.java b/src/test/org/apache/lucene/index/TestAtomicUpdate.java index 27db24cc6e1..1693eb62af2 100644 --- a/src/test/org/apache/lucene/index/TestAtomicUpdate.java +++ b/src/test/org/apache/lucene/index/TestAtomicUpdate.java @@ -20,12 +20,8 @@ import org.apache.lucene.util.*; import org.apache.lucene.store.*; import org.apache.lucene.document.*; import org.apache.lucene.analysis.*; -import org.apache.lucene.index.*; import org.apache.lucene.search.*; import org.apache.lucene.queryParser.*; -import org.apache.lucene.util._TestUtil; - -import org.apache.lucene.util.LuceneTestCase; import java.util.Random; import java.io.File; @@ -83,7 +79,6 @@ public class TestAtomicUpdate extends LuceneTestCase { // Update all 100 docs... for(int i=0; i<100; i++) { Document d = new Document(); - int n = RANDOM.nextInt(); d.add(new Field("id", Integer.toString(i), Field.Store.YES, Field.Index.UN_TOKENIZED)); d.add(new Field("contents", English.intToEnglish(i+10*count), Field.Store.NO, Field.Index.TOKENIZED)); writer.updateDocument(new Term("id", Integer.toString(i)), d); @@ -127,7 +122,7 @@ public class TestAtomicUpdate extends LuceneTestCase { d.add(new Field("contents", English.intToEnglish(i), Field.Store.NO, Field.Index.TOKENIZED)); writer.addDocument(d); } - writer.flush(); + writer.commit(); IndexerThread indexerThread = new IndexerThread(writer, threads); threads[0] = indexerThread; diff --git a/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java b/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java index f64ad651277..ad4309f3e83 100644 --- a/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java +++ b/src/test/org/apache/lucene/index/TestBackwardsCompatibility.java @@ -349,7 +349,6 @@ public class TestBackwardsCompatibility extends LuceneTestCase IndexWriter writer = new IndexWriter(dir, autoCommit, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED); writer.setRAMBufferSizeMB(16.0); - //IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer(), true); for(int i=0;i<35;i++) { addDoc(writer, i); } @@ -390,12 +389,9 @@ public class TestBackwardsCompatibility extends LuceneTestCase expected = new String[] {"_0.cfs", "_0_1.del", "_0_1.s" + contentFieldIndex, - "segments_4", + "segments_3", "segments.gen"}; - if (!autoCommit) - expected[3] = "segments_3"; - String[] actual = dir.list(); Arrays.sort(expected); Arrays.sort(actual); diff --git a/src/test/org/apache/lucene/index/TestCrash.java b/src/test/org/apache/lucene/index/TestCrash.java new file mode 100644 index 00000000000..1dd41953f9c --- /dev/null +++ b/src/test/org/apache/lucene/index/TestCrash.java @@ -0,0 +1,181 @@ +package org.apache.lucene.index; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.IOException; + +import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.analysis.WhitespaceAnalyzer; +import org.apache.lucene.store.MockRAMDirectory; +import org.apache.lucene.store.NoLockFactory; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; + +public class TestCrash extends LuceneTestCase { + + private IndexWriter initIndex() throws IOException { + return initIndex(new MockRAMDirectory()); + } + + private IndexWriter initIndex(MockRAMDirectory dir) throws IOException { + dir.setLockFactory(NoLockFactory.getNoLockFactory()); + + IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer()); + //writer.setMaxBufferedDocs(2); + writer.setMaxBufferedDocs(10); + ((ConcurrentMergeScheduler) writer.getMergeScheduler()).setSuppressExceptions(); + + Document doc = new Document(); + doc.add(new Field("content", "aaa", Field.Store.YES, Field.Index.TOKENIZED)); + doc.add(new Field("id", "0", Field.Store.YES, Field.Index.TOKENIZED)); + for(int i=0;i<157;i++) + writer.addDocument(doc); + + return writer; + } + + private void crash(final IndexWriter writer) throws IOException { + final MockRAMDirectory dir = (MockRAMDirectory) writer.getDirectory(); + ConcurrentMergeScheduler cms = (ConcurrentMergeScheduler) writer.getMergeScheduler(); + dir.crash(); + cms.sync(); + dir.clearCrash(); + } + + public void testCrashWhileIndexing() throws IOException { + IndexWriter writer = initIndex(); + MockRAMDirectory dir = (MockRAMDirectory) writer.getDirectory(); + crash(writer); + IndexReader reader = IndexReader.open(dir); + assertTrue(reader.numDocs() < 157); + } + + public void testWriterAfterCrash() throws IOException { + IndexWriter writer = initIndex(); + MockRAMDirectory dir = (MockRAMDirectory) writer.getDirectory(); + dir.setPreventDoubleWrite(false); + crash(writer); + writer = initIndex(dir); + writer.close(); + + IndexReader reader = IndexReader.open(dir); + assertTrue(reader.numDocs() < 314); + } + + public void testCrashAfterReopen() throws IOException { + IndexWriter writer = initIndex(); + MockRAMDirectory dir = (MockRAMDirectory) writer.getDirectory(); + writer.close(); + writer = initIndex(dir); + assertEquals(314, writer.docCount()); + crash(writer); + + /* + System.out.println("\n\nTEST: open reader"); + String[] l = dir.list(); + Arrays.sort(l); + for(int i=0;i= 157); + } + + public void testCrashAfterClose() throws IOException { + + IndexWriter writer = initIndex(); + MockRAMDirectory dir = (MockRAMDirectory) writer.getDirectory(); + + writer.close(); + dir.crash(); + + /* + String[] l = dir.list(); + Arrays.sort(l); + for(int i=0;i 2); - } else { + if (!autoCommit) // If we are not auto committing then there should // be exactly 2 commits (one per close above): assertEquals(2, policy.numOnCommit); - } // Simplistic check: just verify all segments_N's still // exist, and, I can open a reader on each: @@ -334,13 +331,10 @@ public class TestDeletionPolicy extends LuceneTestCase writer.close(); assertEquals(2, policy.numOnInit); - if (autoCommit) { - assertTrue(policy.numOnCommit > 2); - } else { + if (!autoCommit) // If we are not auto committing then there should // be exactly 2 commits (one per close above): assertEquals(2, policy.numOnCommit); - } // Simplistic check: just verify the index is in fact // readable: @@ -459,11 +453,8 @@ public class TestDeletionPolicy extends LuceneTestCase writer.close(); assertEquals(2*(N+2), policy.numOnInit); - if (autoCommit) { - assertTrue(policy.numOnCommit > 2*(N+2)-1); - } else { + if (!autoCommit) assertEquals(2*(N+2)-1, policy.numOnCommit); - } IndexSearcher searcher = new IndexSearcher(dir); Hits hits = searcher.search(query); @@ -565,11 +556,8 @@ public class TestDeletionPolicy extends LuceneTestCase } assertEquals(1+3*(N+1), policy.numOnInit); - if (autoCommit) { - assertTrue(policy.numOnCommit > 3*(N+1)-1); - } else { + if (!autoCommit) assertEquals(2*(N+1), policy.numOnCommit); - } IndexSearcher searcher = new IndexSearcher(dir); Hits hits = searcher.search(query); diff --git a/src/test/org/apache/lucene/index/TestIndexFileDeleter.java b/src/test/org/apache/lucene/index/TestIndexFileDeleter.java index 7ff3fc44d19..b7beb19e1a0 100644 --- a/src/test/org/apache/lucene/index/TestIndexFileDeleter.java +++ b/src/test/org/apache/lucene/index/TestIndexFileDeleter.java @@ -18,17 +18,8 @@ package org.apache.lucene.index; */ import org.apache.lucene.util.LuceneTestCase; -import java.util.Vector; -import java.util.Arrays; -import java.io.ByteArrayOutputStream; -import java.io.ObjectOutputStream; -import java.io.IOException; -import java.io.File; import org.apache.lucene.analysis.WhitespaceAnalyzer; -import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.TermQuery; -import org.apache.lucene.search.Hits; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; @@ -77,8 +68,8 @@ public class TestIndexFileDeleter extends LuceneTestCase String[] files = dir.list(); /* - for(int i=0;i lastGen); - lastGen = gen; + assertTrue(flushCount > lastFlushCount); + lastFlushCount = flushCount; writer.setRAMBufferSizeMB(0.000001); writer.setMaxBufferedDocs(IndexWriter.DISABLE_AUTO_FLUSH); } else if (j < 20) { - assertTrue(gen > lastGen); - lastGen = gen; + assertTrue(flushCount > lastFlushCount); + lastFlushCount = flushCount; } else if (20 == j) { writer.setRAMBufferSizeMB(16); writer.setMaxBufferedDocs(IndexWriter.DISABLE_AUTO_FLUSH); - lastGen = gen; + lastFlushCount = flushCount; } else if (j < 30) { - assertEquals(gen, lastGen); + assertEquals(flushCount, lastFlushCount); } else if (30 == j) { writer.setRAMBufferSizeMB(0.000001); writer.setMaxBufferedDocs(IndexWriter.DISABLE_AUTO_FLUSH); } else if (j < 40) { - assertTrue(gen> lastGen); - lastGen = gen; + assertTrue(flushCount> lastFlushCount); + lastFlushCount = flushCount; } else if (40 == j) { writer.setMaxBufferedDocs(10); writer.setRAMBufferSizeMB(IndexWriter.DISABLE_AUTO_FLUSH); - lastGen = gen; + lastFlushCount = flushCount; } else if (j < 50) { - assertEquals(gen, lastGen); + assertEquals(flushCount, lastFlushCount); writer.setMaxBufferedDocs(10); writer.setRAMBufferSizeMB(IndexWriter.DISABLE_AUTO_FLUSH); } else if (50 == j) { - assertTrue(gen > lastGen); + assertTrue(flushCount > lastFlushCount); } } writer.close(); @@ -1334,46 +1339,46 @@ public class TestIndexWriter extends LuceneTestCase writer.addDocument(doc); } - long lastGen = -1; + int lastFlushCount = -1; for(int j=1;j<52;j++) { writer.deleteDocuments(new Term("field", "aaa" + j)); _TestUtil.syncConcurrentMerges(writer); - long gen = SegmentInfos.generationFromSegmentsFileName(SegmentInfos.getCurrentSegmentFileName(dir.list())); + int flushCount = writer.getFlushCount(); if (j == 1) - lastGen = gen; + lastFlushCount = flushCount; else if (j < 10) { // No new files should be created - assertEquals(gen, lastGen); + assertEquals(flushCount, lastFlushCount); } else if (10 == j) { - assertTrue(gen > lastGen); - lastGen = gen; + assertTrue(flushCount > lastFlushCount); + lastFlushCount = flushCount; writer.setRAMBufferSizeMB(0.000001); writer.setMaxBufferedDeleteTerms(IndexWriter.DISABLE_AUTO_FLUSH); } else if (j < 20) { - assertTrue(gen > lastGen); - lastGen = gen; + assertTrue(flushCount > lastFlushCount); + lastFlushCount = flushCount; } else if (20 == j) { writer.setRAMBufferSizeMB(16); writer.setMaxBufferedDeleteTerms(IndexWriter.DISABLE_AUTO_FLUSH); - lastGen = gen; + lastFlushCount = flushCount; } else if (j < 30) { - assertEquals(gen, lastGen); + assertEquals(flushCount, lastFlushCount); } else if (30 == j) { writer.setRAMBufferSizeMB(0.000001); writer.setMaxBufferedDeleteTerms(IndexWriter.DISABLE_AUTO_FLUSH); } else if (j < 40) { - assertTrue(gen> lastGen); - lastGen = gen; + assertTrue(flushCount> lastFlushCount); + lastFlushCount = flushCount; } else if (40 == j) { writer.setMaxBufferedDeleteTerms(10); writer.setRAMBufferSizeMB(IndexWriter.DISABLE_AUTO_FLUSH); - lastGen = gen; + lastFlushCount = flushCount; } else if (j < 50) { - assertEquals(gen, lastGen); + assertEquals(flushCount, lastFlushCount); writer.setMaxBufferedDeleteTerms(10); writer.setRAMBufferSizeMB(IndexWriter.DISABLE_AUTO_FLUSH); } else if (50 == j) { - assertTrue(gen > lastGen); + assertTrue(flushCount > lastFlushCount); } } writer.close(); @@ -1831,11 +1836,18 @@ public class TestIndexWriter extends LuceneTestCase public void eval(MockRAMDirectory dir) throws IOException { if (doFail) { StackTraceElement[] trace = new Exception().getStackTrace(); + boolean sawAppend = false; + boolean sawFlush = false; for (int i = 0; i < trace.length; i++) { - if ("org.apache.lucene.index.DocumentsWriter".equals(trace[i].getClassName()) && "appendPostings".equals(trace[i].getMethodName()) && count++ == 30) { - doFail = false; - throw new IOException("now failing during flush"); - } + if ("org.apache.lucene.index.DocumentsWriter".equals(trace[i].getClassName()) && "appendPostings".equals(trace[i].getMethodName())) + sawAppend = true; + if ("doFlush".equals(trace[i].getMethodName())) + sawFlush = true; + } + + if (sawAppend && sawFlush && count++ >= 30) { + doFail = false; + throw new IOException("now failing during flush"); } } } @@ -2263,6 +2275,7 @@ public class TestIndexWriter extends LuceneTestCase try { writer.updateDocument(new Term("id", ""+(idUpto++)), doc); } catch (IOException ioe) { + //ioe.printStackTrace(System.out); if (ioe.getMessage().startsWith("fake disk full at") || ioe.getMessage().equals("now failing on purpose")) { diskFull = true; @@ -2282,6 +2295,7 @@ public class TestIndexWriter extends LuceneTestCase break; } } catch (Throwable t) { + //t.printStackTrace(System.out); if (noErrors) { System.out.println(Thread.currentThread().getName() + ": ERROR: unexpected Throwable:"); t.printStackTrace(System.out); @@ -2300,7 +2314,7 @@ public class TestIndexWriter extends LuceneTestCase public void testCloseWithThreads() throws IOException { int NUM_THREADS = 3; - for(int iter=0;iter<50;iter++) { + for(int iter=0;iter<20;iter++) { MockRAMDirectory dir = new MockRAMDirectory(); IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED); ConcurrentMergeScheduler cms = new ConcurrentMergeScheduler(); @@ -2310,7 +2324,6 @@ public class TestIndexWriter extends LuceneTestCase writer.setMergeFactor(4); IndexerThread[] threads = new IndexerThread[NUM_THREADS]; - boolean diskFull = false; for(int i=0;i 1 but got " + gen, gen > 1); + + final String segmentsFileName = SegmentInfos.getCurrentSegmentFileName(dir); + IndexInput in = dir.openInput(segmentsFileName); + IndexOutput out = dir.createOutput(IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", 1+gen)); + out.copyBytes(in, in.length()-1); + byte b = in.readByte(); + out.writeByte((byte) (1+b)); + out.close(); + in.close(); + + IndexReader reader = null; + try { + reader = IndexReader.open(dir); + } catch (IOException e) { + e.printStackTrace(System.out); + fail("segmentInfos failed to retry fallback to correct segments_N file"); + } + reader.close(); + } + + // LUCENE-1044: test writer.commit() when ac=false + public void testForceCommit() throws IOException { + Directory dir = new MockRAMDirectory(); + + IndexWriter writer = new IndexWriter(dir, false, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED); + writer.setMaxBufferedDocs(2); + writer.setMergeFactor(5); + + for (int i = 0; i < 23; i++) + addDoc(writer); + + IndexReader reader = IndexReader.open(dir); + assertEquals(0, reader.numDocs()); + writer.commit(); + IndexReader reader2 = reader.reopen(); + assertEquals(0, reader.numDocs()); + assertEquals(23, reader2.numDocs()); + reader.close(); + + for (int i = 0; i < 17; i++) + addDoc(writer); + assertEquals(23, reader2.numDocs()); + reader2.close(); + reader = IndexReader.open(dir); + assertEquals(23, reader.numDocs()); + reader.close(); + writer.commit(); + + reader = IndexReader.open(dir); + assertEquals(40, reader.numDocs()); + reader.close(); + writer.close(); + dir.close(); + } + + // Throws IOException during MockRAMDirectory.sync + private static class FailOnlyInSync extends MockRAMDirectory.Failure { + boolean didFail; + public void eval(MockRAMDirectory dir) throws IOException { + if (doFail) { + StackTraceElement[] trace = new Exception().getStackTrace(); + for (int i = 0; i < trace.length; i++) { + if (doFail && "org.apache.lucene.store.MockRAMDirectory".equals(trace[i].getClassName()) && "sync".equals(trace[i].getMethodName())) { + didFail = true; + throw new IOException("now failing on purpose during sync"); + } + } + } + } + } + + // LUCENE-1044: test exception during sync + public void testExceptionDuringSync() throws IOException { + MockRAMDirectory dir = new MockRAMDirectory(); + FailOnlyInSync failure = new FailOnlyInSync(); + dir.failOn(failure); + + IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED); + failure.setDoFail(); + + ConcurrentMergeScheduler cms = new ConcurrentMergeScheduler(); + // We expect sync exceptions in the merge threads + cms.setSuppressExceptions(); + writer.setMergeScheduler(cms); + writer.setMaxBufferedDocs(2); + writer.setMergeFactor(5); + + for (int i = 0; i < 23; i++) + addDoc(writer); + + cms.sync(); + assertTrue(failure.didFail); + failure.clearDoFail(); + writer.close(); + + IndexReader reader = IndexReader.open(dir); + assertEquals(23, reader.numDocs()); + reader.close(); + dir.close(); + } + // LUCENE-1168 public void testTermVectorCorruption() throws IOException { diff --git a/src/test/org/apache/lucene/index/TestIndexWriterDelete.java b/src/test/org/apache/lucene/index/TestIndexWriterDelete.java index 18d190f4ad8..7366ce703af 100644 --- a/src/test/org/apache/lucene/index/TestIndexWriterDelete.java +++ b/src/test/org/apache/lucene/index/TestIndexWriterDelete.java @@ -30,7 +30,6 @@ import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; import org.apache.lucene.store.MockRAMDirectory; -import org.apache.lucene.store.RAMDirectory; public class TestIndexWriterDelete extends LuceneTestCase { @@ -45,7 +44,7 @@ public class TestIndexWriterDelete extends LuceneTestCase { for(int pass=0;pass<2;pass++) { boolean autoCommit = (0==pass); - Directory dir = new RAMDirectory(); + Directory dir = new MockRAMDirectory(); IndexWriter modifier = new IndexWriter(dir, autoCommit, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED); modifier.setUseCompoundFile(true); @@ -65,28 +64,17 @@ public class TestIndexWriterDelete extends LuceneTestCase { modifier.addDocument(doc); } modifier.optimize(); - - if (!autoCommit) { - modifier.close(); - } + modifier.commit(); Term term = new Term("city", "Amsterdam"); int hitCount = getHitCount(dir, term); assertEquals(1, hitCount); - if (!autoCommit) { - modifier = new IndexWriter(dir, autoCommit, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED); - modifier.setUseCompoundFile(true); - } modifier.deleteDocuments(term); - if (!autoCommit) { - modifier.close(); - } + modifier.commit(); hitCount = getHitCount(dir, term); assertEquals(0, hitCount); - if (autoCommit) { - modifier.close(); - } + modifier.close(); dir.close(); } } @@ -96,7 +84,7 @@ public class TestIndexWriterDelete extends LuceneTestCase { for(int pass=0;pass<2;pass++) { boolean autoCommit = (0==pass); - Directory dir = new RAMDirectory(); + Directory dir = new MockRAMDirectory(); IndexWriter modifier = new IndexWriter(dir, autoCommit, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED); modifier.setMaxBufferedDocs(2); @@ -108,38 +96,26 @@ public class TestIndexWriterDelete extends LuceneTestCase { for (int i = 0; i < 7; i++) { addDoc(modifier, ++id, value); } - modifier.flush(); + modifier.commit(); assertEquals(0, modifier.getNumBufferedDocuments()); assertTrue(0 < modifier.getSegmentCount()); - if (!autoCommit) { - modifier.close(); - } + modifier.commit(); IndexReader reader = IndexReader.open(dir); assertEquals(7, reader.numDocs()); reader.close(); - if (!autoCommit) { - modifier = new IndexWriter(dir, autoCommit, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED); - modifier.setMaxBufferedDocs(2); - modifier.setMaxBufferedDeleteTerms(2); - } - modifier.deleteDocuments(new Term("value", String.valueOf(value))); modifier.deleteDocuments(new Term("value", String.valueOf(value))); - if (!autoCommit) { - modifier.close(); - } + modifier.commit(); reader = IndexReader.open(dir); assertEquals(0, reader.numDocs()); reader.close(); - if (autoCommit) { - modifier.close(); - } + modifier.close(); dir.close(); } } @@ -148,7 +124,7 @@ public class TestIndexWriterDelete extends LuceneTestCase { public void testRAMDeletes() throws IOException { for(int pass=0;pass<2;pass++) { boolean autoCommit = (0==pass); - Directory dir = new RAMDirectory(); + Directory dir = new MockRAMDirectory(); IndexWriter modifier = new IndexWriter(dir, autoCommit, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED); modifier.setMaxBufferedDocs(4); @@ -169,9 +145,7 @@ public class TestIndexWriterDelete extends LuceneTestCase { assertEquals(0, modifier.getSegmentCount()); modifier.flush(); - if (!autoCommit) { - modifier.close(); - } + modifier.commit(); IndexReader reader = IndexReader.open(dir); assertEquals(1, reader.numDocs()); @@ -179,9 +153,7 @@ public class TestIndexWriterDelete extends LuceneTestCase { int hitCount = getHitCount(dir, new Term("id", String.valueOf(id))); assertEquals(1, hitCount); reader.close(); - if (autoCommit) { - modifier.close(); - } + modifier.close(); dir.close(); } } @@ -191,7 +163,7 @@ public class TestIndexWriterDelete extends LuceneTestCase { for(int pass=0;pass<2;pass++) { boolean autoCommit = (0==pass); - Directory dir = new RAMDirectory(); + Directory dir = new MockRAMDirectory(); IndexWriter modifier = new IndexWriter(dir, autoCommit, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED); modifier.setMaxBufferedDocs(100); @@ -208,23 +180,18 @@ public class TestIndexWriterDelete extends LuceneTestCase { for (int i = 0; i < 5; i++) { addDoc(modifier, ++id, value); } - modifier.flush(); + modifier.commit(); for (int i = 0; i < 5; i++) { addDoc(modifier, ++id, value); } modifier.deleteDocuments(new Term("value", String.valueOf(value))); - modifier.flush(); - if (!autoCommit) { - modifier.close(); - } + modifier.commit(); IndexReader reader = IndexReader.open(dir); assertEquals(5, reader.numDocs()); - if (autoCommit) { - modifier.close(); - } + modifier.close(); } } @@ -232,7 +199,7 @@ public class TestIndexWriterDelete extends LuceneTestCase { public void testBatchDeletes() throws IOException { for(int pass=0;pass<2;pass++) { boolean autoCommit = (0==pass); - Directory dir = new RAMDirectory(); + Directory dir = new MockRAMDirectory(); IndexWriter modifier = new IndexWriter(dir, autoCommit, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED); modifier.setMaxBufferedDocs(2); @@ -244,29 +211,17 @@ public class TestIndexWriterDelete extends LuceneTestCase { for (int i = 0; i < 7; i++) { addDoc(modifier, ++id, value); } - modifier.flush(); - if (!autoCommit) { - modifier.close(); - } + modifier.commit(); IndexReader reader = IndexReader.open(dir); assertEquals(7, reader.numDocs()); reader.close(); - if (!autoCommit) { - modifier = new IndexWriter(dir, autoCommit, - new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED); - modifier.setMaxBufferedDocs(2); - modifier.setMaxBufferedDeleteTerms(2); - } - id = 0; modifier.deleteDocuments(new Term("id", String.valueOf(++id))); modifier.deleteDocuments(new Term("id", String.valueOf(++id))); - if (!autoCommit) { - modifier.close(); - } + modifier.commit(); reader = IndexReader.open(dir); assertEquals(5, reader.numDocs()); @@ -276,23 +231,13 @@ public class TestIndexWriterDelete extends LuceneTestCase { for (int i = 0; i < terms.length; i++) { terms[i] = new Term("id", String.valueOf(++id)); } - if (!autoCommit) { - modifier = new IndexWriter(dir, autoCommit, - new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED); - modifier.setMaxBufferedDocs(2); - modifier.setMaxBufferedDeleteTerms(2); - } modifier.deleteDocuments(terms); - if (!autoCommit) { - modifier.close(); - } + modifier.commit(); reader = IndexReader.open(dir); assertEquals(2, reader.numDocs()); reader.close(); - if (autoCommit) { - modifier.close(); - } + modifier.close(); dir.close(); } } @@ -338,7 +283,7 @@ public class TestIndexWriterDelete extends LuceneTestCase { boolean autoCommit = (0==pass); // First build up a starting index: - RAMDirectory startDir = new RAMDirectory(); + MockRAMDirectory startDir = new MockRAMDirectory(); IndexWriter writer = new IndexWriter(startDir, autoCommit, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED); for (int i = 0; i < 157; i++) { @@ -444,38 +389,10 @@ public class TestIndexWriterDelete extends LuceneTestCase { } } - // Whether we succeeded or failed, check that all - // un-referenced files were in fact deleted (ie, - // we did not create garbage). Just create a - // new IndexFileDeleter, have it delete - // unreferenced files, then verify that in fact - // no files were deleted: - String[] startFiles = dir.list(); - SegmentInfos infos = new SegmentInfos(); - infos.read(dir); - new IndexFileDeleter(dir, new KeepOnlyLastCommitDeletionPolicy(), infos, null, null); - String[] endFiles = dir.list(); - - Arrays.sort(startFiles); - Arrays.sort(endFiles); - - // for(int i=0;i