1 /*
  2  * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 import org.testng.annotations.Test;
 25 
 26 import java.lang.foreign.*;
 27 import java.lang.foreign.Arena;
 28 import java.lang.invoke.MethodHandle;
 29 import java.nio.file.Path;
 30 import java.util.concurrent.ExecutorService;
 31 import java.util.concurrent.Executors;
 32 import java.util.concurrent.TimeUnit;
 33 
 34 import static org.testng.Assert.*;
 35 
 36 /*
 37  * @test
 38  * @run testng/othervm --enable-native-access=ALL-UNNAMED LibraryLookupTest
 39  */
 40 public class LibraryLookupTest {
 41 
 42     static final Path JAVA_LIBRARY_PATH = Path.of(System.getProperty("java.library.path"));
 43     static final MethodHandle INC = Linker.nativeLinker().downcallHandle(FunctionDescriptor.ofVoid());
 44     static final Path LIB_PATH = JAVA_LIBRARY_PATH.resolve(System.mapLibraryName("LibraryLookup"));
 45 
 46     @Test
 47     void testLoadLibraryConfined() {
 48         try (Arena arena0 = Arena.ofConfined()) {
 49             callFunc(loadLibrary(arena0));
 50             try (Arena arena1 = Arena.ofConfined()) {
 51                 callFunc(loadLibrary(arena1));
 52                 try (Arena arena2 = Arena.ofConfined()) {
 53                     callFunc(loadLibrary(arena2));
 54                 }
 55             }
 56         }
 57     }
 58 
 59     @Test(expectedExceptions = IllegalStateException.class)
 60     void testLoadLibraryConfinedClosed() {
 61         MemorySegment addr;
 62         try (Arena arena = Arena.ofConfined()) {
 63             addr = loadLibrary(arena);
 64         }
 65         callFunc(addr);
 66     }
 67 
 68     @Test(expectedExceptions = IllegalArgumentException.class)
 69     void testLoadLibraryBadName() {
 70         try (Arena arena = Arena.ofConfined()) {
 71             SymbolLookup.libraryLookup(LIB_PATH.toString() + "\u0000", arena);
 72         }
 73     }
 74 
 75     @Test
 76     void testLoadLibraryBadLookupName() {
 77         try (Arena arena = Arena.ofConfined()) {
 78             SymbolLookup lookup = SymbolLookup.libraryLookup(LIB_PATH, arena);
 79             assertTrue(lookup.find("inc\u0000foobar").isEmpty());
 80         }
 81     }
 82 
 83     private static MemorySegment loadLibrary(Arena session) {
 84         SymbolLookup lib = SymbolLookup.libraryLookup(LIB_PATH, session);
 85         MemorySegment addr = lib.find("inc").get();
 86         assertEquals(addr.scope(), session.scope());
 87         return addr;
 88     }
 89 
 90     private static void callFunc(MemorySegment addr) {
 91         try {
 92             INC.invokeExact(addr);
 93         } catch (IllegalStateException ex) {
 94             throw ex;
 95         } catch (Throwable ex) {
 96             throw new AssertionError(ex);
 97         }
 98     }
 99 
100     static final int ITERATIONS = 100;
101     static final int MAX_EXECUTOR_WAIT_SECONDS = 20;
102     static final int NUM_ACCESSORS = Math.min(10, Runtime.getRuntime().availableProcessors());
103 
104     @Test(expectedExceptions = IllegalArgumentException.class)
105     void testBadLibraryLookupName() {
106         SymbolLookup.libraryLookup("nonExistent", Arena.global());
107     }
108 
109     @Test(expectedExceptions = IllegalArgumentException.class)
110     void testBadLibraryLookupPath() {
111         SymbolLookup.libraryLookup(Path.of("nonExistent"), Arena.global());
112     }
113 
114     @Test
115     void testLoadLibraryShared() throws Throwable {
116         ExecutorService accessExecutor = Executors.newCachedThreadPool();
117         for (int i = 0; i < NUM_ACCESSORS ; i++) {
118             accessExecutor.execute(new LibraryLoadAndAccess());
119         }
120         accessExecutor.shutdown();
121         assertTrue(accessExecutor.awaitTermination(MAX_EXECUTOR_WAIT_SECONDS, TimeUnit.SECONDS));
122     }
123 
124     static class LibraryLoadAndAccess implements Runnable {
125         @Override
126         public void run() {
127             for (int i = 0 ; i < ITERATIONS ; i++) {
128                 try (Arena arena = Arena.ofConfined()) {
129                     callFunc(loadLibrary(arena));
130                 }
131             }
132         }
133     }
134 
135     @Test
136     void testLoadLibrarySharedClosed() throws Throwable {
137         Arena arena = Arena.ofShared();
138         MemorySegment addr = loadLibrary(arena);
139         ExecutorService accessExecutor = Executors.newCachedThreadPool();
140         for (int i = 0; i < NUM_ACCESSORS ; i++) {
141             accessExecutor.execute(new LibraryAccess(addr));
142         }
143         while (true) {
144             try {
145                 arena.close();
146                 break;
147             } catch (IllegalStateException ex) {
148                 // wait for addressable parameter to be released
149                 Thread.onSpinWait();
150             }
151         }
152         accessExecutor.shutdown();
153         assertTrue(accessExecutor.awaitTermination(MAX_EXECUTOR_WAIT_SECONDS, TimeUnit.SECONDS));
154     }
155 
156     static class LibraryAccess implements Runnable {
157 
158         final MemorySegment addr;
159 
160         LibraryAccess(MemorySegment addr) {
161             this.addr = addr;
162         }
163 
164         @Override
165         public void run() {
166             for (int i = 0 ; i < ITERATIONS ; i++) {
167                 try {
168                     callFunc(addr);
169                 } catch (IllegalStateException ex) {
170                     // library closed
171                     break;
172                 }
173             }
174         }
175     }
176 }