From e446904c6147fc188b2c77bb22e08bd59586fb01 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Thu, 29 Feb 2024 19:38:37 +0100 Subject: [PATCH] Remove ByteBufferIndexInput and update all Panama implementations (MMap and Vector) to Java 21 (#13146) --- gradle/generation/extract-jdk-apis.gradle | 4 +- .../extract-jdk-apis/ExtractJdkApis.java | 10 +- gradle/testing/defaults-tests.gradle | 2 +- .../policies/replicator-tests.policy | 3 - .../randomization/policies/tests.policy | 3 - .../apache/lucene/core/tests/TestMMap.java | 34 - lucene/core/src/generated/jdk/jdk19.apijar | Bin 18462 -> 0 bytes lucene/core/src/generated/jdk/jdk20.apijar | Bin 58047 -> 0 bytes lucene/core/src/generated/jdk/jdk21.apijar | Bin 16543 -> 57069 bytes lucene/core/src/java/module-info.java | 1 - .../org/apache/lucene/index/IndexWriter.java | 7 - .../vectorization/VectorizationProvider.java | 34 +- .../apache/lucene/store/ByteBufferGuard.java | 156 ---- .../lucene/store/ByteBufferIndexInput.java | 732 ------------------ .../lucene/store/ByteBuffersDirectory.java | 41 - .../org/apache/lucene/store/FSDirectory.java | 34 +- .../apache/lucene/store/MMapDirectory.java | 153 +--- .../MappedByteBufferIndexInputProvider.java | 205 ----- .../lucene/store/NRTCachingDirectory.java | 2 +- .../lucene/store/MemorySegmentIndexInput.java | 683 ---------------- .../MemorySegmentIndexInputProvider.java | 120 --- .../lucene/store/MemorySegmentIndexInput.java | 681 ---------------- .../MemorySegmentIndexInputProvider.java | 120 --- .../PanamaVectorUtilSupport.java | 0 .../PanamaVectorizationProvider.java | 0 .../MemorySegmentIndexInputProvider.java | 19 - .../lucene/util/VectorUtilPanamaProvider.txt | 2 - .../lucene/index/Test4GBStoredFields.java | 2 - .../apache/lucene/index/TestIndexWriter.java | 1 - .../store/TestByteBuffersDirectory.java | 9 - .../apache/lucene/store/TestDirectory.java | 7 +- .../lucene/store/TestMMapDirectory.java | 25 - .../apache/lucene/store/TestMultiMMap.java | 6 - .../org/apache/lucene/util/fst/Test2BFST.java | 2 - .../lucene/util/fst/Test2BFSTOffHeap.java | 2 - .../lucene/distribution/TestModularLayer.java | 11 +- .../expressions/js/JavascriptCompiler.java | 2 +- .../search/suggest/document/NRTSuggester.java | 4 +- .../index/BaseStoredFieldsFormatTestCase.java | 2 - .../lucene/tests/util/LuceneTestCase.java | 26 +- 40 files changed, 60 insertions(+), 3085 deletions(-) delete mode 100644 lucene/core.tests/src/test/org/apache/lucene/core/tests/TestMMap.java delete mode 100644 lucene/core/src/generated/jdk/jdk19.apijar delete mode 100644 lucene/core/src/generated/jdk/jdk20.apijar delete mode 100644 lucene/core/src/java/org/apache/lucene/store/ByteBufferGuard.java delete mode 100644 lucene/core/src/java/org/apache/lucene/store/ByteBufferIndexInput.java delete mode 100644 lucene/core/src/java/org/apache/lucene/store/MappedByteBufferIndexInputProvider.java delete mode 100644 lucene/core/src/java19/org/apache/lucene/store/MemorySegmentIndexInput.java delete mode 100644 lucene/core/src/java19/org/apache/lucene/store/MemorySegmentIndexInputProvider.java delete mode 100644 lucene/core/src/java20/org/apache/lucene/store/MemorySegmentIndexInput.java delete mode 100644 lucene/core/src/java20/org/apache/lucene/store/MemorySegmentIndexInputProvider.java rename lucene/core/src/{java20 => java21}/org/apache/lucene/internal/vectorization/PanamaVectorUtilSupport.java (100%) rename lucene/core/src/{java20 => java21}/org/apache/lucene/internal/vectorization/PanamaVectorizationProvider.java (100%) delete mode 100644 lucene/core/src/java21/org/apache/lucene/util/VectorUtilPanamaProvider.txt diff --git a/gradle/generation/extract-jdk-apis.gradle b/gradle/generation/extract-jdk-apis.gradle index 8ebdd6b1e330..1371fe9d3919 100644 --- a/gradle/generation/extract-jdk-apis.gradle +++ b/gradle/generation/extract-jdk-apis.gradle @@ -20,14 +20,14 @@ def resources = scriptResources(buildscript) configure(rootProject) { ext { // also change this in extractor tool: ExtractForeignAPI - vectorIncubatorJavaVersions = [ JavaVersion.VERSION_20, JavaVersion.VERSION_21, JavaVersion.VERSION_22 ] as Set + vectorIncubatorJavaVersions = [ JavaVersion.VERSION_21, JavaVersion.VERSION_22 ] as Set } } configure(project(":lucene:core")) { ext { apijars = layout.projectDirectory.dir("src/generated/jdk") - mrjarJavaVersions = [ 19, 20, 21 ] + mrjarJavaVersions = [ 21 ] } configurations { diff --git a/gradle/generation/extract-jdk-apis/ExtractJdkApis.java b/gradle/generation/extract-jdk-apis/ExtractJdkApis.java index 82f43c1336fc..c84c8f16996d 100644 --- a/gradle/generation/extract-jdk-apis/ExtractJdkApis.java +++ b/gradle/generation/extract-jdk-apis/ExtractJdkApis.java @@ -54,9 +54,7 @@ public final class ExtractJdkApis { private static final String PATTERN_VECTOR_VM_INTERNALS = "java.base/jdk/internal/vm/vector/VectorSupport{,$Vector,$VectorMask,$VectorPayload,$VectorShuffle}"; static final Map> CLASSFILE_PATTERNS = Map.of( - 19, List.of(PATTERN_PANAMA_FOREIGN), - 20, List.of(PATTERN_PANAMA_FOREIGN, PATTERN_VECTOR_VM_INTERNALS, PATTERN_VECTOR_INCUBATOR), - 21, List.of(PATTERN_PANAMA_FOREIGN) + 21, List.of(PATTERN_PANAMA_FOREIGN, PATTERN_VECTOR_VM_INTERNALS, PATTERN_VECTOR_INCUBATOR) ); public static void main(String... args) throws IOException { @@ -143,7 +141,7 @@ static class Cleaner extends ClassVisitor { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(Opcodes.V11, access, name, signature, superName, interfaces); + super.visit(Opcodes.V21, access, name, signature, superName, interfaces); if (isVisible(access)) { classesToInclude.add(name); } @@ -188,10 +186,6 @@ public void visitInnerClass(String name, String outerName, String innerName, int } } - @Override - public void visitPermittedSubclass(String c) { - } - } } diff --git a/gradle/testing/defaults-tests.gradle b/gradle/testing/defaults-tests.gradle index 89c1d49c3c7e..dc42a2216b4f 100644 --- a/gradle/testing/defaults-tests.gradle +++ b/gradle/testing/defaults-tests.gradle @@ -126,7 +126,7 @@ allprojects { // Lucene needs to optional modules at runtime, which we want to enforce for testing // (if the runner JVM does not support them, it will fail tests): - jvmArgs '--add-modules', 'jdk.unsupported,jdk.management' + jvmArgs '--add-modules', 'jdk.management' // Enable the vector incubator module on supported Java versions: if (rootProject.vectorIncubatorJavaVersions.contains(rootProject.runtimeJavaVersion)) { diff --git a/gradle/testing/randomization/policies/replicator-tests.policy b/gradle/testing/randomization/policies/replicator-tests.policy index 4e78f4650e5c..9a8f6aa7026a 100644 --- a/gradle/testing/randomization/policies/replicator-tests.policy +++ b/gradle/testing/randomization/policies/replicator-tests.policy @@ -54,9 +54,6 @@ grant { permission java.lang.RuntimePermission "getStackTrace"; // needed for mock filesystems in tests permission java.lang.RuntimePermission "fileSystemProvider"; - // needed to test unmap hack on platforms that support it - permission java.lang.RuntimePermission "accessClassInPackage.sun.misc"; - permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; // Permissions to support ant build diff --git a/gradle/testing/randomization/policies/tests.policy b/gradle/testing/randomization/policies/tests.policy index 46a351f4c619..85edc3e572f6 100644 --- a/gradle/testing/randomization/policies/tests.policy +++ b/gradle/testing/randomization/policies/tests.policy @@ -50,9 +50,6 @@ grant { permission java.lang.RuntimePermission "getStackTrace"; // needed for mock filesystems in tests permission java.lang.RuntimePermission "fileSystemProvider"; - // needed to test unmap hack on platforms that support it - permission java.lang.RuntimePermission "accessClassInPackage.sun.misc"; - permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; // needed by cyberneko usage by benchmarks on J9 permission java.lang.RuntimePermission "accessClassInPackage.org.apache.xerces.util"; diff --git a/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestMMap.java b/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestMMap.java deleted file mode 100644 index fcaae01e982b..000000000000 --- a/lucene/core.tests/src/test/org/apache/lucene/core/tests/TestMMap.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ -package org.apache.lucene.core.tests; - -import org.apache.lucene.store.MMapDirectory; -import org.apache.lucene.tests.util.LuceneTestCase; -import org.junit.Assert; - -public class TestMMap extends LuceneTestCase { - public void testUnmapSupported() { - final Module module = MMapDirectory.class.getModule(); - Assert.assertTrue("Lucene Core is not loaded as module", module.isNamed()); - Assert.assertTrue( - "Lucene Core can't read 'jdk.unsupported' module", - module.getLayer().findModule("jdk.unsupported").map(module::canRead).orElse(false)); - - // check that MMapDirectory can unmap by running the autodetection logic: - Assert.assertTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED); - } -} diff --git a/lucene/core/src/generated/jdk/jdk19.apijar b/lucene/core/src/generated/jdk/jdk19.apijar deleted file mode 100644 index cce32639b05d309f924e58126e2216a695d5cfc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18462 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

#m)(CZ|P(FOy=y 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 YCkX}!&jbQu2K>uL26&4Oe*O0U03|T;GXMYp literal 16543 zcmbt*1yCJG(=F~0oQnl_cPDs&;7)+x?wa6EaCdii3l!izIp=)K#U}SA) zXliW5AZTDS;p=)oitON-H>XkIA8y35&Fe!lE+a_uuD2bS9qAWtm zeMF5F%<20@Y7axEj%5buQB~l$W7K8A&gss^;08i4aAW{0#LBX6)V98R&ckJVw?2D7 z@B$QXt*HG>D!pI)6-yX-5E&bryftrqY(6-bEc2`%G7P~37Yh@}a{ z8m=WdtW9))vs!GsjeZYNb$DyVM#1fjl7Cv76|%F*0&NhiBo6}Y)oR?;5BXlwN&+N$ z8hoyjq0__5wdB@kkSfRM*YDEvaA$O9>^7E{!N(S^uP{tj zSU^^5Cx*berZHtWrd0u*VYhNctlV(K$9;3KM&JB)epzSvw93{0oSz$S=XeS1IRu{( z0az&=@9{!!{H!DI8IDeK;0r1@o}pvqFGrX=s1Y=q*PwE*pkYWMm%dJdf`Dv6fPl#T z<8i@%`ncE`TIoJICXVv`GGKzp?mf(1`NZ^v4Wv-0oG7x-c~R&kHIYbHTm#ZkCLB`s z$Gf#p)}Sxsd-VvJ{Ep;qsF#%wJ{I5JUS5OQgxL$(ldp(s!{VQl=znFMx$%yBeL*O6 zb8tkJ*w1(rMWJa`A8N>(z>Q65C=>tS<77o+Ye=H?6J1VhYmv z-orHd%X!6E5s71yb%B}5uqW?w5uM^SG^sy|bwviP{qTG_=&WvFJp_83kl zBIqgHE6Idpf&qH1X_V@`7-S{5w4s8ee>=ro`Pwdv9-n^%=QJbf+=u3u>Htsy1Fo{BI(n_jz8Ss#`b0c z{C;z#f9nR%pWIw2Q!8`B$49>|W>~W8eqRr!*y}uR;o*D;i5=teLdqpY(TgGBtu&>R z7rFONWfG;RwVJ-Oxxr)cdp#pt8S}}e+U^vHZZsbG>XtOr(#qEc(hh3@KZiXKbXu3| zy~=?(?R8zBjj})t18PoClcp2Mr|L%rdKKb=#1o|$5G;qhHrxwVOq!OwHbkUT;j#2) zY8AN>HQ9ArqHL7R9|w|mhGjQ_lig{drvuQ>#*APL`+edbY!k~uXB#e=?hC@ zb`UlZbR!Sih+5))As@m^-fv^kQ>{I%8=^-VLm~Ac=q0C$^M%!gcd*5&B3nL=TBtDZ zitpC$@Kpm!OrzEbyld_fyj};oCz;H$8^wx%Au)4^zU4*=O?PfEJ?ADW=zW)?hhbuY zicK-um&h>>iLwwMZyTzqQqJgvhm#q`2g=na?1GUAG3X|I3pyYN3W{6ma$P=Bx4>Zu zsnTZRMyr#ygJ*GEGgSQPOSY;HUHOUW4bp>;yn%%{v%9BJ@3%hkbSm;Ejijb^)y?8@8t zjMQ(QAiC%jN*hAhP~9mR4tpG8jE#i$<8r)CUW*JY+!X4Z#lYrA?0T7d*!6pMt`T|j zBigQ&L)6&)0J^QTSFQvzBYx$O(QciU)28;c%oexOU)A#YuiU7W@7Sl^I$jiBpJ+XE*h??2p z#Bu|K>x-!Nc0=HtU9mU9BZe&P)wS$Jq#JTyeA3z1fX?5O1U4^SVxWeQSem!HlBfV++p@tIt@dn5=rB- zBB(YSWm#fQQ51fH@Vc}B{B3wHScol&woZUv|_}+|Yx1$R&R3bE#O_l&DTnZTMPz4v@n2csq6in5` zehL%2jKFq;c;51|fLve8QR^!ab8~okx|-pOh6@CZf?V_nOY&tV_r4p5*2~eu53sb< z?rU=xQI&N9oTs+{N#hsm42Cn^?<09YyZmRcgH~OM?!tQu2Yk1SjeCTfWRSLcNV*wk z*ipkevWW`q2eU^nkOMTo1z3M5rnI2UY=-$j9NoaCpQ8bm*6@`b!CM>W4w3~~{6%Ts zx>Jdk>p1ozSP%|=(`g{u#=v}{b#kZ)_2Bv!=F=iW@SSp+YvuRQ-kIda^&*6@A4skR z^fv|J#;sXJZ$5DfoDtJ|17V$A83lI+8?vueZKdb?1FYa?B~Gw87|V7M>;^_Y=U0Eg zF$Z_f%jmK+JXkBQKG&J>z&UBC*ahdcn1ig=K7@&g)9Q|H8dUs|k$-iTMSv}(6WJP& zE-R4pQI{?ZB~&l;8ge3!5`2&1;dH+`Nw4s|4?hP`zl-mT|9ZMf<#ZiPL@f+04XqyE zsN&=oA7m`mW!@)~@Xe7=PzQv&jr*h@7OFm2!yDAL%xXh*wbx5~(TC>z7oZ-oRkTf+ zOxx_H-%_16q!v$poLz&h;gR{o(Bm>s>UT0NPJAmT8Nyvjxb4#;!7e`LQ%s|YIB%3g z_al7qb(PP_`PIP)TC7KfP?flj(_Z=(T#tK4OCmc_sFqO~&W-m=L0F_)WcgfJyVY0- zEZC3P*9#czqVa^^lTZwd0wG>b8aL@tx@xv5IOb{@cb>hItIyr_2M@kZAh-;YlqX~) zck?OzGH8RmI7L09bd%|($~mpSa~D>;JzCC+Mr{Y`3D;Wi0pHf@EjiO`KHs-vB0!pCV~$atqw0jZk4w*ewZyy=N=%0paBhN4vKleh%kw~+v69=W+`TA~|5lWq4mp2{lA@vU6I_+4 zUwWd6qI-hpZGlqu0sRZnr9fb*gI|j*PKoPfNkU{1Hd&JIeKz9Qw<=aXY(2QT{}pc; zUA@e?zDS#w5MQK9Qu5g1us*vAcRv_TnU{^Yxw={vsg1kUt^HBSrb~uDi2n+cH)B!X z%8r7;<<=lP0;3VPH;{?@JEy71IwL{PwwsuFO5xeM*hkceaWF9~{E^l4+U3Zr@~>0# zJPpdp*-=#Ssp(llcLp$ZfSm6eW=z`#Q_r{Rw;9;1E}33ae($js!}^e$fqU+jyN)i# zM8>$V6zv}>eySh_zdovRQ-D9}b8EMy4OeEQGiLuu&!K6^>tc8_I{p;8zeZyBVD+ZlflK}TqGz#O%7kQ+-g7-e9PrH@~~1Ka2BWh(tq2BMap_O>OLu>R2OjdByz9ex-!YfPIewIDNK@d5POTI+7W)Z>xb5U@0lM>D{NBsu{Hl20oalY-l z(R1>Er~nj%<5YJJtdA(`c&_MeGrcwQ`K@b)`0vEJz2D6+N>H#)qH!G0gc&j=N%DUp zYO}H#C_HxV5Ue3rjQ?Wu;?r_F(jIJ7%}tZg+2vB53oe`*Wpj=mG^t`bZfwXBNAPvu zl0^z@V9=GW<*n>QGk#jmQOtKcEvHJ{J>GTh7`xJ}lIGHYJhtKUutaF5%0bjLLeKqL zNwRd~+9|jI@`eL}+GVCXzWR~YFXm@P*m+&{ldLIdAwJ>&YILyi3qGh38;DvAi%%%K z9yzSK?Na&f6_o6CTZ5@{dgDmJDxBQ(XfHO^oue+;Q`4N)M?N;#BrJ<-f{1Yl6N{adVh|pDj*DdSGclN* zq|{OVdT8I0cc?tzI2_bzffB)X7zA5+buJZenN{FZp2;}i*}0$ppsm&X=RxDkDG5Bf zmwcT5AM^$+fb>g2c5it`?GXjvG(gcQJO>s|WsWuAqTRNx2=cblMu*$BIw)3t z7ln&V3OAzFg4mc<44sD}6c?$@f>{4FJUWFblsh?gUSxsNtVagdNCRo6lv4(gIH<3h zX!w&2JCn|o8ru&qM$fXd1?*wJg-z}!FrZFY9i;)06&LnlA(a>J? z^mFy|yj z$4^kTGqEsQa}N#uD=6KRf#z`92^Rro>i81>7t`|U+zCWFa6 zm8EZ~ldqN8981j1l&04k@Bg73^DUTsR9QNmCYe{2&AHs)&T@2au|~aS z#vReL$YY9FmggAxr5(Zs+=j#kjS;p5<{gs_{Gq~{%G%d<8t;$oC0>i(v+ZvPcJsVK zyl24@Kq|oL!Hv9Vyl25jA@(84!Ro(gIf3@HF)bd(@7?`THKa)faf62j0hz@6U3NtC zo9yU6VU|=;-`d9T(f!Upw92Ye09nMCX$hMV7LiRlGIf9qQh6OJmlS<}6UmsVen{(( zEB@G=>cY!SDYk}+I8QeuZF)E5^7gU`L`_dp4ZT=izQ_R+U#iDBKL?9HX`2PO+uh_L zu5dDfDw24cwdmeqN(V&Q;D95A@)MjDifnYJKIzqZbJ0Ko!|z!*focRixjR#azj$dF zQRUoB&hDp^!=`FmRBD)TqCG!ju5T{5nHAyG(t5%Kks8jp&)VMY?$TiI>KBg@sTX)P zg3!AneL5MD(={+KwK9J4ip2BXdX^sw3d$Y|R1E5}w7Xj5 zCbhf8;imRFdur&E_r|L`Hg>4RA)|ZXdIHr6N=yt2)Q|Wsw{*%NlahtR7cXCBb!zE4 zTQX5bMGPwZcqkg7t$RsH3`!A^z%O7enm7|v1}?iS^f zXX-zvNzlT=T3`2x%4fWmA{rBVz$&4Gpa3$4s3(yaQEO;tROWVRS&5!*myd3S#D=LDDd{v) z<1A_;I*Xm9KdLu_++yH0@vsq98xsl!j?5on{=kOfi3Utiu+%p!qBgY~x!2*t!22S^ z7>m6I7>V})p^7ib3(~N|6gru@EEFUgi+HK+{W~Vm0+Mobg<?Ah?f@WezlzaOO#g zC}#(Cm`O^q(=LUppoL>^L@UfFnJ?yrrRiBa+_hK0?svtMzdE`+Ls|oK_45sKQL&UYE#QL_a>8GJe(2Ea(&vr=$)X5_QkqfR{2ulh znFir5Lk-6+wC<^NIV#R>)#$>IqMvZ{^c}$t@;3Y?b)u;*oKpz}UrU3LTUrxmm8-hd z7HD?`-tnXwA2V>Obu=iH*AT(H)7I$ID*-aA1C`6;zQ!APK;qc63`N3Ay0geHOZnI@ z6QY3S6w^m@emT2Ace@9)#^6+?B5Hyte$m%qX7VataD+_(X4!00qT1u_E~wV+lC?p@ zA|EinFWRO}V=PP525H%L|FPxfn=@>f_sZk++miFs)htB^J4bzoCpY{rsn!*PYW%xY zuSI81tfT&-)Hr`BHEq7kdG_@LSfbdkLXCDW)GnEbNzPmMLVZE^hfpVh7ziQAPY?=) z5Q>NVN2uBV6l(RZ5STQ{Ic1_txI%H15?r}y0NR$ik`^c4R!VY;wo=f#I59d3?gHh) z8~{5JpauY7&v^y;#`s5hr+NE&gNoZT+jyH=K|P0p0)0t144==1ebLU!1O7ru6NH)` z1_KK$`991&XgKr>BjKHU4Y&S(*+pkdJ!=aoYin~yn@0&jo}!kuf*>;AD=JkBbMu>0 zf-+*u!>!(nHSpftXh0BQK4~ihV*)*Gv%y;iSkK`!|a2#qBX|-{{){<&ZhwRK_OF5%oyJeXBozlblYjJ>sX?OzMQe*6HZonaXfz zH-^g`_O-GU9#k4fox?Kboc04h&dxEmb4n9ETkJRAJ($!|EP$@6jw{#e%(G`~45`WF zrbZOYSaTo2;MUO$UBKc76c!gds3t4;GlWg$xUIsz*{_{>s>S?hI=V__?!Q4vl5_8x zx0``&DJxy0A7!OEGzL4D(wHYn7%oy$C)vogEvgAe9X@9)mniG6>wkwaW#IJ9sdk%l zD_{L=BbdCD^6hqCpJVq2kFP8Py+IS`p3%VTHfGF4gn>wIR8G!U(sCp}N?W!~xY>H4 zxKg6O)lY{JsZLI~>b>0|xop?pjy$;zw(HZcXFmaZYlC(DV{Q=mT;trf-3jH{VdTdG z3#@mZr)aLBT^MuY7#84Uw+RhmC>nDcZYUc&P-iwQ+TC>ZRwwDAmdCfDZM=))&mPVW z0xRSh@ZJM$f7^+Ex}&CSb>AiUcL$vRy91833|;g7;(w_B<9{k)`=Xb5kc?rap$K?i zlgO|=f*OlL5qfd>VZL{eiT>$*@Am5dNR#fpZ%A&6uv8d1x2vd{beaj8GASilwX-iX z7bvPmf+Q;brn>hlm=as}4|L@OW~Uu@Mf;Nw>L@%3KVmdue6Q@Vuqf{!Z-4J7Z^T4K zPW5{i#O4G5yjUr~u@X0o)^v4{Z6GFEbdqDdpGnoQ918b9W`6dOvn9%uK={M;aH?ZM zzuvo^`EOnC>Fra|wQzhoV8q31sp2Z%XHJut0TD`F1=;oSV3x06WJA7=Ti}ia=`hVl z6{w=d?*qx|3N=UF{yd@K8I5%d;u$-5zMJG%q{|hPvdhbJwQPJ~f5t`Qd3W+n8{|1q z1lSvNvOwh$?y1C!AoRg@9qTRL!BAc5$y%wQ0M$B!(C!V|xz|bw97nVN=+Y!1uQkmT z`u?@`vHDdddg`8QIjFEMJKYT2H3_&coV{sO$(yxGFJ1qn+Ogo#_8n}ynQ-68ENf#* zhp%(6h@l37n*7#PmrVZ6MNxuljp5chLO`IzjdB(6!_wH>`)Ev&C7~X}UGwJIvQdrQ zj}qfGe19R8GH)vX9cxm#!~#%NIWWyG+JTgr~k5xk0FqCV|EUrC-;rIb8NVqG*c zRS~ux9^eZ(rW3Ehf6q#jTRA7(yU7=*;E@&i$^{*2q3_hFs8+pv8a=;kzZvbBW$fFa zt9sr2MAyM)4Z~K6gzmSilEHRSef4G4>dFlXcP? z;>!J$^6Z4wl5yFMN+=Zhis^=`v(>Q$-?9}~iVahe24cDjEYu9hTwUisZ(u1nj)N+4 zqGlYfI8E15n@`(G=$6)0NE}@B^pY*-?1gQOWNI9Y5Pl?EMhjYqxossZZFU2Y!yn0A zgk=^M^v-yE>}X?q{mBWvR;_`S+%WmHcMI?OwWAEz!KA-Kj~`mVi1WO?tX5my7r&iw z?b)|qRe+*oMOGITzN%JLXo5wy0+zxFJZHT^H0hHT^km#05BXc~L}W9{nW=60og3;@ z=GNwNvG}slHr<){ZVPEt8?@%cu|_nX$6i0H&Q&yodEKXW>1p80#+^2!-nU_ovHDhc z9`K>RtE6cdv+I1ha=yX_a<-iV(^(40)VkqWLB-plShd=Y-W-ZiMHbsrt|z_AO|8dq zSf&O)+O6`1*QwO?`A%zyV@g#y=1#o`hl8{(E)U4w`Gw#vy+`g1KEq+m;pm0`^mC>k zaA$oCz(7v|Xn~d7vXEtenlmyava`etPaTMZ&wMST7}jGex%l6#3C^DBCA+2YN~9Bw zMjb@H?0?&T-gn;beEf1}h;z`A4qr|# zD7D>CG)0*HvG#*#(lAooJ~ZHNf14@NJ((%~-N$qqns!B9{RJ!DMs{g>F4oN38UV~6JS*C6-#Vi_VOk>v7F|B~xSW5iI4 zXG<$6XAgOYRRq6cjN&*OPxxSIIioezK@Ai>r+ee%_7vWG7HU>xpQXU1rkyI+BHe(@ z(-Qvxbujl$VVeo_7s>IFLm08oFn9?)v0QRKJ6;8ui=zT2&De%e3_JkHq}sHXW4Y`k zoGdsw<<`08K~{zpOke4#A*fa*A={SD!YCJ7D#{Q^OYB5zk|H6XdzI1Qmp_;^T5h1G zzR!UfWOAP+#4}Ai&gj9|V*Jq?YqnD{OgVDo2_@IG*KeuKvSwQjJ;lHw~pFKk-K z#2z~@(Ne$5X~tH}n1l(;}%FrUo*Fx{Z(It5p~Zxv9;=LHBGrx9gyLJtX!~NotH;S~L9pv#ti+A- zi>YZhGV!$Z-C61#PeT9cmyQl9OnvZ~F)AQqLnl{LL= zKd4^0L@w8JlA}!4Qbr|@uWY5vr1_!%oSfSjT}d*{{xa)k3{FplH*@utFEntX5nPny zgp-N*as)6ZjL4+Z! z)fPTf;MV%>%zx14xfP-Gr^87>&L2qAd6xZC7gtm$jt#p4efGy0TR}XpdC$Ts@!uZr z71O8cj5y%gHgeG}#w#==V4m{=$J4gqWVN``B^kSsp|xy(q%qqD;LU(c@WOdx%EQ%$ zI%bY#AA||m&mpizlnD&ZA)WB(x^bND$$tf$ZK)H7$y3lp3Bh3-^lQLY2mLV+lv1kK zuYJ$o`2RP5!X~k2V7#+l#4+wr-Kw|Ms zBVrpCj#8)AdWN`Bur@6c6d?LsL%c_B+FIx&h-%f*)d%w(w$wd=iTS5pKx4|g*YQ$y zZPr1BUsf@N(rfM#hCw8e$qtTB?1?CQbW*@s$p*B!TR7`_7#?Jku!YO-{TR z`hkRSO`lIw732dD*g+;}?OHeF596!=(Jhp&e%fmwL|6*N6vjNJl1$Jhfwe) zJ_z7sNxr7_hENcX`LLeR+xkZa)o%-4tNX7E(*74(Dq`)ZXYn^5RD8I5qo<1I5)x{A z+)F8w@-sflBQGXFzCc0F#CH|dI#tWbPUDBOycgit^1ZXKy^)EhUQxL7uvo7Q?th!T zg7#9hVA*~9T~&+4aeRECzAG$zY~Qiw(~isPe*Z_KtATyKL`_irKBUIs_w=5za2tSN zPj1RY#S*k3@clNqD}FZO?~tSmdhi2I zbdYrCl8v_$x}+467m~$40Y8WqrBWqTq#29633FByEUuDUntg z&4(u;f4^xduX%c{yro_X`+>;-0AGbcEK!)K7o%@!1lPJdtqyFT%9iK+Csq}=A)V<@ zU^6}^-!ofL4%z#;eCU2`z>-W!1d&Awpi+ItyqTQic#zTtf69ypDFy!46`!t7|0<KPZJr-M6a8WR%$7NTc;{5x!&X*+rt={VM`eX z43WapY3|!r`~9?(vy+`0@OCOEY$Vs~xE=BYt&jRVotu(3;VJIp>)_1?__g=jGR%h& zO2i?wc+PhuFbx?nkpW7Li9N;WpS9rXHiNhY`_j(7F9np_%H9)~qZCU~)JHI6TdAr$ z5~qfRbOqs{sE4jYZZbK?*_!WD|~!Rg-5|mL%EA2Q6+LC$?MTmsaGFZ^y?xy zFt}%N{i@wd^7KlK#gKT24uBw)3If!5L8WcHb?w^HbT)0#OMX`cU~Fck?A zAe7s*C2_~K>Nz;{hb!9eqD(=Z#=S=R>R$5ga%kUvC!Je(CY!{X5{dgGITkmJpsv|2 zFTz|-C_RdjM~`DJi~==u$5G~$#k?kChNAIjr`RJ3^w1FYWvrJkliNcFx75fP$r%NI z3}eb#ZZrG9*Rb7BBwnx|%tpLsGO9o`=wxMP9C0w|{TfX4mJ78MC>}D{NtU=Ft+@nq z6uxT6FUV(>v6zlwqy(G;SHi-K%a3WZ7FW0msVCwyzB7GeS9fW-$~=4W5>3_73YsN< zGO=q|XP*4qP@A0za;3mZ0Xuwm9TrR&p*JZE>t!gjgb$tyNRcyoET+7|>t1R(lfFB) zJg!ojVSQm!?3D3K74+3`Hy&ntu;ycp=;X*ahhZ;ogz5V7tB+rp^ZSp?@qKvJ;7+@UBj5AiFckCp|()LR@x4zLt5C>-1?bvBUo;Px=(a7yh zARi1O4Md%`GhzuuktwRrjLD(F+yjB%7WXN?$x;UnI4v8$6iyb1fH&(yH85rs{4tFg z!FNv}%W^84D~(%<#$G< zQE*0(uhkp-Q7LoYB7Dy?%q#kSt`R*2tcH(DkGXSTXw2!cTxFa+p;#^d(TTJn^@-_y zO4qU0NRsE)7@>~0n<5`(APoaDl@CO8q^zb#fo6237n^_?3R#|^Qmjf9QY^P!TUM=? zbe^aMRhb?S_iE7R<|u1fBN-j-a*QHab%|Gok%CChTV+L)Q-731LB%15{yiT4H!`Yd zVr}=&GOBYn$_&j&o@|(mnT}J+FH8VB;oH>wDmf_&#~5oqFeJpMe#=ouHGcZ^WiAwHKGj(#6>2gROM|e zz+LsOg_Gd@C{OORwWj+N#w3CaDIDri@(>Iiy;yxqEAE3Koep^4%%=O~ENi{nu+HKv zn3K=Ra%L-*%~dR32Pof3F3NUpH>x;c5PFs99qSq5gN&khn|S}bjQ($3=)RD~pl_mU zWo2k#&md-MVR)a7K74rOfMH6D3TT4JdqnWw_x&^!A1}fLX%N+;j`wm!aItFha6&wra?}I(4biEFFDav4AYal3lTi)@>u~wWvGFqcZ71n zX=IwsF|oZ^!u{1WRg7lsj|NjPb6C?IC8`+S_`GAl!{U)|Pnn;?G=GdI!?r|D z-0#QRb1ay&U{)Veg&A6)2x^riJ3vir?7+PYXp=0r`IO^La4as{V(36SzYj7Xbjt^) zQcZF=lEt-jYKY$!?~b<|hrkbuqF42d-y3n!5lz8E2y%fWFwc-d|2%g!JR41K^^3u& z+Uy>C+FT887|nD(sQ1TQKiCQ)$#=sdj5=ZIp%8QTpb1H|1{dScQFTe!+{$JbtnVKt^U>{nc9hPykB0Yhgb4K)cS#$zMd z?$EtDJDmwP&<{sm=sh=k(tR0J`+h<}?iDB)I>=v-;C_DZ^w4?#b^YHxiTfM&Pp@|# zUQGS981BE#6WC9@pL&e>^E;D=VENbjcpq5*j`^>zfF2w8`K`r+3jDQ0rKm&55aoM!tZ8e9%KL9Qh%5s`D---p2Gf@iIT_gKevw``tpCRZrrEg zf77LZ4E}Rh_1AOBf&V1`uNzu&-Imua@b#so95pQJXsujY~tt3|F2>LPWSI7o+{fsw(#@) z^r49G*XpPLcME?nDLlsi`S$cH3xSOPj{n4+_pyPW6Z>DcNRs*A4Lo%vdu-w7JnC0J gLuUPth5z!pAomLLKIDUd;N1TN-0w9w$HS-p1MLS*d;kCd diff --git a/lucene/core/src/java/module-info.java b/lucene/core/src/java/module-info.java index 684f32960e2f..04ee07275411 100644 --- a/lucene/core/src/java/module-info.java +++ b/lucene/core/src/java/module-info.java @@ -21,7 +21,6 @@ @SuppressWarnings("module") // the test framework is compiled after the core... module org.apache.lucene.core { requires java.logging; - requires static jdk.unsupported; // this is optional but without it MMapDirectory won't be enabled requires static jdk.management; // this is optional but explicit declaration is recommended exports org.apache.lucene.analysis; diff --git a/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java b/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java index 8c09cf98b9ef..2bfa25fea43b 100644 --- a/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java +++ b/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java @@ -74,7 +74,6 @@ import org.apache.lucene.store.Lock; import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.store.LockValidatingDirectoryWrapper; -import org.apache.lucene.store.MMapDirectory; import org.apache.lucene.store.MergeInfo; import org.apache.lucene.store.TrackingDirectoryWrapper; import org.apache.lucene.util.Accountable; @@ -1303,12 +1302,6 @@ private void messageState() { + Version.LATEST.toString() + "\n" + config.toString()); - final StringBuilder unmapInfo = - new StringBuilder(Boolean.toString(MMapDirectory.UNMAP_SUPPORTED)); - if (!MMapDirectory.UNMAP_SUPPORTED) { - unmapInfo.append(" (").append(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON).append(")"); - } - infoStream.message("IW", "MMapDirectory.UNMAP_SUPPORTED=" + unmapInfo); } } diff --git a/lucene/core/src/java/org/apache/lucene/internal/vectorization/VectorizationProvider.java b/lucene/core/src/java/org/apache/lucene/internal/vectorization/VectorizationProvider.java index a1f07c6a0435..158cdde5e0ea 100644 --- a/lucene/core/src/java/org/apache/lucene/internal/vectorization/VectorizationProvider.java +++ b/lucene/core/src/java/org/apache/lucene/internal/vectorization/VectorizationProvider.java @@ -17,11 +17,9 @@ package org.apache.lucene.internal.vectorization; -import java.lang.Runtime.Version; import java.lang.StackWalker.StackFrame; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; @@ -97,20 +95,11 @@ public static VectorizationProvider getInstance() { private static final Logger LOG = Logger.getLogger(VectorizationProvider.class.getName()); - /** The minimal version of Java that has the bugfix for JDK-8301190. */ - private static final Version VERSION_JDK8301190_FIXED = Version.parse("20.0.2"); - // visible for tests static VectorizationProvider lookup(boolean testMode) { final int runtimeVersion = Runtime.version().feature(); - if (runtimeVersion >= 20 && runtimeVersion <= 22) { - // is locale sane (only buggy in Java 20) - if (isAffectedByJDK8301190()) { - LOG.warning( - "Java runtime is using a buggy default locale; Java vector incubator API can't be enabled: " - + Locale.getDefault()); - return new DefaultVectorizationProvider(); - } + assert runtimeVersion >= 21; + if (runtimeVersion <= 22) { // only use vector module with Hotspot VM if (!Constants.IS_HOTSPOT_VM) { LOG.warning( @@ -169,13 +158,11 @@ static VectorizationProvider lookup(boolean testMode) { } catch (ClassNotFoundException cnfe) { throw new LinkageError("PanamaVectorizationProvider is missing in Lucene JAR file", cnfe); } - } else if (runtimeVersion >= 23) { - LOG.warning( - "You are running with Java 23 or later. To make full use of the Vector API, please update Apache Lucene."); - } else if (lookupVectorModule().isPresent()) { + } else { LOG.warning( - "Java vector incubator module was enabled by command line flags, but your Java version is too old: " - + runtimeVersion); + "You are running with unsupported Java " + + runtimeVersion + + ". To make full use of the Vector API, please update Apache Lucene."); } return new DefaultVectorizationProvider(); } @@ -189,15 +176,6 @@ private static Optional lookupVectorModule() { .findModule("jdk.incubator.vector"); } - /** - * Check if runtime is affected by JDK-8301190 (avoids assertion when default language is say - * "tr"). - */ - private static boolean isAffectedByJDK8301190() { - return VERSION_JDK8301190_FIXED.compareToIgnoreOptional(Runtime.version()) > 0 - && !Objects.equals("I", "i".toUpperCase(Locale.getDefault())); - } - // add all possible callers here as FQCN: private static final Set VALID_CALLERS = Set.of("org.apache.lucene.util.VectorUtil"); diff --git a/lucene/core/src/java/org/apache/lucene/store/ByteBufferGuard.java b/lucene/core/src/java/org/apache/lucene/store/ByteBufferGuard.java deleted file mode 100644 index da5db6586e2a..000000000000 --- a/lucene/core/src/java/org/apache/lucene/store/ByteBufferGuard.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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. - */ -package org.apache.lucene.store; - -import java.io.IOException; -import java.lang.invoke.VarHandle; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.nio.LongBuffer; - -/** - * A guard that is created for every {@link ByteBufferIndexInput} that tries on best effort to - * reject any access to the {@link ByteBuffer} behind, once it is unmapped. A single instance of - * this is used for the original and all clones, so once the original is closed and unmapped all - * clones also throw {@link AlreadyClosedException}, triggered by a {@link NullPointerException}. - * - *

This code tries to hopefully flush any CPU caches using a store-store barrier. It also yields - * the current thread to give other threads a chance to finish in-flight requests... - */ -final class ByteBufferGuard { - - /** - * Pass in an implementation of this interface to cleanup ByteBuffers. MMapDirectory implements - * this to allow unmapping of bytebuffers with private Java APIs. - */ - @FunctionalInterface - interface BufferCleaner { - void freeBuffer(String resourceDescription, ByteBuffer b) throws IOException; - } - - private final String resourceDescription; - private final BufferCleaner cleaner; - - /** Not volatile; see comments on visibility below! */ - private boolean invalidated = false; - - /** - * Creates an instance to be used for a single {@link ByteBufferIndexInput} which must be shared - * by all of its clones. - */ - public ByteBufferGuard(String resourceDescription, BufferCleaner cleaner) { - this.resourceDescription = resourceDescription; - this.cleaner = cleaner; - } - - /** Invalidates this guard and unmaps (if supported). */ - public void invalidateAndUnmap(ByteBuffer... bufs) throws IOException { - if (cleaner != null) { - invalidated = true; - // This call should hopefully flush any CPU caches and as a result make - // the "invalidated" field update visible to other threads. We specifically - // don't make "invalidated" field volatile for performance reasons, hoping the - // JVM won't optimize away reads of that field and hardware should ensure - // caches are in sync after this call. - // For previous implementation (based on `AtomicInteger#lazySet(0)`) see LUCENE-7409. - VarHandle.fullFence(); - // we give other threads a bit of time to finish reads on their ByteBuffer...: - Thread.yield(); - // finally unmap the ByteBuffers: - for (ByteBuffer b : bufs) { - cleaner.freeBuffer(resourceDescription, b); - } - } - } - - public boolean isInvalidated() { - return invalidated; - } - - private void ensureValid() { - if (invalidated) { - // this triggers an AlreadyClosedException in ByteBufferIndexInput: - throw new NullPointerException(); - } - } - - public void getBytes(ByteBuffer receiver, int pos, byte[] dst, int offset, int length) { - ensureValid(); - receiver.get(pos, dst, offset, length); - } - - public void getBytes(ByteBuffer receiver, byte[] dst, int offset, int length) { - ensureValid(); - receiver.get(dst, offset, length); - } - - public byte getByte(ByteBuffer receiver) { - ensureValid(); - return receiver.get(); - } - - public short getShort(ByteBuffer receiver) { - ensureValid(); - return receiver.getShort(); - } - - public int getInt(ByteBuffer receiver) { - ensureValid(); - return receiver.getInt(); - } - - public long getLong(ByteBuffer receiver) { - ensureValid(); - return receiver.getLong(); - } - - public byte getByte(ByteBuffer receiver, int pos) { - ensureValid(); - return receiver.get(pos); - } - - public short getShort(ByteBuffer receiver, int pos) { - ensureValid(); - return receiver.getShort(pos); - } - - public int getInt(ByteBuffer receiver, int pos) { - ensureValid(); - return receiver.getInt(pos); - } - - public long getLong(ByteBuffer receiver, int pos) { - ensureValid(); - return receiver.getLong(pos); - } - - public void getLongs(LongBuffer receiver, long[] dst, int offset, int length) { - ensureValid(); - receiver.get(dst, offset, length); - } - - public void getInts(IntBuffer receiver, int[] dst, int offset, int length) { - ensureValid(); - receiver.get(dst, offset, length); - } - - public void getFloats(FloatBuffer receiver, float[] dst, int offset, int length) { - ensureValid(); - receiver.get(dst, offset, length); - } -} diff --git a/lucene/core/src/java/org/apache/lucene/store/ByteBufferIndexInput.java b/lucene/core/src/java/org/apache/lucene/store/ByteBufferIndexInput.java deleted file mode 100644 index 04c78a102423..000000000000 --- a/lucene/core/src/java/org/apache/lucene/store/ByteBufferIndexInput.java +++ /dev/null @@ -1,732 +0,0 @@ -/* - * 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. - */ -package org.apache.lucene.store; - -import java.io.EOFException; -import java.io.IOException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.nio.LongBuffer; - -/** - * Base IndexInput implementation that uses an array of ByteBuffers to represent a file. - * - *

Because Java's ByteBuffer uses an int to address the values, it's necessary to access a file - * greater Integer.MAX_VALUE in size using multiple byte buffers. - * - *

For efficiency, this class requires that the buffers are a power-of-two (chunkSizePower - * ). - * - * @deprecated This class was made public for internal reasons ({@code instanceof} checks). In - * {@link MMapDirectory} it was replaced by {@code MemorySegment} based {@link IndexInput} - * implementations and will be no longer required in Lucene 10. - * @lucene.internal - */ -@Deprecated -public abstract class ByteBufferIndexInput extends IndexInput implements RandomAccessInput { - private static final FloatBuffer EMPTY_FLOATBUFFER = FloatBuffer.allocate(0); - private static final LongBuffer EMPTY_LONGBUFFER = LongBuffer.allocate(0); - private static final IntBuffer EMPTY_INTBUFFER = IntBuffer.allocate(0); - - protected final long length; - protected final long chunkSizeMask; - protected final int chunkSizePower; - protected final ByteBufferGuard guard; - - protected ByteBuffer[] buffers; - protected int curBufIndex = -1; - protected ByteBuffer curBuf; // redundant for speed: buffers[curBufIndex] - private LongBuffer[] curLongBufferViews; - private IntBuffer[] curIntBufferViews; - private FloatBuffer[] curFloatBufferViews; - - protected boolean isClone = false; - - public static ByteBufferIndexInput newInstance( - String resourceDescription, - ByteBuffer[] buffers, - long length, - int chunkSizePower, - ByteBufferGuard guard) { - if (buffers.length == 1) { - return new SingleBufferImpl(resourceDescription, buffers[0], length, chunkSizePower, guard); - } else { - return new MultiBufferImpl(resourceDescription, buffers, 0, length, chunkSizePower, guard); - } - } - - ByteBufferIndexInput( - String resourceDescription, - ByteBuffer[] buffers, - long length, - int chunkSizePower, - ByteBufferGuard guard) { - super(resourceDescription); - this.buffers = buffers; - this.length = length; - this.chunkSizePower = chunkSizePower; - this.chunkSizeMask = (1L << chunkSizePower) - 1L; - this.guard = guard; - assert chunkSizePower >= 0 && chunkSizePower <= 30; - assert (length >>> chunkSizePower) < Integer.MAX_VALUE; - } - - protected void setCurBuf(ByteBuffer curBuf) { - this.curBuf = curBuf; - curLongBufferViews = null; - curFloatBufferViews = null; - curIntBufferViews = null; - } - - // the unused parameter is just to silence javac about unused variables - RuntimeException handlePositionalIOOBE(RuntimeException unused, String action, long pos) - throws IOException { - if (pos < 0L) { - return new IllegalArgumentException(action + " negative position (pos=" + pos + "): " + this); - } else { - throw new EOFException(action + " past EOF (pos=" + pos + "): " + this); - } - } - - AlreadyClosedException alreadyClosed(NullPointerException npe) { - // we use NPE to signal if this input is closed (to not have checks everywhere). If NPE happens, - // we check the "is closed" condition explicitly by checking that our "buffers" are null or - // the guard was invalidated. - if (this.buffers == null || this.curBuf == null || guard.isInvalidated()) { - return new AlreadyClosedException("Already closed: " + this); - } - // otherwise rethrow unmodified NPE (as it possibly a bug with passing a null parameter to the - // IndexInput method): - throw npe; - } - - @Override - public final byte readByte() throws IOException { - try { - return guard.getByte(curBuf); - } catch ( - @SuppressWarnings("unused") - BufferUnderflowException e) { - do { - curBufIndex++; - if (curBufIndex >= buffers.length) { - throw new EOFException("read past EOF: " + this); - } - setCurBuf(buffers[curBufIndex]); - curBuf.position(0); - } while (!curBuf.hasRemaining()); - return guard.getByte(curBuf); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public final void readBytes(byte[] b, int offset, int len) throws IOException { - try { - guard.getBytes(curBuf, b, offset, len); - } catch ( - @SuppressWarnings("unused") - BufferUnderflowException e) { - int curAvail = curBuf.remaining(); - while (len > curAvail) { - guard.getBytes(curBuf, b, offset, curAvail); - len -= curAvail; - offset += curAvail; - curBufIndex++; - if (curBufIndex >= buffers.length) { - throw new EOFException("read past EOF: " + this); - } - setCurBuf(buffers[curBufIndex]); - curBuf.position(0); - curAvail = curBuf.remaining(); - } - guard.getBytes(curBuf, b, offset, len); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public final void readLongs(long[] dst, int offset, int length) throws IOException { - // ByteBuffer#getLong could work but it has some per-long overhead and there - // is no ByteBuffer#getLongs to read multiple longs at once. So we use the - // below trick in order to be able to leverage LongBuffer#get(long[]) to - // read multiple longs at once with as little overhead as possible. - if (curLongBufferViews == null) { - // readLELongs is only used for postings today, so we compute the long - // views lazily so that other data-structures don't have to pay for the - // associated initialization/memory overhead. - curLongBufferViews = new LongBuffer[Long.BYTES]; - for (int i = 0; i < Long.BYTES; ++i) { - // Compute a view for each possible alignment. We cache these views - // because #asLongBuffer() has some cost that we don't want to pay on - // each invocation of #readLELongs. - if (i < curBuf.limit()) { - curLongBufferViews[i] = - curBuf.duplicate().position(i).order(ByteOrder.LITTLE_ENDIAN).asLongBuffer(); - } else { - curLongBufferViews[i] = EMPTY_LONGBUFFER; - } - } - } - try { - final int position = curBuf.position(); - guard.getLongs( - curLongBufferViews[position & 0x07].position(position >>> 3), dst, offset, length); - // if the above call succeeded, then we know the below sum cannot overflow - curBuf.position(position + (length << 3)); - } catch ( - @SuppressWarnings("unused") - BufferUnderflowException e) { - super.readLongs(dst, offset, length); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readInts(int[] dst, int offset, int length) throws IOException { - // See notes about readLongs above - if (curIntBufferViews == null) { - curIntBufferViews = new IntBuffer[Integer.BYTES]; - for (int i = 0; i < Integer.BYTES; ++i) { - if (i < curBuf.limit()) { - curIntBufferViews[i] = - curBuf.duplicate().position(i).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer(); - } else { - curIntBufferViews[i] = EMPTY_INTBUFFER; - } - } - } - try { - final int position = curBuf.position(); - guard.getInts( - curIntBufferViews[position & 0x03].position(position >>> 2), dst, offset, length); - // if the above call succeeded, then we know the below sum cannot overflow - curBuf.position(position + (length << 2)); - } catch ( - @SuppressWarnings("unused") - BufferUnderflowException e) { - super.readInts(dst, offset, length); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public final void readFloats(float[] floats, int offset, int len) throws IOException { - // See notes about readLongs above - if (curFloatBufferViews == null) { - curFloatBufferViews = new FloatBuffer[Float.BYTES]; - for (int i = 0; i < Float.BYTES; ++i) { - // Compute a view for each possible alignment. - if (i < curBuf.limit()) { - ByteBuffer dup = curBuf.duplicate().order(ByteOrder.LITTLE_ENDIAN); - dup.position(i); - curFloatBufferViews[i] = dup.asFloatBuffer(); - } else { - curFloatBufferViews[i] = EMPTY_FLOATBUFFER; - } - } - } - try { - final int position = curBuf.position(); - FloatBuffer floatBuffer = curFloatBufferViews[position & 0x03]; - floatBuffer.position(position >>> 2); - guard.getFloats(floatBuffer, floats, offset, len); - // if the above call succeeded, then we know the below sum cannot overflow - curBuf.position(position + (len << 2)); - } catch ( - @SuppressWarnings("unused") - BufferUnderflowException e) { - super.readFloats(floats, offset, len); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public final short readShort() throws IOException { - try { - return guard.getShort(curBuf); - } catch ( - @SuppressWarnings("unused") - BufferUnderflowException e) { - return super.readShort(); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public final int readInt() throws IOException { - try { - return guard.getInt(curBuf); - } catch ( - @SuppressWarnings("unused") - BufferUnderflowException e) { - return super.readInt(); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public final int readVInt() throws IOException { - // this can make JVM less confused (see LUCENE-10366) - return super.readVInt(); - } - - @Override - public final long readVLong() throws IOException { - // this can make JVM less confused (see LUCENE-10366) - return super.readVLong(); - } - - @Override - public final long readLong() throws IOException { - try { - return guard.getLong(curBuf); - } catch ( - @SuppressWarnings("unused") - BufferUnderflowException e) { - return super.readLong(); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public long getFilePointer() { - try { - return (((long) curBufIndex) << chunkSizePower) + curBuf.position(); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public void seek(long pos) throws IOException { - // we use >> here to preserve negative, so we will catch AIOOBE, - // in case pos + offset overflows. - final int bi = (int) (pos >> chunkSizePower); - try { - if (bi == curBufIndex) { - curBuf.position((int) (pos & chunkSizeMask)); - } else { - final ByteBuffer b = buffers[bi]; - b.position((int) (pos & chunkSizeMask)); - // write values, on exception all is unchanged - this.curBufIndex = bi; - setCurBuf(b); - } - } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) { - throw handlePositionalIOOBE(e, "seek", pos); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public byte readByte(long pos) throws IOException { - try { - final int bi = (int) (pos >> chunkSizePower); - return guard.getByte(buffers[bi], (int) (pos & chunkSizeMask)); - } catch (IndexOutOfBoundsException ioobe) { - throw handlePositionalIOOBE(ioobe, "read", pos); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - // used only by random access methods to handle reads across boundaries - private void setPos(long pos, int bi) throws IOException { - try { - final ByteBuffer b = buffers[bi]; - b.position((int) (pos & chunkSizeMask)); - this.curBufIndex = bi; - setCurBuf(b); - } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException aioobe) { - throw handlePositionalIOOBE(aioobe, "read", pos); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readBytes(long pos, byte[] bytes, int offset, int len) throws IOException { - int bi = (int) (pos >> chunkSizePower); - int bufferPos = (int) (pos & chunkSizeMask); - try { - int curAvail = Math.min(buffers[bi].capacity() - bufferPos, len); - while (len > curAvail) { - guard.getBytes(buffers[bi], bufferPos, bytes, offset, curAvail); - len -= curAvail; - offset += curAvail; - bi++; - if (bi >= buffers.length) { - throw new EOFException("read past EOF: " + this); - } - bufferPos = 0; - curAvail = Math.min(len, buffers[bi].capacity()); - } - guard.getBytes(buffers[bi], bufferPos, bytes, offset, curAvail); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public short readShort(long pos) throws IOException { - final int bi = (int) (pos >> chunkSizePower); - try { - return guard.getShort(buffers[bi], (int) (pos & chunkSizeMask)); - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException ioobe) { - // either it's a boundary, or read past EOF, fall back: - setPos(pos, bi); - return readShort(); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public int readInt(long pos) throws IOException { - final int bi = (int) (pos >> chunkSizePower); - try { - return guard.getInt(buffers[bi], (int) (pos & chunkSizeMask)); - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException ioobe) { - // either it's a boundary, or read past EOF, fall back: - setPos(pos, bi); - return readInt(); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public long readLong(long pos) throws IOException { - final int bi = (int) (pos >> chunkSizePower); - try { - return guard.getLong(buffers[bi], (int) (pos & chunkSizeMask)); - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException ioobe) { - // either it's a boundary, or read past EOF, fall back: - setPos(pos, bi); - return readLong(); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public final long length() { - return length; - } - - @Override - public final ByteBufferIndexInput clone() { - final ByteBufferIndexInput clone = buildSlice((String) null, 0L, this.length); - try { - clone.seek(getFilePointer()); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - - return clone; - } - - /** - * Creates a slice of this index input, with the given description, offset, and length. The slice - * is seeked to the beginning. - */ - @Override - public final ByteBufferIndexInput slice(String sliceDescription, long offset, long length) { - if (offset < 0 || length < 0 || offset + length > this.length) { - throw new IllegalArgumentException( - "slice() " - + sliceDescription - + " out of bounds: offset=" - + offset - + ",length=" - + length - + ",fileLength=" - + this.length - + ": " - + this); - } - - return buildSlice(sliceDescription, offset, length); - } - - /** Builds the actual sliced IndexInput (may apply extra offset in subclasses). * */ - protected ByteBufferIndexInput buildSlice(String sliceDescription, long offset, long length) { - if (buffers == null || guard.isInvalidated()) { - throw alreadyClosed(null); - } - - final ByteBuffer[] newBuffers = buildSlice(buffers, offset, length); - final int ofs = (int) (offset & chunkSizeMask); - - final ByteBufferIndexInput clone = - newCloneInstance(getFullSliceDescription(sliceDescription), newBuffers, ofs, length); - clone.isClone = true; - - return clone; - } - - /** - * Factory method that creates a suitable implementation of this class for the given ByteBuffers. - */ - @SuppressWarnings("resource") - protected ByteBufferIndexInput newCloneInstance( - String newResourceDescription, ByteBuffer[] newBuffers, int offset, long length) { - if (newBuffers.length == 1) { - newBuffers[0].position(offset); - return new SingleBufferImpl( - newResourceDescription, - newBuffers[0].slice().order(ByteOrder.LITTLE_ENDIAN), - length, - chunkSizePower, - this.guard); - } else { - return new MultiBufferImpl( - newResourceDescription, newBuffers, offset, length, chunkSizePower, guard); - } - } - - /** - * Returns a sliced view from a set of already-existing buffers: the last buffer's limit() will be - * correct, but you must deal with offset separately (the first buffer will not be adjusted) - */ - private ByteBuffer[] buildSlice(ByteBuffer[] buffers, long offset, long length) { - final long sliceEnd = offset + length; - - final int startIndex = (int) (offset >>> chunkSizePower); - final int endIndex = (int) (sliceEnd >>> chunkSizePower); - - // we always allocate one more slice, the last one may be a 0 byte one - final ByteBuffer[] slices = new ByteBuffer[endIndex - startIndex + 1]; - - for (int i = 0; i < slices.length; i++) { - slices[i] = buffers[startIndex + i].duplicate().order(ByteOrder.LITTLE_ENDIAN); - } - - // set the last buffer's limit for the sliced view. - slices[slices.length - 1].limit((int) (sliceEnd & chunkSizeMask)); - - return slices; - } - - @Override - public final void close() throws IOException { - try { - if (buffers == null) return; - - // make local copy, then un-set early - final ByteBuffer[] bufs = buffers; - unsetBuffers(); - - if (isClone) return; - - // tell the guard to invalidate and later unmap the bytebuffers (if supported): - guard.invalidateAndUnmap(bufs); - } finally { - unsetBuffers(); - } - } - - /** Called to remove all references to byte buffers, so we can throw AlreadyClosed on NPE. */ - private void unsetBuffers() { - buffers = null; - curBuf = null; - curBufIndex = 0; - curLongBufferViews = null; - curIntBufferViews = null; - } - - /** Optimization of ByteBufferIndexInput for when there is only one buffer */ - static final class SingleBufferImpl extends ByteBufferIndexInput { - - SingleBufferImpl( - String resourceDescription, - ByteBuffer buffer, - long length, - int chunkSizePower, - ByteBufferGuard guard) { - super(resourceDescription, new ByteBuffer[] {buffer}, length, chunkSizePower, guard); - this.curBufIndex = 0; - assert buffer.order() == ByteOrder.LITTLE_ENDIAN; - setCurBuf(buffer); - buffer.position(0); - } - - // TODO: investigate optimizing readByte() & Co? - - @Override - public void seek(long pos) throws IOException { - try { - curBuf.position((int) pos); - } catch (IllegalArgumentException e) { - throw handlePositionalIOOBE(e, "seek", pos); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public long getFilePointer() { - try { - return curBuf.position(); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public byte readByte(long pos) throws IOException { - try { - return guard.getByte(curBuf, (int) pos); - } catch (IllegalArgumentException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readBytes(long pos, byte[] bytes, int offset, int len) throws IOException { - try { - guard.getBytes(curBuf, (int) pos, bytes, offset, len); - } catch (IllegalArgumentException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public short readShort(long pos) throws IOException { - try { - return guard.getShort(curBuf, (int) pos); - } catch (IllegalArgumentException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public int readInt(long pos) throws IOException { - try { - return guard.getInt(curBuf, (int) pos); - } catch (IllegalArgumentException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - - @Override - public long readLong(long pos) throws IOException { - try { - return guard.getLong(curBuf, (int) pos); - } catch (IllegalArgumentException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException e) { - throw alreadyClosed(e); - } - } - } - - /** This class adds offset support to ByteBufferIndexInput, which is needed for slices. */ - static final class MultiBufferImpl extends ByteBufferIndexInput { - private final int offset; - - MultiBufferImpl( - String resourceDescription, - ByteBuffer[] buffers, - int offset, - long length, - int chunkSizePower, - ByteBufferGuard guard) { - super(resourceDescription, buffers, length, chunkSizePower, guard); - this.offset = offset; - try { - seek(0L); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - } - - @Override - RuntimeException handlePositionalIOOBE(RuntimeException unused, String action, long pos) - throws IOException { - return super.handlePositionalIOOBE(unused, action, pos - offset); - } - - @Override - public void seek(long pos) throws IOException { - assert pos >= 0L : "negative position"; - super.seek(pos + offset); - } - - @Override - public long getFilePointer() { - return super.getFilePointer() - offset; - } - - @Override - public byte readByte(long pos) throws IOException { - return super.readByte(pos + offset); - } - - @Override - public void readBytes(long pos, byte[] bytes, int offset, int len) throws IOException { - super.readBytes(pos + this.offset, bytes, offset, len); - } - - @Override - public short readShort(long pos) throws IOException { - return super.readShort(pos + offset); - } - - @Override - public int readInt(long pos) throws IOException { - return super.readInt(pos + offset); - } - - @Override - public long readLong(long pos) throws IOException { - return super.readLong(pos + offset); - } - - @Override - protected ByteBufferIndexInput buildSlice(String sliceDescription, long ofs, long length) { - return super.buildSlice(sliceDescription, this.offset + ofs, length); - } - } -} diff --git a/lucene/core/src/java/org/apache/lucene/store/ByteBuffersDirectory.java b/lucene/core/src/java/org/apache/lucene/store/ByteBuffersDirectory.java index dbefd6a03cc9..dd31d13c0c9e 100644 --- a/lucene/core/src/java/org/apache/lucene/store/ByteBuffersDirectory.java +++ b/lucene/core/src/java/org/apache/lucene/store/ByteBuffersDirectory.java @@ -25,7 +25,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; @@ -36,7 +35,6 @@ import java.util.function.Supplier; import java.util.zip.CRC32; import org.apache.lucene.index.IndexFileNames; -import org.apache.lucene.util.BitUtil; /** * A {@link ByteBuffer}-based {@link Directory} implementation that can be used to store index files @@ -82,45 +80,6 @@ public final class ByteBuffersDirectory extends BaseDirectory { public static final BiFunction OUTPUT_AS_BYTE_ARRAY = OUTPUT_AS_ONE_BUFFER; - /** - * Use {@link ByteBufferIndexInput} for reading, otherwise identical to {@link - * #OUTPUT_AS_MANY_BUFFERS}. - * - * @deprecated Use {@link #OUTPUT_AS_MANY_BUFFERS} instead. - */ - @Deprecated - public static final BiFunction - OUTPUT_AS_MANY_BUFFERS_LUCENE = - (fileName, output) -> { - List bufferList = output.toBufferList(); - bufferList.add(ByteBuffer.allocate(0).order(ByteOrder.LITTLE_ENDIAN)); - - int chunkSizePower; - int blockSize = ByteBuffersDataInput.determineBlockPage(bufferList); - if (blockSize == 0) { - chunkSizePower = 30; - } else { - chunkSizePower = - Integer.numberOfTrailingZeros(BitUtil.nextHighestPowerOfTwo(blockSize)); - } - - String inputName = - String.format( - Locale.ROOT, - "%s (file=%s)", - ByteBuffersDirectory.class.getSimpleName(), - fileName); - - ByteBufferGuard guard = - new ByteBufferGuard("none", (String resourceDescription, ByteBuffer b) -> {}); - return ByteBufferIndexInput.newInstance( - inputName, - bufferList.toArray(new ByteBuffer[bufferList.size()]), - output.size(), - chunkSizePower, - guard); - }; - private final Function tempFileName = new Function() { private final AtomicLong counter = new AtomicLong(); diff --git a/lucene/core/src/java/org/apache/lucene/store/FSDirectory.java b/lucene/core/src/java/org/apache/lucene/store/FSDirectory.java index 22da71384ffb..413e22c45ae8 100644 --- a/lucene/core/src/java/org/apache/lucene/store/FSDirectory.java +++ b/lucene/core/src/java/org/apache/lucene/store/FSDirectory.java @@ -44,31 +44,27 @@ /** * Base class for Directory implementations that store index files in the file system. There are currently three core subclasses: + * id="subclasses"> There are currently two core subclasses: * *

    + *
  • {@link MMapDirectory} uses memory-mapped IO when reading. This is a good choice if you have + * plenty of virtual memory relative to your index size, eg if you are running on a 64 bit + * JRE, or you are running on a 32 bit JRE but your index sizes are small enough to fit into + * the virtual memory space. This class will use the modern {@link + * java.lang.foreign.MemorySegment} API available since Java 21 which allows to safely unmap + * previously mmapped files after closing the {@link IndexInput}s. There is no need to enable + * the "preview feature" of your Java version; it works out of box with some compilation + * tricks. For more information about the foreign memory API read documentation of the {@link + * java.lang.foreign} package and Uwe's blog + * post. *
  • {@link NIOFSDirectory} uses java.nio's FileChannel's positional io when reading to avoid * synchronization when reading from the same file. Unfortunately, due to a Windows-only Sun JRE bug this is a * poor choice for Windows, but on all other platforms this is the preferred choice. * Applications using {@link Thread#interrupt()} or {@link Future#cancel(boolean)} should use - * {@code RAFDirectory} instead. See {@link NIOFSDirectory} java doc for details. - *
  • {@link MMapDirectory} uses memory-mapped IO when reading. This is a good choice if you have - * plenty of virtual memory relative to your index size, eg if you are running on a 64 bit - * JRE, or you are running on a 32 bit JRE but your index sizes are small enough to fit into - * the virtual memory space. Java has currently the limitation of not being able to unmap - * files from user code. The files are unmapped, when GC releases the byte buffers. Due to this bug in Sun's - * JRE, MMapDirectory's {@link IndexInput#close} is unable to close the underlying OS file - * handle. Only when GC finally collects the underlying objects, which could be quite some - * time later, will the file handle be closed. This will consume additional transient disk - * usage: on Windows, attempts to delete or overwrite the files will result in an exception; - * on other platforms, which typically have a "delete on last close" semantics, - * while such operations will succeed, the bytes are still consuming space on disk. For many - * applications this limitation is not a problem (e.g. if you have plenty of disk space, and - * you don't rely on overwriting files on Windows) but it's still an important limitation to - * be aware of. This class supplies a (possibly dangerous) workaround mentioned in the bug - * report, which may fail on non-Sun JVMs. + * {@code RAFDirectory} instead, which is provided in the {@code misc} module. See {@link + * NIOFSDirectory} javadoc for details. *
* *

Unfortunately, because of system peculiarities, there is no single overall best @@ -157,7 +153,7 @@ public static FSDirectory open(Path path) throws IOException { /** Just like {@link #open(Path)}, but allows you to also specify a custom {@link LockFactory}. */ public static FSDirectory open(Path path, LockFactory lockFactory) throws IOException { - if (Constants.JRE_IS_64BIT && MMapDirectory.UNMAP_SUPPORTED) { + if (Constants.JRE_IS_64BIT) { return new MMapDirectory(path, lockFactory); } else { return new NIOFSDirectory(path, lockFactory); diff --git a/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java b/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java index 3bf8683266ac..5638f7c33d02 100644 --- a/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java +++ b/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java @@ -21,15 +21,10 @@ import java.lang.invoke.MethodType; import java.nio.channels.ClosedChannelException; // javadoc @link import java.nio.file.Path; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Locale; -import java.util.Optional; import java.util.concurrent.Future; import java.util.function.BiPredicate; -import java.util.logging.Logger; import org.apache.lucene.util.Constants; -import org.apache.lucene.util.SuppressForbidden; /** * File-based {@link Directory} implementation that uses mmap for reading, and {@link @@ -40,45 +35,18 @@ * plenty of virtual address space, e.g. by using a 64 bit JRE, or a 32 bit JRE with indexes that * are guaranteed to fit within the address space. On 32 bit platforms also consult {@link * #MMapDirectory(Path, LockFactory, long)} if you have problems with mmap failing because of - * fragmented address space. If you get an OutOfMemoryException, it is recommended to reduce the - * chunk size, until it works. + * fragmented address space. If you get an {@link IOException} about mapping failed, it is + * recommended to reduce the chunk size, until it works. * *

This class supports preloading files into physical memory upon opening. This can help improve * performance of searches on a cold page cache at the expense of slowing down opening an index. See * {@link #setPreload(BiPredicate)} for more details. * - *

Due to this bug in OpenJDK, - * MMapDirectory's {@link IndexInput#close} is unable to close the underlying OS file handle. Only - * when GC finally collects the underlying objects, which could be quite some time later, will the - * file handle be closed. - * - *

This will consume additional transient disk usage: on Windows, attempts to delete or overwrite - * the files will result in an exception; on other platforms, which typically have a "delete on - * last close" semantics, while such operations will succeed, the bytes are still consuming - * space on disk. For many applications this limitation is not a problem (e.g. if you have plenty of - * disk space, and you don't rely on overwriting files on Windows) but it's still an important - * limitation to be aware of. - * - *

This class supplies the workaround mentioned in the bug report, which may fail on - * non-Oracle/OpenJDK JVMs. It forcefully unmaps the buffer on close by using an undocumented - * internal cleanup functionality. If {@link #UNMAP_SUPPORTED} is true, the workaround - * will be automatically enabled (with no guarantees; if you discover any problems, you can disable - * it by using system property {@link #ENABLE_UNMAP_HACK_SYSPROP}). - * - *

For the hack to work correct, the following requirements need to be fulfilled: The used JVM - * must be at least Oracle Java / OpenJDK. In addition, the following permissions need to be granted - * to {@code lucene-core.jar} in your policy - * file: - * - *

    - *
  • {@code permission java.lang.reflect.ReflectPermission "suppressAccessChecks";} - *
  • {@code permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";} - *
- * - *

On exactly Java 19 / 20 / 21 this class will use the modern {@code MemorySegment} API - * which allows to safely unmap (if you discover any problems with this preview API, you can disable - * it by using system property {@link #ENABLE_MEMORY_SEGMENTS_SYSPROP}). + *

This class will use the modern {@link java.lang.foreign.MemorySegment} API available since + * Java 21 which allows to safely unmap previously mmapped files after closing the {@link + * IndexInput}s. There is no need to enable the "preview feature" of your Java version; it works out + * of box with some compilation tricks. For more information about the foreign memory API read + * documentation of the {@link java.lang.foreign} package. * *

NOTE: Accessing this class either directly or indirectly from a thread while it's * interrupted can close the underlying channel immediately if at the same time the thread is @@ -91,13 +59,11 @@ * synchronize on the MMapDirectory instance as this may cause deadlock; use your own * (non-Lucene) objects instead. * - * @see Blog post + * @see Blog post * about MMapDirectory */ public class MMapDirectory extends FSDirectory { - private static final Logger LOG = Logger.getLogger(MMapDirectory.class.getName()); - /** * Argument for {@link #setPreload(BiPredicate)} that configures all files to be preloaded upon * opening them. @@ -123,35 +89,12 @@ public class MMapDirectory extends FSDirectory { * Default max chunk size: * *

    - *
  • 16 GiBytes for 64 bit Java 19 / 20 / 21 JVMs - *
  • 1 GiBytes for other 64 bit JVMs + *
  • 16 GiBytes for 64 bit JVMs *
  • 256 MiBytes for 32 bit JVMs *
*/ public static final long DEFAULT_MAX_CHUNK_SIZE; - /** - * This sysprop allows to control the workaround/hack for unmapping the buffers from address space - * after closing {@link IndexInput}. By default it is enabled; set to {@code false} to disable the - * unmap hack globally. On command line pass {@code - * -Dorg.apache.lucene.store.MMapDirectory.enableUnmapHack=false} to disable. - * - * @lucene.internal - */ - public static final String ENABLE_UNMAP_HACK_SYSPROP = - "org.apache.lucene.store.MMapDirectory.enableUnmapHack"; - - /** - * This sysprop allows to control if {@code MemorySegment} API should be used on supported Java - * versions. By default it is enabled; set to {@code false} to use legacy {@code ByteBuffer} - * implementation. On command line pass {@code - * -Dorg.apache.lucene.store.MMapDirectory.enableMemorySegments=false} to disable. - * - * @lucene.internal - */ - public static final String ENABLE_MEMORY_SEGMENTS_SYSPROP = - "org.apache.lucene.store.MMapDirectory.enableMemorySegments"; - final int chunkSizePower; /** @@ -255,25 +198,12 @@ public IndexInput openInput(String name, IOContext context) throws IOException { // visible for tests: static final MMapIndexInputProvider PROVIDER; - /** true, if this platform supports unmapping mmapped files. */ - public static final boolean UNMAP_SUPPORTED; - - /** - * if {@link #UNMAP_SUPPORTED} is {@code false}, this contains the reason why unmapping is not - * supported. - */ - public static final String UNMAP_NOT_SUPPORTED_REASON; - interface MMapIndexInputProvider { IndexInput openInput(Path path, IOContext context, int chunkSizePower, boolean preload) throws IOException; long getDefaultMaxChunkSize(); - boolean isUnmapSupported(); - - String getUnmapNotSupportedReason(); - default IOException convertMapFailedIOException( IOException ioe, String resourceDescription, long bufSize) { final String originalMessage; @@ -317,63 +247,30 @@ default IOException convertMapFailedIOException( } } - // Extracted to a method to be able to apply the SuppressForbidden annotation - @SuppressWarnings("removal") - @SuppressForbidden(reason = "security manager") - private static T doPrivileged(PrivilegedAction action) { - return AccessController.doPrivileged(action); - } - - private static boolean checkMemorySegmentsSysprop() { - try { - return Optional.ofNullable(System.getProperty(ENABLE_MEMORY_SEGMENTS_SYSPROP)) - .map(Boolean::valueOf) - .orElse(Boolean.TRUE); - } catch ( - @SuppressWarnings("unused") - SecurityException ignored) { - LOG.warning( - "Cannot read sysprop " - + ENABLE_MEMORY_SEGMENTS_SYSPROP - + ", so MemorySegments will be enabled by default, if possible."); - return true; - } - } - private static MMapIndexInputProvider lookupProvider() { - if (checkMemorySegmentsSysprop() == false) { - return new MappedByteBufferIndexInputProvider(); - } final var lookup = MethodHandles.lookup(); - final int runtimeVersion = Runtime.version().feature(); - if (runtimeVersion >= 19) { + try { + final var cls = lookup.findClass("org.apache.lucene.store.MemorySegmentIndexInputProvider"); + // we use method handles, so we do not need to deal with setAccessible as we have private + // access through the lookup: + final var constr = lookup.findConstructor(cls, MethodType.methodType(void.class)); try { - final var cls = lookup.findClass("org.apache.lucene.store.MemorySegmentIndexInputProvider"); - // we use method handles, so we do not need to deal with setAccessible as we have private - // access through the lookup: - final var constr = lookup.findConstructor(cls, MethodType.methodType(void.class)); - try { - return (MMapIndexInputProvider) constr.invoke(); - } catch (RuntimeException | Error e) { - throw e; - } catch (Throwable th) { - throw new AssertionError(th); - } - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new LinkageError( - "MemorySegmentIndexInputProvider is missing correctly typed constructor", e); - } catch (ClassNotFoundException cnfe) { - throw new LinkageError( - "MemorySegmentIndexInputProvider is missing in Lucene JAR file", cnfe); + return (MMapIndexInputProvider) constr.invoke(); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable th) { + throw new AssertionError(th); } + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new LinkageError( + "MemorySegmentIndexInputProvider is missing correctly typed constructor", e); + } catch (ClassNotFoundException cnfe) { + throw new LinkageError("MemorySegmentIndexInputProvider is missing in Lucene JAR file", cnfe); } - return new MappedByteBufferIndexInputProvider(); } static { - PROVIDER = doPrivileged(MMapDirectory::lookupProvider); + PROVIDER = lookupProvider(); DEFAULT_MAX_CHUNK_SIZE = PROVIDER.getDefaultMaxChunkSize(); - UNMAP_SUPPORTED = PROVIDER.isUnmapSupported(); - UNMAP_NOT_SUPPORTED_REASON = PROVIDER.getUnmapNotSupportedReason(); } } diff --git a/lucene/core/src/java/org/apache/lucene/store/MappedByteBufferIndexInputProvider.java b/lucene/core/src/java/org/apache/lucene/store/MappedByteBufferIndexInputProvider.java deleted file mode 100644 index 9f631ada5c55..000000000000 --- a/lucene/core/src/java/org/apache/lucene/store/MappedByteBufferIndexInputProvider.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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. - */ -package org.apache.lucene.store; - -import static java.lang.invoke.MethodHandles.lookup; -import static java.lang.invoke.MethodType.methodType; - -import java.io.IOException; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.Field; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileChannel.MapMode; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Objects; -import java.util.Optional; -import java.util.logging.Logger; -import org.apache.lucene.store.ByteBufferGuard.BufferCleaner; -import org.apache.lucene.util.Constants; -import org.apache.lucene.util.SuppressForbidden; - -final class MappedByteBufferIndexInputProvider implements MMapDirectory.MMapIndexInputProvider { - - private static final Logger LOG = - Logger.getLogger(MappedByteBufferIndexInputProvider.class.getName()); - - private final BufferCleaner cleaner; - - private final boolean unmapSupported; - private final String unmapNotSupportedReason; - - public MappedByteBufferIndexInputProvider() { - final Object hack = unmapHackImpl(); - if (hack instanceof BufferCleaner) { - cleaner = (BufferCleaner) hack; - unmapSupported = true; - unmapNotSupportedReason = null; - } else { - cleaner = null; - unmapSupported = false; - unmapNotSupportedReason = hack.toString(); - LOG.warning(unmapNotSupportedReason); - } - } - - @Override - public IndexInput openInput(Path path, IOContext context, int chunkSizePower, boolean preload) - throws IOException { - if (chunkSizePower > 30) { - throw new IllegalArgumentException( - "ByteBufferIndexInput cannot use a chunk size of >1 GiBytes."); - } - - final String resourceDescription = "ByteBufferIndexInput(path=\"" + path.toString() + "\")"; - - try (var fc = FileChannel.open(path, StandardOpenOption.READ)) { - final long fileSize = fc.size(); - return ByteBufferIndexInput.newInstance( - resourceDescription, - map(resourceDescription, fc, chunkSizePower, preload, fileSize), - fileSize, - chunkSizePower, - new ByteBufferGuard(resourceDescription, cleaner)); - } - } - - @Override - public long getDefaultMaxChunkSize() { - return Constants.JRE_IS_64BIT ? (1L << 30) : (1L << 28); - } - - @Override - public boolean isUnmapSupported() { - return unmapSupported; - } - - @Override - public String getUnmapNotSupportedReason() { - return unmapNotSupportedReason; - } - - /** Maps a file into a set of buffers */ - final ByteBuffer[] map( - String resourceDescription, FileChannel fc, int chunkSizePower, boolean preload, long length) - throws IOException { - if ((length >>> chunkSizePower) >= Integer.MAX_VALUE) - throw new IllegalArgumentException( - "RandomAccessFile too big for chunk size: " + resourceDescription); - - final long chunkSize = 1L << chunkSizePower; - - // we always allocate one more buffer, the last one may be a 0 byte one - final int nrBuffers = (int) (length >>> chunkSizePower) + 1; - - final ByteBuffer[] buffers = new ByteBuffer[nrBuffers]; - - long startOffset = 0L; - for (int bufNr = 0; bufNr < nrBuffers; bufNr++) { - final int bufSize = - (int) ((length > (startOffset + chunkSize)) ? chunkSize : (length - startOffset)); - final MappedByteBuffer buffer; - try { - buffer = fc.map(MapMode.READ_ONLY, startOffset, bufSize); - buffer.order(ByteOrder.LITTLE_ENDIAN); - } catch (IOException ioe) { - throw convertMapFailedIOException(ioe, resourceDescription, bufSize); - } - if (preload) { - buffer.load(); - } - buffers[bufNr] = buffer; - startOffset += bufSize; - } - - return buffers; - } - - private static boolean checkUnmapHackSysprop() { - try { - return Optional.ofNullable(System.getProperty(MMapDirectory.ENABLE_UNMAP_HACK_SYSPROP)) - .map(Boolean::valueOf) - .orElse(Boolean.TRUE); - } catch ( - @SuppressWarnings("unused") - SecurityException ignored) { - LOG.warning( - "Cannot read sysprop " - + MMapDirectory.ENABLE_UNMAP_HACK_SYSPROP - + ", so buffer unmap hack will be enabled by default, if possible."); - return true; - } - } - - @SuppressForbidden(reason = "Needs access to sun.misc.Unsafe to enable hack") - private static Object unmapHackImpl() { - if (checkUnmapHackSysprop() == false) { - return "Unmapping was disabled by system property " - + MMapDirectory.ENABLE_UNMAP_HACK_SYSPROP - + "=false"; - } - final Lookup lookup = lookup(); - try { - // *** sun.misc.Unsafe unmapping (Java 9+) *** - final Class unsafeClass = lookup.findClass("sun.misc.Unsafe"); - // first check if Unsafe has the right method, otherwise we can give up - // without doing any security critical stuff: - final MethodHandle unmapper = - lookup.findVirtual( - unsafeClass, "invokeCleaner", methodType(void.class, ByteBuffer.class)); - // fetch the unsafe instance and bind it to the virtual MH: - final Field f = unsafeClass.getDeclaredField("theUnsafe"); - f.setAccessible(true); - final Object theUnsafe = f.get(null); - return newBufferCleaner(unmapper.bindTo(theUnsafe)); - } catch (SecurityException se) { - return "Unmapping is not supported, because not all required permissions are given to the Lucene JAR file: " - + se - + " [Please grant at least the following permissions: RuntimePermission(\"accessClassInPackage.sun.misc\") " - + " and ReflectPermission(\"suppressAccessChecks\")]"; - } catch (ReflectiveOperationException | RuntimeException e) { - final Module module = MappedByteBufferIndexInputProvider.class.getModule(); - final ModuleLayer layer = module.getLayer(); - // classpath / unnamed module has no layer, so we need to check: - if (layer != null - && layer.findModule("jdk.unsupported").map(module::canRead).orElse(false) == false) { - return "Unmapping is not supported, because Lucene cannot read 'jdk.unsupported' module " - + "[please add 'jdk.unsupported' to modular application either by command line or its module descriptor]"; - } - return "Unmapping is not supported on this platform, because internal Java APIs are not compatible with this Lucene version: " - + e; - } - } - - private static BufferCleaner newBufferCleaner(final MethodHandle unmapper) { - assert Objects.equals(methodType(void.class, ByteBuffer.class), unmapper.type()); - return (String resourceDescription, ByteBuffer buffer) -> { - if (!buffer.isDirect()) { - throw new IllegalArgumentException("unmapping only works with direct buffers"); - } - try { - unmapper.invokeExact(buffer); - } catch (Throwable t) { - throw new IOException("Unable to unmap the mapped buffer: " + resourceDescription, t); - } - }; - } -} diff --git a/lucene/core/src/java/org/apache/lucene/store/NRTCachingDirectory.java b/lucene/core/src/java/org/apache/lucene/store/NRTCachingDirectory.java index d32498c16639..e5d93fb74ab5 100644 --- a/lucene/core/src/java/org/apache/lucene/store/NRTCachingDirectory.java +++ b/lucene/core/src/java/org/apache/lucene/store/NRTCachingDirectory.java @@ -74,7 +74,7 @@ public class NRTCachingDirectory extends FilterDirectory implements Accountable ByteBuffersDataOutput::new, (fileName, content) -> { cacheSize.addAndGet(content.size()); - return ByteBuffersDirectory.OUTPUT_AS_MANY_BUFFERS_LUCENE.apply(fileName, content); + return ByteBuffersDirectory.OUTPUT_AS_MANY_BUFFERS.apply(fileName, content); }); private final long maxMergeSizeBytes; diff --git a/lucene/core/src/java19/org/apache/lucene/store/MemorySegmentIndexInput.java b/lucene/core/src/java19/org/apache/lucene/store/MemorySegmentIndexInput.java deleted file mode 100644 index ff2ab9993198..000000000000 --- a/lucene/core/src/java19/org/apache/lucene/store/MemorySegmentIndexInput.java +++ /dev/null @@ -1,683 +0,0 @@ -/* - * 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. - */ -package org.apache.lucene.store; - -import java.io.EOFException; -import java.io.IOException; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.MemorySession; -import java.lang.foreign.ValueLayout; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Objects; -import org.apache.lucene.util.ArrayUtil; -import org.apache.lucene.util.GroupVIntUtil; - -/** - * Base IndexInput implementation that uses an array of MemorySegments to represent a file. - * - *

For efficiency, this class requires that the segment size are a power-of-two ( - * chunkSizePower). - */ -@SuppressWarnings("preview") -abstract class MemorySegmentIndexInput extends IndexInput implements RandomAccessInput { - static final ValueLayout.OfByte LAYOUT_BYTE = ValueLayout.JAVA_BYTE; - static final ValueLayout.OfShort LAYOUT_LE_SHORT = - ValueLayout.JAVA_SHORT.withOrder(ByteOrder.LITTLE_ENDIAN).withBitAlignment(8); - static final ValueLayout.OfInt LAYOUT_LE_INT = - ValueLayout.JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN).withBitAlignment(8); - static final ValueLayout.OfLong LAYOUT_LE_LONG = - ValueLayout.JAVA_LONG.withOrder(ByteOrder.LITTLE_ENDIAN).withBitAlignment(8); - static final ValueLayout.OfFloat LAYOUT_LE_FLOAT = - ValueLayout.JAVA_FLOAT.withOrder(ByteOrder.LITTLE_ENDIAN).withBitAlignment(8); - - final long length; - final long chunkSizeMask; - final int chunkSizePower; - final MemorySession session; - final MemorySegment[] segments; - - int curSegmentIndex = -1; - MemorySegment - curSegment; // redundant for speed: segments[curSegmentIndex], also marker if closed! - long curPosition; // relative to curSegment, not globally - - public static MemorySegmentIndexInput newInstance( - String resourceDescription, - MemorySession session, - MemorySegment[] segments, - long length, - int chunkSizePower) { - assert Arrays.stream(segments).map(MemorySegment::session).allMatch(session::equals); - if (segments.length == 1) { - return new SingleSegmentImpl( - resourceDescription, session, segments[0], length, chunkSizePower); - } else { - return new MultiSegmentImpl( - resourceDescription, session, segments, 0, length, chunkSizePower); - } - } - - private MemorySegmentIndexInput( - String resourceDescription, - MemorySession session, - MemorySegment[] segments, - long length, - int chunkSizePower) { - super(resourceDescription); - this.session = session; - this.segments = segments; - this.length = length; - this.chunkSizePower = chunkSizePower; - this.chunkSizeMask = (1L << chunkSizePower) - 1L; - this.curSegment = segments[0]; - } - - void ensureOpen() { - if (curSegment == null) { - throw alreadyClosed(null); - } - } - - // the unused parameter is just to silence javac about unused variables - RuntimeException handlePositionalIOOBE(RuntimeException unused, String action, long pos) - throws IOException { - if (pos < 0L) { - return new IllegalArgumentException(action + " negative position (pos=" + pos + "): " + this); - } else { - throw new EOFException(action + " past EOF (pos=" + pos + "): " + this); - } - } - - AlreadyClosedException alreadyClosed(RuntimeException e) { - // we use NPE to signal if this input is closed (to not have checks everywhere). If NPE happens, - // we check the "is closed" condition explicitly by checking that our "curSegment" is null. - // Care must be taken to not leak the NPE to code outside MemorySegmentIndexInput! - if (this.curSegment == null) { - return new AlreadyClosedException("Already closed: " + this); - } - // ISE can be thrown by MemorySegment and contains "closed" in message: - if (e instanceof IllegalStateException - && e.getMessage() != null - && e.getMessage().contains("closed")) { - return new AlreadyClosedException("Already closed: " + this, e); - } - // otherwise rethrow unmodified NPE/ISE (as it possibly a bug with passing a null parameter to - // the IndexInput method): - throw e; - } - - @Override - public final byte readByte() throws IOException { - try { - final byte v = curSegment.get(LAYOUT_BYTE, curPosition); - curPosition++; - return v; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - do { - curSegmentIndex++; - if (curSegmentIndex >= segments.length) { - throw new EOFException("read past EOF: " + this); - } - curSegment = segments[curSegmentIndex]; - curPosition = 0L; - } while (curSegment.byteSize() == 0L); - final byte v = curSegment.get(LAYOUT_BYTE, curPosition); - curPosition++; - return v; - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final void readBytes(byte[] b, int offset, int len) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_BYTE, curPosition, b, offset, len); - curPosition += len; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - readBytesBoundary(b, offset, len); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - private void readBytesBoundary(byte[] b, int offset, int len) throws IOException { - try { - long curAvail = curSegment.byteSize() - curPosition; - while (len > curAvail) { - MemorySegment.copy(curSegment, LAYOUT_BYTE, curPosition, b, offset, (int) curAvail); - len -= curAvail; - offset += curAvail; - curSegmentIndex++; - if (curSegmentIndex >= segments.length) { - throw new EOFException("read past EOF: " + this); - } - curSegment = segments[curSegmentIndex]; - curPosition = 0L; - curAvail = curSegment.byteSize(); - } - MemorySegment.copy(curSegment, LAYOUT_BYTE, curPosition, b, offset, len); - curPosition += len; - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readInts(int[] dst, int offset, int length) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_LE_INT, curPosition, dst, offset, length); - curPosition += Integer.BYTES * (long) length; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException iobe) { - super.readInts(dst, offset, length); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readLongs(long[] dst, int offset, int length) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_LE_LONG, curPosition, dst, offset, length); - curPosition += Long.BYTES * (long) length; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException iobe) { - super.readLongs(dst, offset, length); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readFloats(float[] dst, int offset, int length) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_LE_FLOAT, curPosition, dst, offset, length); - curPosition += Float.BYTES * (long) length; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException iobe) { - super.readFloats(dst, offset, length); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final short readShort() throws IOException { - try { - final short v = curSegment.get(LAYOUT_LE_SHORT, curPosition); - curPosition += Short.BYTES; - return v; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - return super.readShort(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final int readInt() throws IOException { - try { - final int v = curSegment.get(LAYOUT_LE_INT, curPosition); - curPosition += Integer.BYTES; - return v; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - return super.readInt(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final int readVInt() throws IOException { - // this can make JVM less confused (see LUCENE-10366) - return super.readVInt(); - } - - @Override - public final long readVLong() throws IOException { - // this can make JVM less confused (see LUCENE-10366) - return super.readVLong(); - } - - @Override - public final long readLong() throws IOException { - try { - final long v = curSegment.get(LAYOUT_LE_LONG, curPosition); - curPosition += Long.BYTES; - return v; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - return super.readLong(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public long getFilePointer() { - ensureOpen(); - return (((long) curSegmentIndex) << chunkSizePower) + curPosition; - } - - @Override - public void seek(long pos) throws IOException { - ensureOpen(); - // we use >> here to preserve negative, so we will catch AIOOBE, - // in case pos + offset overflows. - final int si = (int) (pos >> chunkSizePower); - try { - if (si != curSegmentIndex) { - final MemorySegment seg = segments[si]; - // write values, on exception all is unchanged - this.curSegmentIndex = si; - this.curSegment = seg; - } - this.curPosition = Objects.checkIndex(pos & chunkSizeMask, curSegment.byteSize() + 1); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "seek", pos); - } - } - - @Override - public byte readByte(long pos) throws IOException { - try { - final int si = (int) (pos >> chunkSizePower); - return segments[si].get(LAYOUT_BYTE, pos & chunkSizeMask); - } catch (IndexOutOfBoundsException ioobe) { - throw handlePositionalIOOBE(ioobe, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - protected void readGroupVInt(long[] dst, int offset) throws IOException { - try { - final int len = - GroupVIntUtil.readGroupVInt( - this, - curSegment.byteSize() - curPosition, - p -> curSegment.get(LAYOUT_LE_INT, p), - curPosition, - dst, - offset); - curPosition += len; - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readBytes(long pos, byte[] b, int offset, int len) throws IOException { - try { - int si = (int) (pos >> chunkSizePower); - pos = pos & chunkSizeMask; - long curAvail = segments[si].byteSize() - pos; - while (len > curAvail) { - MemorySegment.copy(segments[si], LAYOUT_BYTE, pos, b, offset, (int) curAvail); - len -= curAvail; - offset += curAvail; - si++; - if (si >= segments.length) { - throw new EOFException("read past EOF: " + this); - } - pos = 0L; - curAvail = segments[si].byteSize(); - } - MemorySegment.copy(segments[si], LAYOUT_BYTE, pos, b, offset, len); - } catch (IndexOutOfBoundsException ioobe) { - throw handlePositionalIOOBE(ioobe, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - // used only by random access methods to handle reads across boundaries - private void setPos(long pos, int si) throws IOException { - try { - final MemorySegment seg = segments[si]; - // write values, on exception above all is unchanged - this.curPosition = pos & chunkSizeMask; - this.curSegmentIndex = si; - this.curSegment = seg; - } catch (IndexOutOfBoundsException ioobe) { - throw handlePositionalIOOBE(ioobe, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public short readShort(long pos) throws IOException { - final int si = (int) (pos >> chunkSizePower); - try { - return segments[si].get(LAYOUT_LE_SHORT, pos & chunkSizeMask); - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException ioobe) { - // either it's a boundary, or read past EOF, fall back: - setPos(pos, si); - return readShort(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public int readInt(long pos) throws IOException { - final int si = (int) (pos >> chunkSizePower); - try { - return segments[si].get(LAYOUT_LE_INT, pos & chunkSizeMask); - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException ioobe) { - // either it's a boundary, or read past EOF, fall back: - setPos(pos, si); - return readInt(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public long readLong(long pos) throws IOException { - final int si = (int) (pos >> chunkSizePower); - try { - return segments[si].get(LAYOUT_LE_LONG, pos & chunkSizeMask); - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException ioobe) { - // either it's a boundary, or read past EOF, fall back: - setPos(pos, si); - return readLong(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final long length() { - return length; - } - - @Override - public final MemorySegmentIndexInput clone() { - final MemorySegmentIndexInput clone = buildSlice((String) null, 0L, this.length); - try { - clone.seek(getFilePointer()); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - - return clone; - } - - /** - * Creates a slice of this index input, with the given description, offset, and length. The slice - * is seeked to the beginning. - */ - @Override - public final MemorySegmentIndexInput slice(String sliceDescription, long offset, long length) { - if (offset < 0 || length < 0 || offset + length > this.length) { - throw new IllegalArgumentException( - "slice() " - + sliceDescription - + " out of bounds: offset=" - + offset - + ",length=" - + length - + ",fileLength=" - + this.length - + ": " - + this); - } - - return buildSlice(sliceDescription, offset, length); - } - - /** Builds the actual sliced IndexInput (may apply extra offset in subclasses). * */ - MemorySegmentIndexInput buildSlice(String sliceDescription, long offset, long length) { - ensureOpen(); - - final long sliceEnd = offset + length; - final int startIndex = (int) (offset >>> chunkSizePower); - final int endIndex = (int) (sliceEnd >>> chunkSizePower); - - // we always allocate one more slice, the last one may be a 0 byte one after truncating with - // asSlice(): - final MemorySegment slices[] = ArrayUtil.copyOfSubArray(segments, startIndex, endIndex + 1); - - // set the last segment's limit for the sliced view. - slices[slices.length - 1] = slices[slices.length - 1].asSlice(0L, sliceEnd & chunkSizeMask); - - offset = offset & chunkSizeMask; - - final String newResourceDescription = getFullSliceDescription(sliceDescription); - if (slices.length == 1) { - return new SingleSegmentImpl( - newResourceDescription, - null, // clones don't have a MemorySession, as they can't close) - slices[0].asSlice(offset, length), - length, - chunkSizePower); - } else { - return new MultiSegmentImpl( - newResourceDescription, - null, // clones don't have a MemorySession, as they can't close) - slices, - offset, - length, - chunkSizePower); - } - } - - @Override - public final void close() throws IOException { - if (curSegment == null) { - return; - } - - // the master IndexInput has a MemorySession and is able - // to release all resources (unmap segments) - a - // side effect is that other threads still using clones - // will throw IllegalStateException - if (session != null) { - while (session.isAlive()) { - try { - session.close(); - break; - } catch ( - @SuppressWarnings("unused") - IllegalStateException e) { - Thread.onSpinWait(); - } - } - } - - // make sure all accesses to this IndexInput instance throw NPE: - curSegment = null; - Arrays.fill(segments, null); - } - - /** Optimization of MemorySegmentIndexInput for when there is only one segment. */ - static final class SingleSegmentImpl extends MemorySegmentIndexInput { - - SingleSegmentImpl( - String resourceDescription, - MemorySession session, - MemorySegment segment, - long length, - int chunkSizePower) { - super(resourceDescription, session, new MemorySegment[] {segment}, length, chunkSizePower); - this.curSegmentIndex = 0; - } - - @Override - public void seek(long pos) throws IOException { - ensureOpen(); - try { - curPosition = Objects.checkIndex(pos, length + 1); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "seek", pos); - } - } - - @Override - public long getFilePointer() { - ensureOpen(); - return curPosition; - } - - @Override - public byte readByte(long pos) throws IOException { - try { - return curSegment.get(LAYOUT_BYTE, pos); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readBytes(long pos, byte[] bytes, int offset, int length) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_BYTE, pos, bytes, offset, length); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public short readShort(long pos) throws IOException { - try { - return curSegment.get(LAYOUT_LE_SHORT, pos); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public int readInt(long pos) throws IOException { - try { - return curSegment.get(LAYOUT_LE_INT, pos); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public long readLong(long pos) throws IOException { - try { - return curSegment.get(LAYOUT_LE_LONG, pos); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - } - - /** This class adds offset support to MemorySegmentIndexInput, which is needed for slices. */ - static final class MultiSegmentImpl extends MemorySegmentIndexInput { - private final long offset; - - MultiSegmentImpl( - String resourceDescription, - MemorySession session, - MemorySegment[] segments, - long offset, - long length, - int chunkSizePower) { - super(resourceDescription, session, segments, length, chunkSizePower); - this.offset = offset; - try { - seek(0L); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - assert curSegment != null && curSegmentIndex >= 0; - } - - @Override - RuntimeException handlePositionalIOOBE(RuntimeException unused, String action, long pos) - throws IOException { - return super.handlePositionalIOOBE(unused, action, pos - offset); - } - - @Override - public void seek(long pos) throws IOException { - assert pos >= 0L : "negative position"; - super.seek(pos + offset); - } - - @Override - public long getFilePointer() { - return super.getFilePointer() - offset; - } - - @Override - public byte readByte(long pos) throws IOException { - return super.readByte(pos + offset); - } - - @Override - public void readBytes(long pos, byte[] bytes, int offset, int length) throws IOException { - super.readBytes(pos + this.offset, bytes, offset, length); - } - - @Override - public short readShort(long pos) throws IOException { - return super.readShort(pos + offset); - } - - @Override - public int readInt(long pos) throws IOException { - return super.readInt(pos + offset); - } - - @Override - public long readLong(long pos) throws IOException { - return super.readLong(pos + offset); - } - - @Override - MemorySegmentIndexInput buildSlice(String sliceDescription, long ofs, long length) { - return super.buildSlice(sliceDescription, this.offset + ofs, length); - } - } -} diff --git a/lucene/core/src/java19/org/apache/lucene/store/MemorySegmentIndexInputProvider.java b/lucene/core/src/java19/org/apache/lucene/store/MemorySegmentIndexInputProvider.java deleted file mode 100644 index deaca72ae412..000000000000 --- a/lucene/core/src/java19/org/apache/lucene/store/MemorySegmentIndexInputProvider.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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. - */ -package org.apache.lucene.store; - -import java.io.IOException; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.MemorySession; -import java.nio.channels.FileChannel; -import java.nio.channels.FileChannel.MapMode; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.logging.Logger; -import org.apache.lucene.util.Constants; -import org.apache.lucene.util.Unwrappable; - -@SuppressWarnings("preview") -final class MemorySegmentIndexInputProvider implements MMapDirectory.MMapIndexInputProvider { - - public MemorySegmentIndexInputProvider() { - var log = Logger.getLogger(getClass().getName()); - log.info( - "Using MemorySegmentIndexInput with Java 19; to disable start with -D" - + MMapDirectory.ENABLE_MEMORY_SEGMENTS_SYSPROP - + "=false"); - } - - @Override - public IndexInput openInput(Path path, IOContext context, int chunkSizePower, boolean preload) - throws IOException { - final String resourceDescription = "MemorySegmentIndexInput(path=\"" + path.toString() + "\")"; - - // Work around for JDK-8259028: we need to unwrap our test-only file system layers - path = Unwrappable.unwrapAll(path); - - boolean success = false; - final MemorySession session = MemorySession.openShared(); - try (var fc = FileChannel.open(path, StandardOpenOption.READ)) { - final long fileSize = fc.size(); - final IndexInput in = - MemorySegmentIndexInput.newInstance( - resourceDescription, - session, - map(session, resourceDescription, fc, chunkSizePower, preload, fileSize), - fileSize, - chunkSizePower); - success = true; - return in; - } finally { - if (success == false) { - session.close(); - } - } - } - - @Override - public long getDefaultMaxChunkSize() { - return Constants.JRE_IS_64BIT ? (1L << 34) : (1L << 28); - } - - @Override - public boolean isUnmapSupported() { - return true; - } - - @Override - public String getUnmapNotSupportedReason() { - return null; - } - - private final MemorySegment[] map( - MemorySession session, - String resourceDescription, - FileChannel fc, - int chunkSizePower, - boolean preload, - long length) - throws IOException { - if ((length >>> chunkSizePower) >= Integer.MAX_VALUE) - throw new IllegalArgumentException("File too big for chunk size: " + resourceDescription); - - final long chunkSize = 1L << chunkSizePower; - - // we always allocate one more segments, the last one may be a 0 byte one - final int nrSegments = (int) (length >>> chunkSizePower) + 1; - - final MemorySegment[] segments = new MemorySegment[nrSegments]; - - long startOffset = 0L; - for (int segNr = 0; segNr < nrSegments; segNr++) { - final long segSize = - (length > (startOffset + chunkSize)) ? chunkSize : (length - startOffset); - final MemorySegment segment; - try { - segment = fc.map(MapMode.READ_ONLY, startOffset, segSize, session); - } catch (IOException ioe) { - throw convertMapFailedIOException(ioe, resourceDescription, segSize); - } - if (preload) { - segment.load(); - } - segments[segNr] = segment; - startOffset += segSize; - } - return segments; - } -} diff --git a/lucene/core/src/java20/org/apache/lucene/store/MemorySegmentIndexInput.java b/lucene/core/src/java20/org/apache/lucene/store/MemorySegmentIndexInput.java deleted file mode 100644 index ba890d8056c7..000000000000 --- a/lucene/core/src/java20/org/apache/lucene/store/MemorySegmentIndexInput.java +++ /dev/null @@ -1,681 +0,0 @@ -/* - * 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. - */ -package org.apache.lucene.store; - -import java.io.EOFException; -import java.io.IOException; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Objects; -import org.apache.lucene.util.ArrayUtil; -import org.apache.lucene.util.GroupVIntUtil; - -/** - * Base IndexInput implementation that uses an array of MemorySegments to represent a file. - * - *

For efficiency, this class requires that the segment size are a power-of-two ( - * chunkSizePower). - */ -@SuppressWarnings("preview") -abstract class MemorySegmentIndexInput extends IndexInput implements RandomAccessInput { - static final ValueLayout.OfByte LAYOUT_BYTE = ValueLayout.JAVA_BYTE; - static final ValueLayout.OfShort LAYOUT_LE_SHORT = - ValueLayout.JAVA_SHORT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); - static final ValueLayout.OfInt LAYOUT_LE_INT = - ValueLayout.JAVA_INT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); - static final ValueLayout.OfLong LAYOUT_LE_LONG = - ValueLayout.JAVA_LONG_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); - static final ValueLayout.OfFloat LAYOUT_LE_FLOAT = - ValueLayout.JAVA_FLOAT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); - - final long length; - final long chunkSizeMask; - final int chunkSizePower; - final Arena arena; - final MemorySegment[] segments; - - int curSegmentIndex = -1; - MemorySegment - curSegment; // redundant for speed: segments[curSegmentIndex], also marker if closed! - long curPosition; // relative to curSegment, not globally - - public static MemorySegmentIndexInput newInstance( - String resourceDescription, - Arena arena, - MemorySegment[] segments, - long length, - int chunkSizePower) { - assert Arrays.stream(segments).map(MemorySegment::scope).allMatch(arena.scope()::equals); - if (segments.length == 1) { - return new SingleSegmentImpl(resourceDescription, arena, segments[0], length, chunkSizePower); - } else { - return new MultiSegmentImpl(resourceDescription, arena, segments, 0, length, chunkSizePower); - } - } - - private MemorySegmentIndexInput( - String resourceDescription, - Arena arena, - MemorySegment[] segments, - long length, - int chunkSizePower) { - super(resourceDescription); - this.arena = arena; - this.segments = segments; - this.length = length; - this.chunkSizePower = chunkSizePower; - this.chunkSizeMask = (1L << chunkSizePower) - 1L; - this.curSegment = segments[0]; - } - - void ensureOpen() { - if (curSegment == null) { - throw alreadyClosed(null); - } - } - - // the unused parameter is just to silence javac about unused variables - RuntimeException handlePositionalIOOBE(RuntimeException unused, String action, long pos) - throws IOException { - if (pos < 0L) { - return new IllegalArgumentException(action + " negative position (pos=" + pos + "): " + this); - } else { - throw new EOFException(action + " past EOF (pos=" + pos + "): " + this); - } - } - - AlreadyClosedException alreadyClosed(RuntimeException e) { - // we use NPE to signal if this input is closed (to not have checks everywhere). If NPE happens, - // we check the "is closed" condition explicitly by checking that our "curSegment" is null. - // Care must be taken to not leak the NPE to code outside MemorySegmentIndexInput! - if (this.curSegment == null) { - return new AlreadyClosedException("Already closed: " + this); - } - // ISE can be thrown by MemorySegment and contains "closed" in message: - if (e instanceof IllegalStateException - && e.getMessage() != null - && e.getMessage().contains("closed")) { - return new AlreadyClosedException("Already closed: " + this, e); - } - // otherwise rethrow unmodified NPE/ISE (as it possibly a bug with passing a null parameter to - // the IndexInput method): - throw e; - } - - @Override - public final byte readByte() throws IOException { - try { - final byte v = curSegment.get(LAYOUT_BYTE, curPosition); - curPosition++; - return v; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - do { - curSegmentIndex++; - if (curSegmentIndex >= segments.length) { - throw new EOFException("read past EOF: " + this); - } - curSegment = segments[curSegmentIndex]; - curPosition = 0L; - } while (curSegment.byteSize() == 0L); - final byte v = curSegment.get(LAYOUT_BYTE, curPosition); - curPosition++; - return v; - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final void readBytes(byte[] b, int offset, int len) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_BYTE, curPosition, b, offset, len); - curPosition += len; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - readBytesBoundary(b, offset, len); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - private void readBytesBoundary(byte[] b, int offset, int len) throws IOException { - try { - long curAvail = curSegment.byteSize() - curPosition; - while (len > curAvail) { - MemorySegment.copy(curSegment, LAYOUT_BYTE, curPosition, b, offset, (int) curAvail); - len -= curAvail; - offset += curAvail; - curSegmentIndex++; - if (curSegmentIndex >= segments.length) { - throw new EOFException("read past EOF: " + this); - } - curSegment = segments[curSegmentIndex]; - curPosition = 0L; - curAvail = curSegment.byteSize(); - } - MemorySegment.copy(curSegment, LAYOUT_BYTE, curPosition, b, offset, len); - curPosition += len; - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readInts(int[] dst, int offset, int length) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_LE_INT, curPosition, dst, offset, length); - curPosition += Integer.BYTES * (long) length; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException iobe) { - super.readInts(dst, offset, length); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readLongs(long[] dst, int offset, int length) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_LE_LONG, curPosition, dst, offset, length); - curPosition += Long.BYTES * (long) length; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException iobe) { - super.readLongs(dst, offset, length); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readFloats(float[] dst, int offset, int length) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_LE_FLOAT, curPosition, dst, offset, length); - curPosition += Float.BYTES * (long) length; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException iobe) { - super.readFloats(dst, offset, length); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final short readShort() throws IOException { - try { - final short v = curSegment.get(LAYOUT_LE_SHORT, curPosition); - curPosition += Short.BYTES; - return v; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - return super.readShort(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final int readInt() throws IOException { - try { - final int v = curSegment.get(LAYOUT_LE_INT, curPosition); - curPosition += Integer.BYTES; - return v; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - return super.readInt(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final int readVInt() throws IOException { - // this can make JVM less confused (see LUCENE-10366) - return super.readVInt(); - } - - @Override - public final long readVLong() throws IOException { - // this can make JVM less confused (see LUCENE-10366) - return super.readVLong(); - } - - @Override - public final long readLong() throws IOException { - try { - final long v = curSegment.get(LAYOUT_LE_LONG, curPosition); - curPosition += Long.BYTES; - return v; - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException e) { - return super.readLong(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public long getFilePointer() { - ensureOpen(); - return (((long) curSegmentIndex) << chunkSizePower) + curPosition; - } - - @Override - public void seek(long pos) throws IOException { - ensureOpen(); - // we use >> here to preserve negative, so we will catch AIOOBE, - // in case pos + offset overflows. - final int si = (int) (pos >> chunkSizePower); - try { - if (si != curSegmentIndex) { - final MemorySegment seg = segments[si]; - // write values, on exception all is unchanged - this.curSegmentIndex = si; - this.curSegment = seg; - } - this.curPosition = Objects.checkIndex(pos & chunkSizeMask, curSegment.byteSize() + 1); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "seek", pos); - } - } - - @Override - public byte readByte(long pos) throws IOException { - try { - final int si = (int) (pos >> chunkSizePower); - return segments[si].get(LAYOUT_BYTE, pos & chunkSizeMask); - } catch (IndexOutOfBoundsException ioobe) { - throw handlePositionalIOOBE(ioobe, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - protected void readGroupVInt(long[] dst, int offset) throws IOException { - try { - final int len = - GroupVIntUtil.readGroupVInt( - this, - curSegment.byteSize() - curPosition, - p -> curSegment.get(LAYOUT_LE_INT, p), - curPosition, - dst, - offset); - curPosition += len; - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readBytes(long pos, byte[] b, int offset, int len) throws IOException { - try { - int si = (int) (pos >> chunkSizePower); - pos = pos & chunkSizeMask; - long curAvail = segments[si].byteSize() - pos; - while (len > curAvail) { - MemorySegment.copy(segments[si], LAYOUT_BYTE, pos, b, offset, (int) curAvail); - len -= curAvail; - offset += curAvail; - si++; - if (si >= segments.length) { - throw new EOFException("read past EOF: " + this); - } - pos = 0L; - curAvail = segments[si].byteSize(); - } - MemorySegment.copy(segments[si], LAYOUT_BYTE, pos, b, offset, len); - } catch (IndexOutOfBoundsException ioobe) { - throw handlePositionalIOOBE(ioobe, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - // used only by random access methods to handle reads across boundaries - private void setPos(long pos, int si) throws IOException { - try { - final MemorySegment seg = segments[si]; - // write values, on exception above all is unchanged - this.curPosition = pos & chunkSizeMask; - this.curSegmentIndex = si; - this.curSegment = seg; - } catch (IndexOutOfBoundsException ioobe) { - throw handlePositionalIOOBE(ioobe, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public short readShort(long pos) throws IOException { - final int si = (int) (pos >> chunkSizePower); - try { - return segments[si].get(LAYOUT_LE_SHORT, pos & chunkSizeMask); - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException ioobe) { - // either it's a boundary, or read past EOF, fall back: - setPos(pos, si); - return readShort(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public int readInt(long pos) throws IOException { - final int si = (int) (pos >> chunkSizePower); - try { - return segments[si].get(LAYOUT_LE_INT, pos & chunkSizeMask); - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException ioobe) { - // either it's a boundary, or read past EOF, fall back: - setPos(pos, si); - return readInt(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public long readLong(long pos) throws IOException { - final int si = (int) (pos >> chunkSizePower); - try { - return segments[si].get(LAYOUT_LE_LONG, pos & chunkSizeMask); - } catch ( - @SuppressWarnings("unused") - IndexOutOfBoundsException ioobe) { - // either it's a boundary, or read past EOF, fall back: - setPos(pos, si); - return readLong(); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public final long length() { - return length; - } - - @Override - public final MemorySegmentIndexInput clone() { - final MemorySegmentIndexInput clone = buildSlice((String) null, 0L, this.length); - try { - clone.seek(getFilePointer()); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - - return clone; - } - - /** - * Creates a slice of this index input, with the given description, offset, and length. The slice - * is seeked to the beginning. - */ - @Override - public final MemorySegmentIndexInput slice(String sliceDescription, long offset, long length) { - if (offset < 0 || length < 0 || offset + length > this.length) { - throw new IllegalArgumentException( - "slice() " - + sliceDescription - + " out of bounds: offset=" - + offset - + ",length=" - + length - + ",fileLength=" - + this.length - + ": " - + this); - } - - return buildSlice(sliceDescription, offset, length); - } - - /** Builds the actual sliced IndexInput (may apply extra offset in subclasses). * */ - MemorySegmentIndexInput buildSlice(String sliceDescription, long offset, long length) { - ensureOpen(); - - final long sliceEnd = offset + length; - final int startIndex = (int) (offset >>> chunkSizePower); - final int endIndex = (int) (sliceEnd >>> chunkSizePower); - - // we always allocate one more slice, the last one may be a 0 byte one after truncating with - // asSlice(): - final MemorySegment slices[] = ArrayUtil.copyOfSubArray(segments, startIndex, endIndex + 1); - - // set the last segment's limit for the sliced view. - slices[slices.length - 1] = slices[slices.length - 1].asSlice(0L, sliceEnd & chunkSizeMask); - - offset = offset & chunkSizeMask; - - final String newResourceDescription = getFullSliceDescription(sliceDescription); - if (slices.length == 1) { - return new SingleSegmentImpl( - newResourceDescription, - null, // clones don't have an Arena, as they can't close) - slices[0].asSlice(offset, length), - length, - chunkSizePower); - } else { - return new MultiSegmentImpl( - newResourceDescription, - null, // clones don't have an Arena, as they can't close) - slices, - offset, - length, - chunkSizePower); - } - } - - @Override - public final void close() throws IOException { - if (curSegment == null) { - return; - } - - // the master IndexInput has an Arena and is able - // to release all resources (unmap segments) - a - // side effect is that other threads still using clones - // will throw IllegalStateException - if (arena != null) { - while (arena.scope().isAlive()) { - try { - arena.close(); - break; - } catch ( - @SuppressWarnings("unused") - IllegalStateException e) { - Thread.onSpinWait(); - } - } - } - - // make sure all accesses to this IndexInput instance throw NPE: - curSegment = null; - Arrays.fill(segments, null); - } - - /** Optimization of MemorySegmentIndexInput for when there is only one segment. */ - static final class SingleSegmentImpl extends MemorySegmentIndexInput { - - SingleSegmentImpl( - String resourceDescription, - Arena arena, - MemorySegment segment, - long length, - int chunkSizePower) { - super(resourceDescription, arena, new MemorySegment[] {segment}, length, chunkSizePower); - this.curSegmentIndex = 0; - } - - @Override - public void seek(long pos) throws IOException { - ensureOpen(); - try { - curPosition = Objects.checkIndex(pos, length + 1); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "seek", pos); - } - } - - @Override - public long getFilePointer() { - ensureOpen(); - return curPosition; - } - - @Override - public byte readByte(long pos) throws IOException { - try { - return curSegment.get(LAYOUT_BYTE, pos); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public void readBytes(long pos, byte[] bytes, int offset, int length) throws IOException { - try { - MemorySegment.copy(curSegment, LAYOUT_BYTE, pos, bytes, offset, length); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public short readShort(long pos) throws IOException { - try { - return curSegment.get(LAYOUT_LE_SHORT, pos); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public int readInt(long pos) throws IOException { - try { - return curSegment.get(LAYOUT_LE_INT, pos); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - - @Override - public long readLong(long pos) throws IOException { - try { - return curSegment.get(LAYOUT_LE_LONG, pos); - } catch (IndexOutOfBoundsException e) { - throw handlePositionalIOOBE(e, "read", pos); - } catch (NullPointerException | IllegalStateException e) { - throw alreadyClosed(e); - } - } - } - - /** This class adds offset support to MemorySegmentIndexInput, which is needed for slices. */ - static final class MultiSegmentImpl extends MemorySegmentIndexInput { - private final long offset; - - MultiSegmentImpl( - String resourceDescription, - Arena arena, - MemorySegment[] segments, - long offset, - long length, - int chunkSizePower) { - super(resourceDescription, arena, segments, length, chunkSizePower); - this.offset = offset; - try { - seek(0L); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - assert curSegment != null && curSegmentIndex >= 0; - } - - @Override - RuntimeException handlePositionalIOOBE(RuntimeException unused, String action, long pos) - throws IOException { - return super.handlePositionalIOOBE(unused, action, pos - offset); - } - - @Override - public void seek(long pos) throws IOException { - assert pos >= 0L : "negative position"; - super.seek(pos + offset); - } - - @Override - public long getFilePointer() { - return super.getFilePointer() - offset; - } - - @Override - public byte readByte(long pos) throws IOException { - return super.readByte(pos + offset); - } - - @Override - public void readBytes(long pos, byte[] bytes, int offset, int length) throws IOException { - super.readBytes(pos + this.offset, bytes, offset, length); - } - - @Override - public short readShort(long pos) throws IOException { - return super.readShort(pos + offset); - } - - @Override - public int readInt(long pos) throws IOException { - return super.readInt(pos + offset); - } - - @Override - public long readLong(long pos) throws IOException { - return super.readLong(pos + offset); - } - - @Override - MemorySegmentIndexInput buildSlice(String sliceDescription, long ofs, long length) { - return super.buildSlice(sliceDescription, this.offset + ofs, length); - } - } -} diff --git a/lucene/core/src/java20/org/apache/lucene/store/MemorySegmentIndexInputProvider.java b/lucene/core/src/java20/org/apache/lucene/store/MemorySegmentIndexInputProvider.java deleted file mode 100644 index 5fc2533af28c..000000000000 --- a/lucene/core/src/java20/org/apache/lucene/store/MemorySegmentIndexInputProvider.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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. - */ -package org.apache.lucene.store; - -import java.io.IOException; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.nio.channels.FileChannel; -import java.nio.channels.FileChannel.MapMode; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.logging.Logger; -import org.apache.lucene.util.Constants; -import org.apache.lucene.util.Unwrappable; - -@SuppressWarnings("preview") -final class MemorySegmentIndexInputProvider implements MMapDirectory.MMapIndexInputProvider { - - public MemorySegmentIndexInputProvider() { - var log = Logger.getLogger(getClass().getName()); - log.info( - "Using MemorySegmentIndexInput with Java 20; to disable start with -D" - + MMapDirectory.ENABLE_MEMORY_SEGMENTS_SYSPROP - + "=false"); - } - - @Override - public IndexInput openInput(Path path, IOContext context, int chunkSizePower, boolean preload) - throws IOException { - final String resourceDescription = "MemorySegmentIndexInput(path=\"" + path.toString() + "\")"; - - // Work around for JDK-8259028: we need to unwrap our test-only file system layers - path = Unwrappable.unwrapAll(path); - - boolean success = false; - final Arena arena = Arena.openShared(); - try (var fc = FileChannel.open(path, StandardOpenOption.READ)) { - final long fileSize = fc.size(); - final IndexInput in = - MemorySegmentIndexInput.newInstance( - resourceDescription, - arena, - map(arena, resourceDescription, fc, chunkSizePower, preload, fileSize), - fileSize, - chunkSizePower); - success = true; - return in; - } finally { - if (success == false) { - arena.close(); - } - } - } - - @Override - public long getDefaultMaxChunkSize() { - return Constants.JRE_IS_64BIT ? (1L << 34) : (1L << 28); - } - - @Override - public boolean isUnmapSupported() { - return true; - } - - @Override - public String getUnmapNotSupportedReason() { - return null; - } - - private final MemorySegment[] map( - Arena arena, - String resourceDescription, - FileChannel fc, - int chunkSizePower, - boolean preload, - long length) - throws IOException { - if ((length >>> chunkSizePower) >= Integer.MAX_VALUE) - throw new IllegalArgumentException("File too big for chunk size: " + resourceDescription); - - final long chunkSize = 1L << chunkSizePower; - - // we always allocate one more segments, the last one may be a 0 byte one - final int nrSegments = (int) (length >>> chunkSizePower) + 1; - - final MemorySegment[] segments = new MemorySegment[nrSegments]; - - long startOffset = 0L; - for (int segNr = 0; segNr < nrSegments; segNr++) { - final long segSize = - (length > (startOffset + chunkSize)) ? chunkSize : (length - startOffset); - final MemorySegment segment; - try { - segment = fc.map(MapMode.READ_ONLY, startOffset, segSize, arena.scope()); - } catch (IOException ioe) { - throw convertMapFailedIOException(ioe, resourceDescription, segSize); - } - if (preload) { - segment.load(); - } - segments[segNr] = segment; - startOffset += segSize; - } - return segments; - } -} diff --git a/lucene/core/src/java20/org/apache/lucene/internal/vectorization/PanamaVectorUtilSupport.java b/lucene/core/src/java21/org/apache/lucene/internal/vectorization/PanamaVectorUtilSupport.java similarity index 100% rename from lucene/core/src/java20/org/apache/lucene/internal/vectorization/PanamaVectorUtilSupport.java rename to lucene/core/src/java21/org/apache/lucene/internal/vectorization/PanamaVectorUtilSupport.java diff --git a/lucene/core/src/java20/org/apache/lucene/internal/vectorization/PanamaVectorizationProvider.java b/lucene/core/src/java21/org/apache/lucene/internal/vectorization/PanamaVectorizationProvider.java similarity index 100% rename from lucene/core/src/java20/org/apache/lucene/internal/vectorization/PanamaVectorizationProvider.java rename to lucene/core/src/java21/org/apache/lucene/internal/vectorization/PanamaVectorizationProvider.java diff --git a/lucene/core/src/java21/org/apache/lucene/store/MemorySegmentIndexInputProvider.java b/lucene/core/src/java21/org/apache/lucene/store/MemorySegmentIndexInputProvider.java index 57c9b5275f05..7ccd89ff2bb5 100644 --- a/lucene/core/src/java21/org/apache/lucene/store/MemorySegmentIndexInputProvider.java +++ b/lucene/core/src/java21/org/apache/lucene/store/MemorySegmentIndexInputProvider.java @@ -23,21 +23,12 @@ import java.nio.channels.FileChannel.MapMode; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.logging.Logger; import org.apache.lucene.util.Constants; import org.apache.lucene.util.Unwrappable; @SuppressWarnings("preview") final class MemorySegmentIndexInputProvider implements MMapDirectory.MMapIndexInputProvider { - public MemorySegmentIndexInputProvider() { - var log = Logger.getLogger(getClass().getName()); - log.info( - "Using MemorySegmentIndexInput with Java 21 or later; to disable start with -D" - + MMapDirectory.ENABLE_MEMORY_SEGMENTS_SYSPROP - + "=false"); - } - @Override public IndexInput openInput(Path path, IOContext context, int chunkSizePower, boolean preload) throws IOException { @@ -71,16 +62,6 @@ public long getDefaultMaxChunkSize() { return Constants.JRE_IS_64BIT ? (1L << 34) : (1L << 28); } - @Override - public boolean isUnmapSupported() { - return true; - } - - @Override - public String getUnmapNotSupportedReason() { - return null; - } - private final MemorySegment[] map( Arena arena, String resourceDescription, diff --git a/lucene/core/src/java21/org/apache/lucene/util/VectorUtilPanamaProvider.txt b/lucene/core/src/java21/org/apache/lucene/util/VectorUtilPanamaProvider.txt deleted file mode 100644 index db75951206ef..000000000000 --- a/lucene/core/src/java21/org/apache/lucene/util/VectorUtilPanamaProvider.txt +++ /dev/null @@ -1,2 +0,0 @@ -The version of VectorUtilPanamaProvider for Java 21 is identical to that of Java 20. -As such, there is no specific 21 version - the Java 20 version will be loaded from the MRJAR. \ No newline at end of file diff --git a/lucene/core/src/test/org/apache/lucene/index/Test4GBStoredFields.java b/lucene/core/src/test/org/apache/lucene/index/Test4GBStoredFields.java index f03024a9860d..c75ed932876c 100644 --- a/lucene/core/src/test/org/apache/lucene/index/Test4GBStoredFields.java +++ b/lucene/core/src/test/org/apache/lucene/index/Test4GBStoredFields.java @@ -38,8 +38,6 @@ public class Test4GBStoredFields extends LuceneTestCase { public void test() throws Exception { - assumeWorkingMMapOnWindows(); - MockDirectoryWrapper dir = new MockDirectoryWrapper(random(), new MMapDirectory(createTempDir("4GBStoredFields"))); dir.setThrottling(MockDirectoryWrapper.Throttling.NEVER); diff --git a/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java b/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java index 813212cf81d1..61e5f7e361d8 100644 --- a/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java +++ b/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java @@ -1234,7 +1234,6 @@ public void testNoDocsIndex() throws Throwable { public void testDeleteUnusedFiles() throws Exception { assumeFalse("test relies on exact filenames", Codec.getDefault() instanceof SimpleTextCodec); - assumeWorkingMMapOnWindows(); for (int iter = 0; iter < 2; iter++) { // relies on Windows semantics diff --git a/lucene/core/src/test/org/apache/lucene/store/TestByteBuffersDirectory.java b/lucene/core/src/test/org/apache/lucene/store/TestByteBuffersDirectory.java index 87cfee6e7c40..3717f490cfd3 100644 --- a/lucene/core/src/test/org/apache/lucene/store/TestByteBuffersDirectory.java +++ b/lucene/core/src/test/org/apache/lucene/store/TestByteBuffersDirectory.java @@ -84,15 +84,6 @@ public static Iterable parametersWithCustomName() { ByteBuffersDirectory.OUTPUT_AS_ONE_BUFFER), "one buffer (heap)" }, - { - (Supplier) - () -> - new ByteBuffersDirectory( - new SingleInstanceLockFactory(), - ByteBuffersDataOutput::new, - ByteBuffersDirectory.OUTPUT_AS_MANY_BUFFERS_LUCENE), - "lucene's buffers (heap)" - }, { (Supplier) () -> diff --git a/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java b/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java index bd954f19d3fb..3f601e4388da 100644 --- a/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java +++ b/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java @@ -43,9 +43,7 @@ public void testDirectInstantiation() throws Exception { final List dirs0 = new ArrayList<>(); dirs0.add(new NIOFSDirectory(path)); - if (hasWorkingMMapOnWindows()) { - dirs0.add(new MMapDirectory(path)); - } + dirs0.add(new MMapDirectory(path)); final FSDirectory[] dirs = dirs0.toArray(FSDirectory[]::new); for (int i = 0; i < dirs.length; i++) { @@ -63,9 +61,6 @@ public void testDirectInstantiation() throws Exception { assertTrue(slowFileExists(d2, fname)); assertEquals(1 + largeBuffer.length, d2.fileLength(fname)); - // don't do read tests if unmapping is not supported! - if (d2 instanceof MMapDirectory && !MMapDirectory.UNMAP_SUPPORTED) continue; - IndexInput input = d2.openInput(fname, newIOContext(random())); assertEquals((byte) i, input.readByte()); // read array with buffering enabled diff --git a/lucene/core/src/test/org/apache/lucene/store/TestMMapDirectory.java b/lucene/core/src/test/org/apache/lucene/store/TestMMapDirectory.java index 6be7e570a627..611dd6b1c826 100644 --- a/lucene/core/src/test/org/apache/lucene/store/TestMMapDirectory.java +++ b/lucene/core/src/test/org/apache/lucene/store/TestMMapDirectory.java @@ -18,11 +18,9 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.Objects; import java.util.Random; import java.util.concurrent.CountDownLatch; import org.apache.lucene.tests.store.BaseDirectoryTestCase; -import org.junit.BeforeClass; /** Tests MMapDirectory */ // See: https://issues.apache.org/jira/browse/SOLR-12028 Tests cannot remove files on Windows @@ -36,30 +34,7 @@ protected Directory getDirectory(Path path) throws IOException { return m; } - @BeforeClass - public static void beforeClass() throws Exception { - assertTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED); - } - - private static boolean isMemorySegmentImpl() { - return Objects.equals( - "MemorySegmentIndexInputProvider", MMapDirectory.PROVIDER.getClass().getSimpleName()); - } - - public void testCorrectImplementation() { - final int runtimeVersion = Runtime.version().feature(); - if (runtimeVersion >= 19) { - assertTrue( - "on Java 19 or later we should use MemorySegmentIndexInputProvider to create mmap IndexInputs", - isMemorySegmentImpl()); - } else { - assertSame(MappedByteBufferIndexInputProvider.class, MMapDirectory.PROVIDER.getClass()); - } - } - public void testAceWithThreads() throws Exception { - assumeTrue("Test requires MemorySegmentIndexInput", isMemorySegmentImpl()); - final int nInts = 8 * 1024 * 1024; try (Directory dir = getDirectory(createTempDir("testAceWithThreads"))) { diff --git a/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java b/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java index a0c5a29ccf82..3b2ef1b664ee 100644 --- a/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java +++ b/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java @@ -23,7 +23,6 @@ import java.util.Locale; import org.apache.lucene.tests.store.BaseChunkedDirectoryTestCase; import org.apache.lucene.util.BytesRef; -import org.junit.BeforeClass; /** * Tests MMapDirectory's MultiMMapIndexInput @@ -38,11 +37,6 @@ protected Directory getDirectory(Path path, int maxChunkSize) throws IOException return new MMapDirectory(path, maxChunkSize); } - @BeforeClass - public static void beforeClass() throws Exception { - assertTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED); - } - public void testSeekingExceptions() throws IOException { final int sliceSize = 128; try (Directory dir = getDirectory(createTempDir(), sliceSize)) { diff --git a/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFST.java b/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFST.java index 1cf72208a938..6ba979beb539 100644 --- a/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFST.java +++ b/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFST.java @@ -42,8 +42,6 @@ public class Test2BFST extends LuceneTestCase { private static long LIMIT = 3L * 1024 * 1024 * 1024; public void test() throws Exception { - assumeWorkingMMapOnWindows(); - int[] ints = new int[7]; IntsRef input = new IntsRef(ints, 0, ints.length); long seed = random().nextLong(); diff --git a/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFSTOffHeap.java b/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFSTOffHeap.java index 1773c47f4328..42fd4e1128a4 100644 --- a/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFSTOffHeap.java +++ b/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFSTOffHeap.java @@ -44,8 +44,6 @@ public class Test2BFSTOffHeap extends LuceneTestCase { private static long LIMIT = 3L * 1024 * 1024 * 1024; public void test() throws Exception { - assumeWorkingMMapOnWindows(); - int[] ints = new int[7]; IntsRef input = new IntsRef(ints, 0, ints.length); long seed = random().nextLong(); diff --git a/lucene/distribution.tests/src/test/org/apache/lucene/distribution/TestModularLayer.java b/lucene/distribution.tests/src/test/org/apache/lucene/distribution/TestModularLayer.java index 577f0f9e36c9..084a044092f6 100644 --- a/lucene/distribution.tests/src/test/org/apache/lucene/distribution/TestModularLayer.java +++ b/lucene/distribution.tests/src/test/org/apache/lucene/distribution/TestModularLayer.java @@ -207,7 +207,7 @@ public void testMultiReleaseJar() { ClassLoader loader = layer.findLoader(coreModuleId); - final Set jarVersions = Set.of(19, 20, 21); + final Set jarVersions = Set.of(21); for (var v : jarVersions) { Assertions.assertThat( loader.getResource( @@ -217,12 +217,9 @@ public void testMultiReleaseJar() { .isNotNull(); } - final int runtimeVersion = Runtime.version().feature(); - if (jarVersions.contains(runtimeVersion)) { - Assertions.assertThat( - loader.loadClass("org.apache.lucene.store.MemorySegmentIndexInput")) - .isNotNull(); - } + Assertions.assertThat( + loader.loadClass("org.apache.lucene.store.MemorySegmentIndexInput")) + .isNotNull(); }); } diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java index 732c9553a646..d62e4230bb8d 100644 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java +++ b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java @@ -97,7 +97,7 @@ public final class JavascriptCompiler { private static final Lookup LOOKUP = MethodHandles.lookup(); - private static final int CLASSFILE_VERSION = Opcodes.V17; + private static final int CLASSFILE_VERSION = Opcodes.V21; private static final MethodType MT_EXPRESSION_CTOR_LOOKUP = MethodType.methodType(void.class, String.class, String[].class); diff --git a/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/NRTSuggester.java b/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/NRTSuggester.java index 4f6936ee205d..b059d3e73568 100644 --- a/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/NRTSuggester.java +++ b/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/NRTSuggester.java @@ -27,7 +27,6 @@ import org.apache.lucene.search.suggest.document.CompletionPostingsFormat.FSTLoadMode; import org.apache.lucene.store.ByteArrayDataInput; import org.apache.lucene.store.ByteArrayDataOutput; -import org.apache.lucene.store.ByteBufferIndexInput; import org.apache.lucene.store.IndexInput; import org.apache.lucene.util.Accountable; import org.apache.lucene.util.Bits; @@ -327,8 +326,7 @@ private static boolean shouldLoadFSTOffHeap(IndexInput input, FSTLoadMode fstLoa case AUTO: // TODO: Make this less hacky to maybe expose "off-heap" feature using a marker interface on // the IndexInput - return input instanceof ByteBufferIndexInput - || input.getClass().getName().contains(".MemorySegmentIndexInput"); + return input.getClass().getName().contains(".MemorySegmentIndexInput"); default: throw new IllegalStateException("unknown enum constant: " + fstLoadMode); } diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseStoredFieldsFormatTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseStoredFieldsFormatTestCase.java index efd351c21fe0..63aa50bf713c 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseStoredFieldsFormatTestCase.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseStoredFieldsFormatTestCase.java @@ -723,8 +723,6 @@ public void testMergeFilterReader() throws IOException { @Nightly public void testBigDocuments() throws IOException { - assumeWorkingMMapOnWindows(); - // "big" as "much bigger than the chunk size" // for this test we force an FS dir // we can't just use newFSDirectory, because this test doesn't really index anything. diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java index 492bf9423fd1..f1b9a6441782 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/util/LuceneTestCase.java @@ -166,7 +166,6 @@ import org.apache.lucene.store.FlushInfo; import org.apache.lucene.store.IOContext; import org.apache.lucene.store.LockFactory; -import org.apache.lucene.store.MMapDirectory; import org.apache.lucene.store.MergeInfo; import org.apache.lucene.store.NRTCachingDirectory; import org.apache.lucene.tests.analysis.MockAnalyzer; @@ -190,7 +189,6 @@ import org.apache.lucene.util.Bits; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.CommandLineUtil; -import org.apache.lucene.util.Constants; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.InfoStream; import org.apache.lucene.util.NamedThreadFactory; @@ -498,31 +496,9 @@ static int defaultRandomMultiplier() { LEAVE_TEMPORARY = defaultValue; } - /** - * Returns true, if MMapDirectory supports unmapping on this platform (required for Windows), or - * if we are not on Windows. - */ - public static boolean hasWorkingMMapOnWindows() { - return !Constants.WINDOWS || MMapDirectory.UNMAP_SUPPORTED; - } - - /** - * Assumes that the current MMapDirectory implementation supports unmapping, so the test will not - * fail on Windows. - * - * @see #hasWorkingMMapOnWindows() - */ - public static void assumeWorkingMMapOnWindows() { - assumeTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, hasWorkingMMapOnWindows()); - } - /** Filesystem-based {@link Directory} implementations. */ private static final List FS_DIRECTORIES = - Arrays.asList( - "NIOFSDirectory", - // NIOFSDirectory as replacement for MMapDirectory if unmapping is not supported on - // Windows (to make randomization stable): - hasWorkingMMapOnWindows() ? "MMapDirectory" : "NIOFSDirectory"); + Arrays.asList("NIOFSDirectory", "MMapDirectory"); /** All {@link Directory} implementations. */ private static final List CORE_DIRECTORIES;