View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.buildcache.hash;
20  
21  import java.io.IOException;
22  import java.lang.reflect.Method;
23  import java.nio.ByteBuffer;
24  import java.nio.channels.FileChannel;
25  import java.nio.channels.FileChannel.MapMode;
26  
27  import static org.apache.maven.buildcache.hash.ReflectionUtils.getField;
28  import static org.apache.maven.buildcache.hash.ReflectionUtils.getMethod;
29  
30  /**
31   * CloseableBuffer https://stackoverflow.com/a/54046774
32   */
33  public class CloseableBuffer implements AutoCloseable {
34  
35      private static final Cleaner CLEANER = getJVMDependentCleaner();
36  
37      private static Cleaner getJVMDependentCleaner() {
38          final String jsv = System.getProperty("java.specification.version", "9");
39          if (jsv.startsWith("1.")) {
40              return DirectCleaner.isSupported() ? new DirectCleaner() : new NoopCleaner();
41          } else {
42              return UnsafeCleaner.isSupported() ? new UnsafeCleaner() : new NoopCleaner();
43          }
44      }
45  
46      public static CloseableBuffer directBuffer(int capacity) {
47          return new CloseableBuffer(ByteBuffer.allocateDirect(capacity));
48      }
49  
50      public static CloseableBuffer mappedBuffer(FileChannel channel, MapMode mode) throws IOException {
51          return new CloseableBuffer(channel.map(mode, 0, channel.size()));
52      }
53  
54      private ByteBuffer buffer;
55  
56      /**
57       * Unmap only DirectByteBuffer and MappedByteBuffer
58       */
59      private CloseableBuffer(ByteBuffer buffer) {
60          // Java 8: buffer.isDirect()
61          this.buffer = buffer;
62      }
63  
64      /**
65       * Do not use buffer after close
66       */
67      public ByteBuffer getBuffer() {
68          return buffer;
69      }
70  
71      @Override
72      public void close() {
73          boolean done = CLEANER.clean(buffer);
74          if (done) {
75              buffer = null;
76          }
77      }
78  
79      // Java 8: @FunctionalInterface
80      private interface Cleaner {
81  
82          boolean clean(ByteBuffer buffer);
83      }
84  
85      private static class NoopCleaner implements Cleaner {
86  
87          @Override
88          public boolean clean(ByteBuffer buffer) {
89              return false;
90          }
91      }
92  
93      private static class DirectCleaner implements Cleaner {
94  
95          private static final Method ATTACHMENT = getMethod("sun.nio.ch.DirectBuffer", "attachment");
96          private static final Method CLEANER = getMethod("sun.nio.ch.DirectBuffer", "cleaner");
97          private static final Method CLEAN = getMethod("sun.misc.Cleaner", "clean");
98  
99          public static boolean isSupported() {
100             return ATTACHMENT != null && CLEAN != null && CLEANER != null;
101         }
102 
103         /**
104          * Make sure duplicates and slices are not cleaned, since this can result in duplicate attempts to clean the
105          * same buffer, which trigger a crash with: "A fatal error has been detected by the Java Runtime Environment:
106          * EXCEPTION_ACCESS_VIOLATION" See: https://stackoverflow.com/a/31592947/3950982
107          */
108         @Override
109         public boolean clean(ByteBuffer buffer) {
110             try {
111                 if (ATTACHMENT.invoke(buffer) == null) {
112                     CLEAN.invoke(CLEANER.invoke(buffer));
113                     return true;
114                 }
115             } catch (Exception ignore) {
116             }
117             return false;
118         }
119     }
120 
121     private static class UnsafeCleaner implements Cleaner {
122 
123         // Java 9: getMethod("jdk.internal.misc.Unsafe", "invokeCleaner", ByteBuffer.class);
124         private static final Method INVOKE_CLEANER = getMethod("sun.misc.Unsafe", "invokeCleaner", ByteBuffer.class);
125         private static final Object UNSAFE = getField("sun.misc.Unsafe", "theUnsafe");
126 
127         public static boolean isSupported() {
128             return UNSAFE != null && INVOKE_CLEANER != null;
129         }
130 
131         /**
132          * Calling the above code in JDK9+ gives a reflection warning on stderr,
133          * Unsafe.theUnsafe.invokeCleaner(byteBuffer)
134          * makes the same call, but does not print the reflection warning
135          */
136         @Override
137         public boolean clean(ByteBuffer buffer) {
138             try {
139                 // throws IllegalArgumentException if buffer is a duplicate or slice
140                 INVOKE_CLEANER.invoke(UNSAFE, buffer);
141                 return true;
142             } catch (Exception ignore) {
143             }
144             return false;
145         }
146     }
147 }