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  * @enablePreview
 39  * @requires jdk.foreign.linker != "UNSUPPORTED"
 40  * @run testng/othervm --enable-native-access=ALL-UNNAMED LibraryLookupTest
 41  */
 42 public class LibraryLookupTest {
 43 
 44     static final Path JAVA_LIBRARY_PATH = Path.of(System.getProperty("java.library.path"));
 45     static final MethodHandle INC = Linker.nativeLinker().downcallHandle(FunctionDescriptor.ofVoid());
 46     static final Path LIB_PATH = JAVA_LIBRARY_PATH.resolve(System.mapLibraryName("LibraryLookup"));
 47 
 48     @Test
 49     void testLoadLibraryConfined() {
 50         try (Arena arena0 = Arena.ofConfined()) {
 51             callFunc(loadLibrary(arena0));
 52             try (Arena arena1 = Arena.ofConfined()) {
 53                 callFunc(loadLibrary(arena1));
 54                 try (Arena arena2 = Arena.ofConfined()) {
 55                     callFunc(loadLibrary(arena2));
 56                 }
 57             }
 58         }
 59     }
 60 
 61     @Test(expectedExceptions = IllegalStateException.class)
 62     void testLoadLibraryConfinedClosed() {
 63         MemorySegment addr;
 64         try (Arena arena = Arena.ofConfined()) {
 65             addr = loadLibrary(arena);
 66         }
 67         callFunc(addr);
 68     }
 69 
 70     @Test(expectedExceptions = IllegalArgumentException.class)
 71     void testLoadLibraryBadName() {
 72         try (Arena arena = Arena.ofConfined()) {
 73             SymbolLookup.libraryLookup(LIB_PATH.toString() + "\u0000", arena);
 74         }
 75     }
 76 
 77     @Test
 78     void testLoadLibraryBadLookupName() {
 79         try (Arena arena = Arena.ofConfined()) {
 80             SymbolLookup lookup = SymbolLookup.libraryLookup(LIB_PATH, arena);
 81             assertTrue(lookup.find("inc\u0000foobar").isEmpty());
 82         }
 83     }
 84 
 85     private static MemorySegment loadLibrary(Arena session) {
 86         SymbolLookup lib = SymbolLookup.libraryLookup(LIB_PATH, session);
 87         MemorySegment addr = lib.find("inc").get();
 88         assertEquals(addr.scope(), session.scope());
 89         return addr;
 90     }
 91 
 92     private static void callFunc(MemorySegment addr) {
 93         try {
 94             INC.invokeExact(addr);
 95         } catch (IllegalStateException ex) {
 96             throw ex;
 97         } catch (Throwable ex) {
 98             throw new AssertionError(ex);
 99         }
100     }
101 
102     static final int ITERATIONS = 100;
103     static final int MAX_EXECUTOR_WAIT_SECONDS = 20;
104     static final int NUM_ACCESSORS = Math.min(10, Runtime.getRuntime().availableProcessors());
105 
106     @Test(expectedExceptions = IllegalArgumentException.class)
107     void testBadLibraryLookupName() {
108         SymbolLookup.libraryLookup("nonExistent", Arena.global());
109     }
110 
111     @Test(expectedExceptions = IllegalArgumentException.class)
112     void testBadLibraryLookupPath() {
113         SymbolLookup.libraryLookup(Path.of("nonExistent"), Arena.global());
114     }
115 
116     @Test
117     void testLoadLibraryShared() throws Throwable {
118         ExecutorService accessExecutor = Executors.newCachedThreadPool();
119         for (int i = 0; i < NUM_ACCESSORS ; i++) {
120             accessExecutor.execute(new LibraryLoadAndAccess());
121         }
122         accessExecutor.shutdown();
123         assertTrue(accessExecutor.awaitTermination(MAX_EXECUTOR_WAIT_SECONDS, TimeUnit.SECONDS));
124     }
125 
126     static class LibraryLoadAndAccess implements Runnable {
127         @Override
128         public void run() {
129             for (int i = 0 ; i < ITERATIONS ; i++) {
130                 try (Arena arena = Arena.ofConfined()) {
131                     callFunc(loadLibrary(arena));
132                 }
133             }
134         }
135     }
136 
137     @Test
138     void testLoadLibrarySharedClosed() throws Throwable {
139         Arena arena = Arena.ofShared();
140         MemorySegment addr = loadLibrary(arena);
141         ExecutorService accessExecutor = Executors.newCachedThreadPool();
142         for (int i = 0; i < NUM_ACCESSORS ; i++) {
143             accessExecutor.execute(new LibraryAccess(addr));
144         }
145         while (true) {
146             try {
147                 arena.close();
148                 break;
149             } catch (IllegalStateException ex) {
150                 // wait for addressable parameter to be released
151                 Thread.onSpinWait();
152             }
153         }
154         accessExecutor.shutdown();
155         assertTrue(accessExecutor.awaitTermination(MAX_EXECUTOR_WAIT_SECONDS, TimeUnit.SECONDS));
156     }
157 
158     static class LibraryAccess implements Runnable {
159 
160         final MemorySegment addr;
161 
162         LibraryAccess(MemorySegment addr) {
163             this.addr = addr;
164         }
165 
166         @Override
167         public void run() {
168             for (int i = 0 ; i < ITERATIONS ; i++) {
169                 try {
170                     callFunc(addr);
171                 } catch (IllegalStateException ex) {
172                     // library closed
173                     break;
174                 }
175             }
176         }
177     }
178 }