From: Robert Varga Date: Mon, 22 Apr 2024 08:20:31 +0000 (+0200) Subject: Use Netty to clean mapped buffers X-Git-Tag: v9.0.3~55 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=e1577ac90a997cc9e4839fa33f7fe60051c1fc7e;p=controller.git Use Netty to clean mapped buffers Netty offers PlatformDependent.freeDirectBuffer(), use that to clean our buffers. Change-Id: Id737c9e951bc1c77376133d7053287e2840bafc6 Signed-off-by: Robert Varga --- diff --git a/atomix-storage/pom.xml b/atomix-storage/pom.xml index 8fc7ca3709..bb07137137 100644 --- a/atomix-storage/pom.xml +++ b/atomix-storage/pom.xml @@ -42,6 +42,10 @@ io.netty netty-buffer + + io.netty + netty-common + org.eclipse.jdt org.eclipse.jdt.annotation diff --git a/atomix-storage/src/main/java/io/atomix/storage/journal/BufferCleaner.java b/atomix-storage/src/main/java/io/atomix/storage/journal/BufferCleaner.java deleted file mode 100644 index 8244e5743c..0000000000 --- a/atomix-storage/src/main/java/io/atomix/storage/journal/BufferCleaner.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2019-2022 Open Networking Foundation and others. All rights reserved. - * - * Licensed 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 io.atomix.storage.journal; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Objects; - -import static java.lang.invoke.MethodHandles.constant; -import static java.lang.invoke.MethodHandles.dropArguments; -import static java.lang.invoke.MethodHandles.filterReturnValue; -import static java.lang.invoke.MethodHandles.guardWithTest; -import static java.lang.invoke.MethodHandles.lookup; -import static java.lang.invoke.MethodType.methodType; - -/** - * Utility class which allows explicit calls to the DirectByteBuffer cleaner method instead of relying on GC. - */ -public class BufferCleaner { - - private static final Logger LOGGER = LoggerFactory.getLogger(BufferCleaner.class); - - /** - * Reference to a Cleaner that does unmapping; no-op if not supported. - */ - private static final Cleaner CLEANER; - - static { - final Object hack = AccessController.doPrivileged((PrivilegedAction) BufferCleaner::unmapHackImpl); - if (hack instanceof Cleaner) { - CLEANER = (Cleaner) hack; - LOGGER.debug("java.nio.DirectByteBuffer.cleaner(): available"); - } else { - CLEANER = (ByteBuffer buffer) -> { - // noop - }; - LOGGER.debug("java.nio.DirectByteBuffer.cleaner(): unavailable: {}", hack); - } - } - - private static Object unmapHackImpl() { - final MethodHandles.Lookup lookup = lookup(); - try { - try { - // *** sun.misc.Unsafe unmapping (Java 9+) *** - final Class unsafeClass = Class.forName("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(ByteBuffer.class, unmapper.bindTo(theUnsafe)); - } catch (SecurityException se) { - // rethrow to report errors correctly (we need to catch it here, as we also catch RuntimeException below!): - throw se; - } catch (ReflectiveOperationException | RuntimeException e) { - // *** sun.misc.Cleaner unmapping (Java 8) *** - final Class directBufferClass = Class.forName("java.nio.DirectByteBuffer"); - - final Method m = directBufferClass.getMethod("cleaner"); - m.setAccessible(true); - final MethodHandle directBufferCleanerMethod = lookup.unreflect(m); - final Class cleanerClass = directBufferCleanerMethod.type().returnType(); - - /* "Compile" a MH that basically is equivalent to the following code: - * void unmapper(ByteBuffer byteBuffer) { - * sun.misc.Cleaner cleaner = ((java.nio.DirectByteBuffer) byteBuffer).cleaner(); - * if (Objects.nonNull(cleaner)) { - * cleaner.clean(); - * } else { - * noop(cleaner); // the noop is needed because MethodHandles#guardWithTest always needs ELSE - * } - * } - */ - final MethodHandle cleanMethod = lookup.findVirtual(cleanerClass, "clean", methodType(void.class)); - final MethodHandle nonNullTest = lookup.findStatic(Objects.class, "nonNull", methodType(boolean.class, Object.class)) - .asType(methodType(boolean.class, cleanerClass)); - final MethodHandle noop = dropArguments(constant(Void.class, null).asType(methodType(void.class)), 0, cleanerClass); - final MethodHandle unmapper = filterReturnValue(directBufferCleanerMethod, guardWithTest(nonNullTest, cleanMethod, noop)) - .asType(methodType(void.class, ByteBuffer.class)); - return newBufferCleaner(directBufferClass, unmapper); - } - } 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) { - return "Unmapping is not supported on this platform, because internal Java APIs are not compatible with this Atomix version: " + e; - } - } - - private static Cleaner newBufferCleaner(final Class unmappableBufferClass, final MethodHandle unmapper) { - return (ByteBuffer buffer) -> { - if (!buffer.isDirect()) { - return; - } - if (!unmappableBufferClass.isInstance(buffer)) { - throw new IllegalArgumentException("buffer is not an instance of " + unmappableBufferClass.getName()); - } - final Throwable error = AccessController.doPrivileged((PrivilegedAction) () -> { - try { - unmapper.invokeExact(buffer); - return null; - } catch (Throwable t) { - return t; - } - }); - if (error != null) { - throw new IOException("Unable to unmap the mapped buffer", error); - } - }; - } - - /** - * Free {@link ByteBuffer} if possible. - */ - public static void freeBuffer(ByteBuffer buffer) throws IOException { - CLEANER.freeBuffer(buffer); - } -} diff --git a/atomix-storage/src/main/java/io/atomix/storage/journal/Cleaner.java b/atomix-storage/src/main/java/io/atomix/storage/journal/Cleaner.java deleted file mode 100644 index d81268038f..0000000000 --- a/atomix-storage/src/main/java/io/atomix/storage/journal/Cleaner.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2017-2022 Open Networking Foundation and others. All rights reserved. - * - * Licensed 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 io.atomix.storage.journal; - -import java.io.IOException; -import java.nio.ByteBuffer; - -@FunctionalInterface -interface Cleaner { - - /** - * Free {@link ByteBuffer} if possible. - */ - void freeBuffer(ByteBuffer buffer) throws IOException; -} diff --git a/atomix-storage/src/main/java/io/atomix/storage/journal/MappedFileWriter.java b/atomix-storage/src/main/java/io/atomix/storage/journal/MappedFileWriter.java index d1bbd7d37e..47f26ba151 100644 --- a/atomix-storage/src/main/java/io/atomix/storage/journal/MappedFileWriter.java +++ b/atomix-storage/src/main/java/io/atomix/storage/journal/MappedFileWriter.java @@ -15,6 +15,7 @@ */ package io.atomix.storage.journal; +import io.netty.util.internal.PlatformDependent; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; @@ -91,10 +92,6 @@ final class MappedFileWriter extends FileWriter { @Override void close() { flush(); - try { - BufferCleaner.freeBuffer(mappedBuffer); - } catch (IOException e) { - throw new StorageException(e); - } + PlatformDependent.freeDirectBuffer(mappedBuffer); } }