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  import java.security.PrivilegedAction;
27  
28  import static java.security.AccessController.doPrivileged;
29  import static org.apache.maven.buildcache.hash.ReflectionUtils.getField;
30  import static org.apache.maven.buildcache.hash.ReflectionUtils.getMethod;
31  
32  /**
33   * CloseableBuffer https://stackoverflow.com/a/54046774
34   */
35  public class CloseableBuffer implements AutoCloseable {
36  
37      private static final Cleaner CLEANER = doPrivileged(new PrivilegedAction<Cleaner>() {
38  
39          @Override
40          public Cleaner run() {
41              final String jsv = System.getProperty("java.specification.version", "9");
42              if (jsv.startsWith("1.")) {
43                  return DirectCleaner.isSupported() ? new DirectCleaner() : new NoopCleaner();
44              } else {
45                  return UnsafeCleaner.isSupported() ? new UnsafeCleaner() : new NoopCleaner();
46              }
47          }
48      });
49  
50      public static CloseableBuffer directBuffer(int capacity) {
51          return new CloseableBuffer(ByteBuffer.allocateDirect(capacity));
52      }
53  
54      public static CloseableBuffer mappedBuffer(FileChannel channel, MapMode mode) throws IOException {
55          return new CloseableBuffer(channel.map(mode, 0, channel.size()));
56      }
57  
58      private ByteBuffer buffer;
59  
60      /**
61       * Unmap only DirectByteBuffer and MappedByteBuffer
62       */
63      private CloseableBuffer(ByteBuffer buffer) {
64          // Java 8: buffer.isDirect()
65          this.buffer = buffer;
66      }
67  
68      /**
69       * Do not use buffer after close
70       */
71      public ByteBuffer getBuffer() {
72          return buffer;
73      }
74  
75      @Override
76      public void close() {
77          // Java 8: () -> CLEANER.clean(buffer)
78          boolean done = doPrivileged(new PrivilegedAction<Boolean>() {
79  
80              @Override
81              public Boolean run() {
82                  return CLEANER.clean(buffer);
83              }
84          });
85          if (done) {
86              buffer = null;
87          }
88      }
89  
90      // Java 8: @FunctionalInterface
91      private interface Cleaner {
92  
93          boolean clean(ByteBuffer buffer);
94      }
95  
96      private static class NoopCleaner implements Cleaner {
97  
98          @Override
99          public boolean clean(ByteBuffer buffer) {
100             return false;
101         }
102     }
103 
104     private static class DirectCleaner implements Cleaner {
105 
106         private static final Method ATTACHMENT = getMethod("sun.nio.ch.DirectBuffer", "attachment");
107         private static final Method CLEANER = getMethod("sun.nio.ch.DirectBuffer", "cleaner");
108         private static final Method CLEAN = getMethod("sun.misc.Cleaner", "clean");
109 
110         public static boolean isSupported() {
111             return ATTACHMENT != null && CLEAN != null && CLEANER != null;
112         }
113 
114         /**
115          * Make sure duplicates and slices are not cleaned, since this can result in duplicate attempts to clean the
116          * same buffer, which trigger a crash with: "A fatal error has been detected by the Java Runtime Environment:
117          * EXCEPTION_ACCESS_VIOLATION" See: https://stackoverflow.com/a/31592947/3950982
118          */
119         @Override
120         public boolean clean(ByteBuffer buffer) {
121             try {
122                 if (ATTACHMENT.invoke(buffer) == null) {
123                     CLEAN.invoke(CLEANER.invoke(buffer));
124                     return true;
125                 }
126             } catch (Exception ignore) {
127             }
128             return false;
129         }
130     }
131 
132     private static class UnsafeCleaner implements Cleaner {
133 
134         // Java 9: getMethod("jdk.internal.misc.Unsafe", "invokeCleaner", ByteBuffer.class);
135         private static final Method INVOKE_CLEANER = getMethod("sun.misc.Unsafe", "invokeCleaner", ByteBuffer.class);
136         private static final Object UNSAFE = getField("sun.misc.Unsafe", "theUnsafe");
137 
138         public static boolean isSupported() {
139             return UNSAFE != null && INVOKE_CLEANER != null;
140         }
141 
142         /**
143          * Calling the above code in JDK9+ gives a reflection warning on stderr,
144          * Unsafe.theUnsafe.invokeCleaner(byteBuffer)
145          * makes the same call, but does not print the reflection warning
146          */
147         @Override
148         public boolean clean(ByteBuffer buffer) {
149             try {
150                 // throws IllegalArgumentException if buffer is a duplicate or slice
151                 INVOKE_CLEANER.invoke(UNSAFE, buffer);
152                 return true;
153             } catch (Exception ignore) {
154             }
155             return false;
156         }
157     }
158 }