From fd23532d1ab18a8b20f60c470ce75f81434563d9 Mon Sep 17 00:00:00 2001 From: jovanstevanovic Date: Thu, 31 Mar 2022 17:37:05 +0200 Subject: [PATCH 1/3] SIGPROF signal handler. --- .../posix/PosixSubstrateSigprofHandler.java | 143 +++++++ .../svm/core/posix/headers/Pthread.java | 3 + .../svm/core/IsolateListenerSupport.java | 11 + .../jdk/management/SubstrateThreadMXBean.java | 1 + .../core/jfr/JfrNativeEventWriterData.java | 5 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 4 +- .../svm/core/sampler/SamplerBuffer.java | 100 +++++ .../svm/core/sampler/SamplerBufferAccess.java | 91 +++++ .../svm/core/sampler/SamplerBufferPool.java | 130 ++++++ .../svm/core/sampler/SamplerBufferStack.java | 91 +++++ .../svm/core/sampler/SamplerIsolateLocal.java | 88 ++++ .../svm/core/sampler/SamplerSampleWriter.java | 133 ++++++ .../core/sampler/SamplerSampleWriterData.java | 87 ++++ .../svm/core/sampler/SamplerSpinLock.java | 73 ++++ .../core/sampler/SamplerStackWalkVisitor.java | 49 +++ .../svm/core/sampler/SamplerThreadLocal.java | 141 +++++++ .../core/sampler/SubstrateSigprofHandler.java | 381 ++++++++++++++++++ .../core/thread/ThreadListenerSupport.java | 2 +- .../com/oracle/svm/core/thread/VMThreads.java | 2 + 19 files changed, 1530 insertions(+), 5 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java new file mode 100644 index 000000000000..50541c51b511 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.posix; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.function.CEntryPointLiteral; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.VoidPointer; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.annotate.RestrictHeapAccess; +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.c.function.CEntryPointOptions; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.posix.headers.Pthread; +import com.oracle.svm.core.posix.headers.Signal; +import com.oracle.svm.core.posix.headers.Time; +import com.oracle.svm.core.sampler.SubstrateSigprofHandler; + +@AutomaticFeature +@SuppressWarnings("unused") +class PosixSubstrateSigprofHandlerFeature implements Feature { + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(SubstrateSigprofHandler.class, new PosixSubstrateSigprofHandler()); + } +} + +public class PosixSubstrateSigprofHandler extends SubstrateSigprofHandler { + + public static final long INTERVAL_S = 0; + public static final long INTERVAL_uS = 20_000; + + @Platforms(Platform.HOSTED_ONLY.class) + public PosixSubstrateSigprofHandler() { + } + + /** The address of the signal handler for signals handled by Java code, below. */ + private static final CEntryPointLiteral advancedSignalDispatcher = CEntryPointLiteral.create(PosixSubstrateSigprofHandler.class, + "dispatch", int.class, Signal.siginfo_t.class, Signal.ucontext_t.class); + + @SuppressWarnings("unused") + @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished) + @CEntryPointOptions(prologue = CEntryPointOptions.NoPrologue.class, epilogue = CEntryPointOptions.NoEpilogue.class) + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate in sigprof signal handler.") + @Uninterruptible(reason = "Signal handler may only execute uninterruptible code.") + private static void dispatch(@SuppressWarnings("unused") int signalNumber, @SuppressWarnings("unused") Signal.siginfo_t sigInfo, Signal.ucontext_t uContext) { + tryEnterIsolateAndDoWalk(uContext); + } + + private static void registerSigprofSignal() { + int structSigActionSize = SizeOf.get(Signal.sigaction.class); + Signal.sigaction structSigAction = StackValue.get(structSigActionSize); + LibC.memset(structSigAction, WordFactory.signed(0), WordFactory.unsigned(structSigActionSize)); + + /* Register sa_sigaction signal handler */ + structSigAction.sa_flags(Signal.SA_SIGINFO() | Signal.SA_NODEFER()); + structSigAction.sa_sigaction(advancedSignalDispatcher.getFunctionPointer()); + Signal.sigaction(Signal.SignalEnum.SIGPROF.getCValue(), structSigAction, WordFactory.nullPointer()); + } + + private static int callSetitimer() { + /* Call setitimer to start profiling. */ + Time.itimerval newValue = StackValue.get(Time.itimerval.class); + Time.itimerval oldValue = StackValue.get(Time.itimerval.class); + + newValue.it_value().set_tv_sec(INTERVAL_S); + newValue.it_value().set_tv_usec(INTERVAL_uS); + newValue.it_interval().set_tv_sec(INTERVAL_S); + newValue.it_interval().set_tv_usec(INTERVAL_uS); + + return Time.NoTransitions.setitimer(Time.TimerTypeEnum.ITIMER_PROF, newValue, oldValue); + } + + @Override + protected void install0() { + registerSigprofSignal(); + PosixUtils.checkStatusIs0(callSetitimer(), "setitimer(which, newValue, oldValue): wrong arguments."); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected UnsignedWord createThreadLocalKey() { + Pthread.pthread_key_tPointer key = StackValue.get(Pthread.pthread_key_tPointer.class); + PosixUtils.checkStatusIs0(Pthread.pthread_key_create(key, WordFactory.nullPointer()), "pthread_key_create(key, keyDestructor): failed."); + return key.read(); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected void deleteThreadLocalKey(UnsignedWord key) { + int resultCode = Pthread.pthread_key_delete((Pthread.pthread_key_t) key); + PosixUtils.checkStatusIs0(resultCode, "pthread_key_delete(key): failed."); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected void setThreadLocalKeyValue(UnsignedWord key, IsolateThread value) { + int resultCode = Pthread.pthread_setspecific((Pthread.pthread_key_t) key, (VoidPointer) value); + PosixUtils.checkStatusIs0(resultCode, "pthread_setspecific(key, value): wrong arguments."); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected IsolateThread getThreadLocalKeyValue(UnsignedWord key) { + /* + * Although this method is not async-signal-safe in general we rely on + * implementation-specific behavior here. + */ + return (IsolateThread) Pthread.pthread_getspecific((Pthread.pthread_key_t) key); + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Pthread.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Pthread.java index fb447ac4ad57..c3163b6c2540 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Pthread.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Pthread.java @@ -177,6 +177,9 @@ public interface pthread_key_tPointer extends PointerBase { @CFunction(transition = Transition.NO_TRANSITION) public static native int pthread_key_create(pthread_key_tPointer key, PointerBase keyDestructor); + @CFunction(transition = Transition.NO_TRANSITION) + public static native int pthread_key_delete(pthread_key_t key); + @CFunction(transition = Transition.NO_TRANSITION) public static native int pthread_setspecific(pthread_key_t key, VoidPointer value); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java index ed4c2d76447b..c2bf212c4c8a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java @@ -62,8 +62,19 @@ public void afterCreateIsolate(Isolate isolate) { } } + @Uninterruptible(reason = "The isolate teardown is in progress.") + public void onIsolateTeardown() { + for (int i = 0; i < listeners.length; i++) { + listeners[i].onIsolateTeardown(); + } + } + public interface IsolateListener { @Uninterruptible(reason = "Thread state not yet set up.") void afterCreateIsolate(Isolate isolate); + + @Uninterruptible(reason = "The isolate teardown is in progress.") + default void onIsolateTeardown() { + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/SubstrateThreadMXBean.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/SubstrateThreadMXBean.java index ecc54763cd03..322a5487b911 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/SubstrateThreadMXBean.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/SubstrateThreadMXBean.java @@ -115,6 +115,7 @@ public boolean isCurrentThreadCpuTimeSupported() { } @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public int getThreadCount() { return threadCount.get(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java index 5d13e056578c..a67410ee653f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriterData.java @@ -73,14 +73,13 @@ public interface JfrNativeEventWriterData extends PointerBase { void setCurrentPos(Pointer value); /** - * Returns the end position for the current event write. Writing of data cannot exceed this - * position. + * Returns the position where the buffer ends. Writing of data cannot exceed this position. */ @RawField Pointer getEndPos(); /** - * Sets the end position for the current event write. + * Sets the position where the buffer ends. */ @RawField void setEndPos(Pointer value); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java index 7fe7efb266bb..2c6d918d12c6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java @@ -261,7 +261,9 @@ public long getThreadId(Thread thread) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public long getThreadId(IsolateThread isolateThread) { - return threadLocal.getTraceId(isolateThread); + long threadId = threadLocal.getTraceId(isolateThread); + VMError.guarantee(threadId > 0); + return threadId; } /** See {@link JVM#storeMetadataDescriptor}. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java new file mode 100644 index 000000000000..5e5f95e17776 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.UnsignedWord; + +/** + * A {@link SamplerBuffer} is a block of native memory into which the results of stack walks are + * written. + */ +@RawStructure +interface SamplerBuffer extends PointerBase { + + /** + * Returns a buffer that is next in the {@link SamplerBufferStack}, otherwise null. + */ + @RawField + SamplerBuffer getNext(); + + /** + * Sets a buffer as a new head in the {@link SamplerBufferStack}. + */ + @RawField + void setNext(SamplerBuffer buffer); + + /** + * Returns the JFR id of the thread that owns this buffer. + */ + @RawField + long getOwner(); + + /** + * Sets the JFR id of the thread that owns this buffer. + */ + @RawField + void setOwner(long threadId); + + /** + * Returns the current position. Any data before this position is valid sample data. + */ + @RawField + Pointer getPos(); + + /** + * Sets the current position. + */ + @RawField + void setPos(Pointer pos); + + /** + * Returns the size of the buffer. This excludes the header of the buffer. + */ + @RawField + UnsignedWord getSize(); + + /** + * Sets the size of the buffer. + */ + @RawField + void setSize(UnsignedWord value); + + /** + * Should this buffer be freed after processing the data in it. + */ + @RawField + boolean getFreeable(); + + /** + * Sets the freeable status of the buffer. + */ + @RawField + void setFreeable(boolean freeable); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java new file mode 100644 index 000000000000..f8021d4ee885 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.util.UnsignedUtils; + +/** + * Used to access the raw memory of a {@link SamplerBufferAccess}. + */ +final class SamplerBufferAccess { + + private SamplerBufferAccess() { + } + + @Fold + public static UnsignedWord getHeaderSize() { + return UnsignedUtils.roundUp(SizeOf.unsigned(SamplerBuffer.class), WordFactory.unsigned(ConfigurationValues.getTarget().wordSize)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static SamplerBuffer allocate(UnsignedWord dataSize) { + UnsignedWord headerSize = SamplerBufferAccess.getHeaderSize(); + SamplerBuffer result = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(headerSize.add(dataSize)); + if (result.isNonNull()) { + result.setSize(dataSize); + result.setFreeable(false); + reinitialize(result); + } + return result; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void free(SamplerBuffer buffer) { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(buffer); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void reinitialize(SamplerBuffer buffer) { + Pointer dataStart = getDataStart(buffer); + buffer.setPos(dataStart); + buffer.setOwner(0L); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static Pointer getDataStart(SamplerBuffer buffer) { + return ((Pointer) buffer).add(getHeaderSize()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isEmpty(SamplerBuffer buffer) { + return getDataStart(buffer).equal(buffer.getPos()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static Pointer getDataEnd(SamplerBuffer buffer) { + return getDataStart(buffer).add(buffer.getSize()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java new file mode 100644 index 000000000000..58243c0d8dba --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.locks.VMMutex; +import com.oracle.svm.core.util.VMError; + +import jdk.jfr.internal.Options; + +/** + * The pool that maintains the desirable number of buffers in the system by allocating/releasing + * extra buffers. + */ +class SamplerBufferPool { + + private static final long THREAD_BUFFER_SIZE = Options.getThreadBufferSize(); + + private static final VMMutex mutex = new VMMutex("profilerBufferUtils"); + + private static long bufferCount; + + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) + public static void adjustBufferCount(SamplerBuffer threadLocalBuffer) { + mutex.lockNoTransition(); + try { + releaseThreadLocalBuffer(threadLocalBuffer); + adjustBufferCount0(); + } finally { + mutex.unlock(); + } + } + + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) + public static void adjustBufferCount() { + mutex.lockNoTransition(); + try { + adjustBufferCount0(); + } finally { + mutex.unlock(); + } + } + + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) + public static void adjustBufferCount0() { + long diff = diff(); + if (diff > 0) { + for (int i = 0; i < diff; i++) { + allocateAndPush(); + } + } else { + for (long i = diff; i < 0; i++) { + if (!popAndFree()) { + break; + } + } + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static void allocateAndPush() { + VMError.guarantee(bufferCount >= 0); + SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(THREAD_BUFFER_SIZE)); + if (buffer.isNull()) { + return; + } + SubstrateSigprofHandler.availableBuffers().pushBuffer(buffer); + bufferCount++; + } + + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) + private static void releaseThreadLocalBuffer(SamplerBuffer buffer) { + /* buffer will be null if no stack walk were performed. */ + if (buffer.isNonNull()) { + if (SamplerBufferAccess.isEmpty(buffer)) { + /* We can free it right away. */ + SamplerBufferAccess.free(buffer); + } else { + /* Put it in the stack with other unprocessed buffers. */ + buffer.setFreeable(true); + SubstrateSigprofHandler.fullBuffers().pushBuffer(buffer); + } + VMError.guarantee(bufferCount > 0); + bufferCount--; + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean popAndFree() { + VMError.guarantee(bufferCount > 0); + SamplerBuffer buffer = SubstrateSigprofHandler.availableBuffers().popBuffer(); + if (buffer.isNonNull()) { + SamplerBufferAccess.free(buffer); + bufferCount--; + return false; + } + return true; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static long diff() { + double diffD = SubstrateSigprofHandler.getSubstrateThreadMXBean().getThreadCount() * 1.5 - bufferCount; + return (long) (diffD + 0.5); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java new file mode 100644 index 000000000000..db7a82e44577 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.annotate.Uninterruptible; + +/** + * The linked-list implementation of the stack that holds a sequence of native memory buffers. + * + * The stack uses spin-lock to protect itself from races with competing pop operations (ABA + * problem). + * + * @see SamplerSpinLock + */ +class SamplerBufferStack { + + private SamplerBuffer head; + private final SamplerSpinLock spinLock; + + @Platforms(Platform.HOSTED_ONLY.class) + SamplerBufferStack() { + this.spinLock = new SamplerSpinLock(); + } + + /** + * Push a profiler buffer into the global linked list. + */ + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + public void pushBuffer(SamplerBuffer buffer) { + spinLock.lock(); + try { + buffer.setNext(head); + head = buffer; + } finally { + spinLock.unlock(); + } + } + + /** + * Pop a profiler buffer from the global linked list. Returns {@code null} if the list is empty. + */ + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + public SamplerBuffer popBuffer() { + spinLock.lock(); + try { + SamplerBuffer result = head; + if (result.isNonNull()) { + head = head.getNext(); + result.setNext(WordFactory.nullPointer()); + } + return result; + } finally { + spinLock.unlock(); + } + } + + /** + * Returns the lock that this stack is using. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isLockedByCurrentThread() { + return spinLock.isOwner(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java new file mode 100644 index 000000000000..28b00171363f --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.Isolate; +import org.graalvm.word.LocationIdentity; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.IsolateListenerSupport; +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; + +class SamplerIsolateLocal implements IsolateListenerSupport.IsolateListener { + + /** Stores the address of the first isolate created. */ + private static final CGlobalData firstIsolate = CGlobalDataFactory.createWord(); + + /** Stores the isolate-specific key. */ + private static UnsignedWord key = WordFactory.unsigned(0); + + @Override + @Uninterruptible(reason = "Thread state not yet set up.") + public void afterCreateIsolate(Isolate isolate) { + if (SubstrateSigprofHandler.isProfilingSupported()) { + if (firstIsolate.get().logicCompareAndSwapWord(0, WordFactory.zero(), isolate, LocationIdentity.ANY_LOCATION)) { + key = SubstrateSigprofHandler.singleton().createThreadLocalKey(); + } + } + } + + @Override + @Uninterruptible(reason = "The isolate teardown is in progress.") + public void onIsolateTeardown() { + if (SubstrateSigprofHandler.isProfilingEnabled() && isKeySet()) { + /* Invalidate the isolate-specific key. */ + UnsignedWord oldKey = key; + key = WordFactory.unsigned(0); + + /* We need to manually call teardown because isKeySet will now return false. */ + SamplerThreadLocal.teardown(CurrentIsolate.getCurrentThread()); + + /* Now, it's safe to delete the isolate-specific key. */ + SubstrateSigprofHandler.singleton().deleteThreadLocalKey(oldKey); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static Isolate getIsolate() { + return firstIsolate.get().readWord(0); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static UnsignedWord getKey() { + return key; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isKeySet() { + return key.aboveThan(0); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java new file mode 100644 index 000000000000..89802833d164 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.util.VMError; + +class SamplerSampleWriter { + + private static final int END_MARKER_SIZE = Long.BYTES; + private static final long END_MARKER = -1; + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean putLong(SamplerSampleWriterData data, long value) { + if (ensureSize(data, Long.BYTES)) { + data.getCurrentPos().writeLong(0, value); + increaseCurrentPos(data, WordFactory.unsigned(Long.BYTES)); + return true; + } else { + return false; + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void commit(SamplerSampleWriterData data) { + SamplerBuffer buffer = data.getSamplerBuffer(); + /* + * put END_MARKER should not fail as ensureSize takes end marker size in consideration. + */ + VMError.guarantee(getAvailableSize(data).aboveOrEqual(END_MARKER_SIZE)); + data.getCurrentPos().writeLong(0, END_MARKER); + increaseCurrentPos(data, WordFactory.unsigned(Long.BYTES)); + + buffer.setPos(data.getCurrentPos()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean ensureSize(SamplerSampleWriterData data, int requested) { + assert requested > 0; + int totalRequested = requested + END_MARKER_SIZE; + if (getAvailableSize(data).belowThan(totalRequested)) { + if (!accommodate(data, getUncommittedSize(data))) { + return false; + } + } + assert getAvailableSize(data).aboveOrEqual(totalRequested); + return true; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean accommodate(SamplerSampleWriterData data, UnsignedWord uncommitted) { + if (SamplerBufferAccess.isEmpty(data.getSamplerBuffer())) { + /* + * Sample is too big to fit into the size of one buffer i.e. we want to do + * accommodations while nothing is committed into buffer. + */ + SamplerThreadLocal.increaseMissedSamples(); + return false; + } + + /* Pop first free buffer from the pool. */ + SamplerBuffer newBuffer = SubstrateSigprofHandler.availableBuffers().popBuffer(); + if (newBuffer.isNull()) { + /* No available buffers on the pool. Fallback! */ + SamplerThreadLocal.increaseMissedSamples(); + return false; + } + SamplerThreadLocal.setThreadLocalBuffer(newBuffer); + + /* Copy the uncommitted content of old buffer into new one. */ + UnmanagedMemoryUtil.copy(data.getStartPos(), SamplerBufferAccess.getDataStart(newBuffer), uncommitted); + + /* Put in the stack with other unprocessed buffers. */ + SamplerBuffer oldBuffer = data.getSamplerBuffer(); + SubstrateSigprofHandler.fullBuffers().pushBuffer(oldBuffer); + + /* Reinitialize data structure. */ + data.setSamplerBuffer(newBuffer); + reset(data); + increaseCurrentPos(data, uncommitted); + return true; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static UnsignedWord getAvailableSize(SamplerSampleWriterData data) { + return data.getEndPos().subtract(data.getCurrentPos()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static UnsignedWord getUncommittedSize(SamplerSampleWriterData data) { + return data.getCurrentPos().subtract(data.getStartPos()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static void increaseCurrentPos(SamplerSampleWriterData data, UnsignedWord delta) { + data.setCurrentPos(data.getCurrentPos().add(delta)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static void reset(SamplerSampleWriterData data) { + SamplerBuffer buffer = data.getSamplerBuffer(); + data.setStartPos(buffer.getPos()); + data.setCurrentPos(buffer.getPos()); + data.setEndPos(SamplerBufferAccess.getDataEnd(buffer)); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java new file mode 100644 index 000000000000..49cdaaf18635 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterData.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; + +/** + * A data structure that holds the mutable state of a {@link SamplerSampleWriter}. Typically, it is + * allocated on the stack. + */ +@RawStructure +interface SamplerSampleWriterData extends PointerBase { + /** + * Gets the buffer that data will be written to. + */ + @RawField + SamplerBuffer getSamplerBuffer(); + + /** + * Sets the buffer that data will be written to. + */ + @RawField + void setSamplerBuffer(SamplerBuffer buffer); + + /** + * Gets the start position for the current sample write. + */ + @RawField + Pointer getStartPos(); + + /** + * Sets the start position for the current sample write. + */ + @RawField + void setStartPos(Pointer value); + + /** + * Gets the current position of the sample write. This position is moved forward as data is + * written for a sample. + */ + @RawField + Pointer getCurrentPos(); + + /** + * Sets the current position of the sample write. + */ + @RawField + void setCurrentPos(Pointer value); + + /** + * Returns the position where the buffer ends. Writing of data cannot exceed this position. + */ + @RawField + Pointer getEndPos(); + + /** + * Sets the position where the buffer ends. + */ + @RawField + void setEndPos(Pointer value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java new file mode 100644 index 000000000000..97ed3f3165e5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import com.oracle.svm.core.util.VMError; +import org.graalvm.compiler.nodes.PauseNode; +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleUtils; + +/** + * The custom implementation of spin lock that is async signal safe. + * + * In some specific situations, the signal handler can interrupt execution while the same thread + * already has the lock. Other spin lock implementations can deadlock in such a case. So it is + * essential to check if the current thread is the owner of the lock, before acquiring it. + */ +class SamplerSpinLock { + private final UninterruptibleUtils.AtomicPointer owner; + + @Platforms(Platform.HOSTED_ONLY.class) + SamplerSpinLock() { + this.owner = new UninterruptibleUtils.AtomicPointer<>(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean isOwner() { + return owner.get().equal(CurrentIsolate.getCurrentThread()); + } + + @Uninterruptible(reason = "This method does not do a transition, so the whole critical section must be uninterruptible.", callerMustBe = true) + public void lock() { + VMError.guarantee(!isOwner(), "The current thread already has the lock!"); + IsolateThread currentThread = CurrentIsolate.getCurrentThread(); + while (!owner.compareAndSet(WordFactory.nullPointer(), currentThread)) { + PauseNode.pause(); + } + } + + @Uninterruptible(reason = "The whole critical section must be uninterruptible.", callerMustBe = true) + public void unlock() { + VMError.guarantee(isOwner(), "The current thread doesn't have the lock!"); + owner.compareAndSet(CurrentIsolate.getCurrentThread(), WordFactory.nullPointer()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java new file mode 100644 index 000000000000..3998a5b550d4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerStackWalkVisitor.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.word.Pointer; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.code.CodeInfo; +import com.oracle.svm.core.deopt.DeoptimizedFrame; +import com.oracle.svm.core.stack.ParameterizedStackFrameVisitor; + +final class SamplerStackWalkVisitor extends ParameterizedStackFrameVisitor { + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame, Void voidData) { + return SamplerSampleWriter.putLong(SamplerThreadLocal.getWriterData(), ip.rawValue()); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected boolean unknownFrame(Pointer sp, CodePointer ip, DeoptimizedFrame deoptimizedFrame, Void data) { + SamplerThreadLocal.increaseUnparseableStacks(); + return false; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java new file mode 100644 index 000000000000..66c83f47ab54 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.thread.ThreadListener; +import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; +import com.oracle.svm.core.threadlocal.FastThreadLocalLong; +import com.oracle.svm.core.threadlocal.FastThreadLocalWord; + +class SamplerThreadLocal implements ThreadListener { + + private static final FastThreadLocalWord localBuffer = FastThreadLocalFactory.createWord("SamplerThreadLocal.localBuffer"); + private static final FastThreadLocalLong missedSamples = FastThreadLocalFactory.createLong("SamplerThreadLocal.missedSamples"); + private static final FastThreadLocalLong unparseableStacks = FastThreadLocalFactory.createLong("SamplerThreadLocal.unparseableStacks"); + /** + * The data that we are using during the stack walk, allocated on the stack. + */ + private static final FastThreadLocalWord writerData = FastThreadLocalFactory.createWord("SamplerThreadLocal.writerData"); + + @Override + @Uninterruptible(reason = "Only uninterruptible code may be executed before Thread.run.") + public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { + if (SubstrateSigprofHandler.isProfilingEnabled()) { + initialize(isolateThread); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static void initialize(IsolateThread isolateThread) { + if (SamplerIsolateLocal.isKeySet()) { + /* Adjust the number of buffers. */ + SamplerBufferPool.adjustBufferCount(); + + /* + * Save isolate thread in thread-local area. + * + * Once this value is set, the signal handler may interrupt this thread at any time. So, + * it is essential that this value is set at the very end of this method. + */ + UnsignedWord key = SamplerIsolateLocal.getKey(); + SubstrateSigprofHandler.singleton().setThreadLocalKeyValue(key, isolateThread); + } + } + + @Override + @Uninterruptible(reason = "Only uninterruptible code may be executed after Thread.exit.") + public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { + if (SubstrateSigprofHandler.isProfilingEnabled() && SamplerIsolateLocal.isKeySet()) { + teardown(isolateThread); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static void teardown(IsolateThread isolateThread) { + /* + * Invalidate thread-local area. + * + * Once this value is set to null, the signal handler can't interrupt this thread anymore. + * So, it is essential that this value is set at the very beginning of this method i.e. + * before doing cleanup. + */ + UnsignedWord key = SamplerIsolateLocal.getKey(); + SubstrateSigprofHandler.singleton().setThreadLocalKeyValue(key, WordFactory.nullPointer()); + + /* Adjust the number of buffers (including the thread-local buffer). */ + SamplerBuffer threadLocalBuffer = localBuffer.get(isolateThread); + SamplerBufferPool.adjustBufferCount(threadLocalBuffer); + localBuffer.set(isolateThread, WordFactory.nullPointer()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static SamplerBuffer getThreadLocalBuffer() { + return localBuffer.get(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void setThreadLocalBuffer(SamplerBuffer buffer) { + buffer.setOwner(SubstrateJVM.get().getThreadId(CurrentIsolate.getCurrentThread())); + localBuffer.set(buffer); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void increaseMissedSamples() { + missedSamples.set(getMissedSamples() + 1); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long getMissedSamples() { + return missedSamples.get(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void increaseUnparseableStacks() { + unparseableStacks.set(getUnparseableStacks() + 1); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static long getUnparseableStacks() { + return unparseableStacks.get(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void setWriterData(SamplerSampleWriterData data) { + writerData.set(data); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static SamplerSampleWriterData getWriterData() { + return writerData.get(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java new file mode 100644 index 000000000000..09daa64dfd85 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.sampler; + +import java.lang.management.ManagementFactory; +import java.util.Arrays; +import java.util.List; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.options.Option; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Isolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; + +import com.oracle.svm.core.IsolateListenerSupport; +import com.oracle.svm.core.RegisterDumper; +import com.oracle.svm.core.VMInspectionOptions; +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.code.CodeInfo; +import com.oracle.svm.core.code.CodeInfoAccess; +import com.oracle.svm.core.code.CodeInfoTable; +import com.oracle.svm.core.graal.nodes.WriteCurrentVMThreadNode; +import com.oracle.svm.core.graal.nodes.WriteHeapBaseNode; +import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.jdk.RuntimeSupport; +import com.oracle.svm.core.jdk.management.ManagementFeature; +import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean; +import com.oracle.svm.core.jfr.JfrFeature; +import com.oracle.svm.core.option.RuntimeOptionKey; +import com.oracle.svm.core.stack.JavaFrameAnchor; +import com.oracle.svm.core.stack.JavaFrameAnchors; +import com.oracle.svm.core.stack.JavaStackWalker; +import com.oracle.svm.core.thread.JavaVMOperation; +import com.oracle.svm.core.thread.ThreadListenerFeature; +import com.oracle.svm.core.thread.ThreadListenerSupport; +import com.oracle.svm.core.thread.VMThreads; + +@AutomaticFeature +@SuppressWarnings("unused") +class SubstrateSigprofHandlerFeature implements Feature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return VMInspectionOptions.AllowVMInspection.getValue() && SubstrateSigprofHandler.isProfilingSupported(); + } + + @Override + public List> getRequiredFeatures() { + return Arrays.asList(ThreadListenerFeature.class, JfrFeature.class, ManagementFeature.class); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + if (!ImageSingletons.contains(SubstrateSigprofHandler.class)) { + /* No sigprof handler. */ + return; + } + + /* Create stack visitor. */ + ImageSingletons.add(SamplerStackWalkVisitor.class, new SamplerStackWalkVisitor()); + + /* Add listeners. */ + ThreadListenerSupport.get().register(new SamplerThreadLocal()); + IsolateListenerSupport.singleton().register(new SamplerIsolateLocal()); + + /* Add startup hook. */ + RuntimeSupport.getRuntimeSupport().addStartupHook(new SubstrateSigprofHandlerStartupHook()); + } +} + +final class SubstrateSigprofHandlerStartupHook implements RuntimeSupport.Hook { + @Override + public void execute(boolean isFirstIsolate) { + if (isFirstIsolate) { + SubstrateSigprofHandler.singleton().install(); + } + } +} + +/** + *

+ * The core class of low overhead asynchronous sampling based profiler. + * {@link SubstrateSigprofHandler} handles the periodic signal generated by the OS with a given + * time-frequency. The asynchronous nature of the signal means that the OS could invoke the signal + * handler at any time (could be during GC, VM operation, uninterruptible code) and that the signal + * handler can only execute specific code i.e. the calls that are asynchronous signal safe. + *

+ * + *

+ * The signal handler is divided into three part: restore isolate, isolate-thread, stack and + * instruction pointers, prepare everything necessary for stack walk, do a stack walk and write IPs + * into buffer. + *

+ * + *

+ * The signal handler is as a producer. On the other side of relation is + * {@link com.oracle.svm.core.jfr.JfrRecorderThread} that is consumer. The + * {@link SamplerBuffer} that we are using in this consumer-producer communication is allocated + * eagerly, in a part of the heap that is not accessible via GC, and there will always be more + * available buffers that threads. + *

+ * + *

+ * The communication between consumer and producer goes as follows: + *

    + *
  • Signal handler (producer): pops the buffer from the pool of available buffers (the buffer now + * becomes thread-local), writes the IPs into buffer, if the buffer is full and moves it to a pool + * with buffers that awaits processing.
  • + *
  • Recorder thread (consumer): pops the buffer from the pool of full buffers, reconstructs the + * stack walk based on IPs and pushes the buffer into pool of available buffers.
  • + *
+ * NOTE: The producer and the consumer are always accessing different buffers. + *

+ * + *

+ * In some rare cases, the profiling is impossible e.g. no available buffers in the pool, unknown IP + * during stack walk, the thread holds the pool's lock when the signal arrives, etc. + *

+ * + * @see SamplerSpinLock + * @see SamplerBufferStack + */ +public abstract class SubstrateSigprofHandler { + + public static class Options { + @Option(help = "Allow sampling-based profiling. Default: disabled in execution.")// + static final RuntimeOptionKey SamplingBasedProfiling = new RuntimeOptionKey<>(Boolean.FALSE); + } + + private boolean enabled; + private final SamplerBufferStack availableBuffers; + private final SamplerBufferStack fullBuffers; + private SubstrateThreadMXBean threadMXBean; + + @Platforms(Platform.HOSTED_ONLY.class) + protected SubstrateSigprofHandler() { + this.availableBuffers = new SamplerBufferStack(); + this.fullBuffers = new SamplerBufferStack(); + } + + @Fold + static SubstrateSigprofHandler singleton() { + return ImageSingletons.lookup(SubstrateSigprofHandler.class); + } + + @Fold + static SamplerStackWalkVisitor visitor() { + return ImageSingletons.lookup(SamplerStackWalkVisitor.class); + } + + @Fold + static boolean isProfilingSupported() { + return Platform.includedIn(Platform.LINUX.class); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static boolean isProfilingEnabled() { + return singleton().enabled; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static SamplerBufferStack availableBuffers() { + return singleton().availableBuffers; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static SamplerBufferStack fullBuffers() { + return singleton().fullBuffers; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + static SubstrateThreadMXBean getSubstrateThreadMXBean() { + return singleton().threadMXBean; + } + + /** + * Installs the platform dependent sigprof handler. + */ + void install() { + if (Options.SamplingBasedProfiling.getValue()) { + threadMXBean = (SubstrateThreadMXBean) ManagementFactory.getThreadMXBean(); + /* Call VM operation to initialize the profiler and the threads. */ + InitializeProfilerOperation initializeProfilerOperation = new InitializeProfilerOperation(); + initializeProfilerOperation.enqueue(); + + /* After the VM operations finishes. Install handler and start profiling. */ + install0(); + } + } + + protected abstract void install0(); + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected abstract UnsignedWord createThreadLocalKey(); + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected abstract void deleteThreadLocalKey(UnsignedWord key); + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected abstract void setThreadLocalKeyValue(UnsignedWord key, IsolateThread value); + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + protected abstract IsolateThread getThreadLocalKeyValue(UnsignedWord key); + + /** Is sigprof handler called from native code? */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean isIPInJavaCode(RegisterDumper.Context uContext) { + Pointer ip = (Pointer) RegisterDumper.singleton().getIP(uContext); + CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo(); + Pointer codeStart = (Pointer) CodeInfoAccess.getCodeStart(codeInfo); + UnsignedWord codeSize = CodeInfoAccess.getCodeSize(codeInfo); + return ip.aboveOrEqual(codeStart) && ip.belowOrEqual(codeStart.add(codeSize)); + } + + /** + * Walk the current stacktrace using isolate thread local area and isolate heap base. + */ + @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true) + private static void doUninterruptibleStackWalk(RegisterDumper.Context uContext, boolean isIPInJavaCode) { + CodePointer ip; + Pointer sp; + if (isIPInJavaCode) { + ip = (CodePointer) RegisterDumper.singleton().getIP(uContext); + sp = (Pointer) RegisterDumper.singleton().getSP(uContext); + } else { + JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor(); + if (anchor.isNull()) { + /* + * The anchor is still null if the function is interrupted during prologue. See: + * com.oracle.svm.core.graal.snippets.CFunctionSnippets.prologueSnippet + */ + return; + } + + ip = anchor.getLastJavaIP(); + sp = anchor.getLastJavaSP(); + + if (ip.isNull() || sp.isNull()) { + /* + * It can happen that anchor is in list of all anchors, but its IP and SP are not + * filled yet. + */ + return; + } + } + + /* Initialize stack walk. */ + SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class); + if (prepareStackWalk(data)) { + /* Walk the stack. */ + if (JavaStackWalker.walkCurrentThread(sp, ip, visitor())) { + SamplerSampleWriter.commit(data); + } + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean prepareStackWalk(SamplerSampleWriterData data) { + if (availableBuffers().isLockedByCurrentThread() || fullBuffers().isLockedByCurrentThread()) { + /* + * The current thread already holds the stack lock, so we can't access it. It's way + * better to lose one sample, then potentially the whole buffer. + */ + SamplerThreadLocal.increaseMissedSamples(); + return false; + } + + SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer(); + if (buffer.isNull()) { + /* Pop first free buffer from the pool. */ + buffer = availableBuffers().popBuffer(); + if (buffer.isNull()) { + /* No available buffers on the pool. Fallback! */ + SamplerThreadLocal.increaseMissedSamples(); + return false; + } + SamplerThreadLocal.setThreadLocalBuffer(buffer); + } + + /* Initialize the buffer. */ + data.setSamplerBuffer(buffer); + data.setStartPos(buffer.getPos()); + data.setCurrentPos(buffer.getPos()); + data.setEndPos(SamplerBufferAccess.getDataEnd(buffer)); + SamplerThreadLocal.setWriterData(data); + return true; + } + + /** Called from the platform dependent sigprof handler to enter isolate. */ + @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true) + protected static void tryEnterIsolateAndDoWalk(RegisterDumper.Context uContext) { + if (!SamplerIsolateLocal.isKeySet()) { + /* The key is set for initial isolate only. */ + return; + } + + Isolate isolate = SamplerIsolateLocal.getIsolate(); + if (isolate.isNull()) { + /* It may happen that the initial isolate exited. */ + return; + } + + /* Write isolate pointer (heap base) into register. */ + WriteHeapBaseNode.writeCurrentVMHeapBase(isolate); + + /* We are keeping reference to isolate thread inside OS thread local area. */ + UnsignedWord key = SamplerIsolateLocal.getKey(); + IsolateThread thread = singleton().getThreadLocalKeyValue(key); + if (thread.isNull()) { + /* Thread is not yet initialized or already detached from isolate. */ + return; + } + + /* Write isolate thread pointer into register. */ + WriteCurrentVMThreadNode.writeCurrentVMThread(thread); + + /* Check if the instruction pointer (IP) is inside native or java code. */ + boolean isIPInJavaCode = isIPInJavaCode(uContext); + + /* Walk the stack. */ + doUninterruptibleStackWalk(uContext, isIPInJavaCode); + } + + private class InitializeProfilerOperation extends JavaVMOperation { + + protected InitializeProfilerOperation() { + super(VMOperationInfos.get(InitializeProfilerOperation.class, "Initialize Profiler", SystemEffect.SAFEPOINT)); + } + + @Override + protected void operate() { + initialize(); + } + + /** + * We need to ensure that all threads are properly initialized at a moment when we start a + * profiling. + */ + @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") + private void initialize() { + enabled = true; + /* + * Iterate all over all thread and initialize the thread-local storage of each thread. + */ + for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + SamplerThreadLocal.initialize(thread); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java index 2436aaf19898..a0283ea6b14b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadListenerSupport.java @@ -65,7 +65,7 @@ public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) { @Uninterruptible(reason = "Force that all listeners are uninterruptible.") public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { - for (int i = 0; i < listeners.length; i++) { + for (int i = listeners.length - 1; i >= 0; i--) { listeners[i].afterThreadExit(isolateThread, javaThread); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java index fd596ed88cca..6a28ba4f18ef 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.thread; +import com.oracle.svm.core.IsolateListenerSupport; import org.graalvm.compiler.api.directives.GraalDirectives; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.replacements.ReplacementsUtil; @@ -450,6 +451,7 @@ public void tearDown() { VMOperationControl.shutdownAndDetachVMOperationThread(); } // At this point, it is guaranteed that all other threads were detached. + IsolateListenerSupport.singleton().onIsolateTeardown(); waitUntilLastOsThreadExited(); } From b7af1f99691c9f5cafde270d7567b5b72ff1bd6c Mon Sep 17 00:00:00 2001 From: jovanstevanovic Date: Thu, 12 May 2022 09:18:23 +0200 Subject: [PATCH 2/3] Changes according to comments. --- .../svm/core/sampler/SamplerBuffer.java | 4 +- .../svm/core/sampler/SamplerBufferPool.java | 81 +++++++++---------- .../svm/core/sampler/SamplerBufferStack.java | 7 +- .../svm/core/sampler/SamplerIsolateLocal.java | 4 +- .../svm/core/sampler/SamplerSampleWriter.java | 9 ++- .../svm/core/sampler/SamplerSpinLock.java | 7 +- .../svm/core/sampler/SamplerThreadLocal.java | 6 +- .../core/sampler/SubstrateSigprofHandler.java | 22 ++--- 8 files changed, 70 insertions(+), 70 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java index 5e5f95e17776..d7c1dfa20c2e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java @@ -39,13 +39,13 @@ interface SamplerBuffer extends PointerBase { /** - * Returns a buffer that is next in the {@link SamplerBufferStack}, otherwise null. + * Returns the buffer that is next in the {@link SamplerBufferStack}, otherwise null. */ @RawField SamplerBuffer getNext(); /** - * Sets a buffer as a new head in the {@link SamplerBufferStack}. + * Sets the successor to this node in the {@link SamplerBufferStack}. */ @RawField void setNext(SamplerBuffer buffer); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java index 58243c0d8dba..e50ebc6533b4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java @@ -41,61 +41,48 @@ class SamplerBufferPool { private static final long THREAD_BUFFER_SIZE = Options.getThreadBufferSize(); - private static final VMMutex mutex = new VMMutex("profilerBufferUtils"); + private static final VMMutex mutex = new VMMutex("SamplerBufferPool"); private static long bufferCount; @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) - public static void adjustBufferCount(SamplerBuffer threadLocalBuffer) { - mutex.lockNoTransition(); - try { - releaseThreadLocalBuffer(threadLocalBuffer); - adjustBufferCount0(); - } finally { - mutex.unlock(); - } + public static void releaseBufferAndAdjustCount(SamplerBuffer threadLocalBuffer) { + adjustBufferCount0(threadLocalBuffer); } @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) public static void adjustBufferCount() { - mutex.lockNoTransition(); - try { - adjustBufferCount0(); - } finally { - mutex.unlock(); - } + adjustBufferCount0(WordFactory.nullPointer()); } @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) - public static void adjustBufferCount0() { - long diff = diff(); - if (diff > 0) { - for (int i = 0; i < diff; i++) { - allocateAndPush(); - } - } else { - for (long i = diff; i < 0; i++) { - if (!popAndFree()) { - break; + private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) { + mutex.lockNoTransition(); + try { + releaseThreadLocalBuffer(threadLocalBuffer); + long diff = diff(); + if (diff > 0) { + for (int i = 0; i < diff; i++) { + allocateAndPush(); + } + } else { + for (long i = diff; i < 0; i++) { + if (!popAndFree()) { + break; + } } } + } finally { + mutex.unlock(); } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static void allocateAndPush() { - VMError.guarantee(bufferCount >= 0); - SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(THREAD_BUFFER_SIZE)); - if (buffer.isNull()) { - return; - } - SubstrateSigprofHandler.availableBuffers().pushBuffer(buffer); - bufferCount++; - } - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) private static void releaseThreadLocalBuffer(SamplerBuffer buffer) { - /* buffer will be null if no stack walk were performed. */ + /* + * buffer is null if the thread is not running yet, or we did not perform the stack walk for + * this thread during the run. + */ if (buffer.isNonNull()) { if (SamplerBufferAccess.isEmpty(buffer)) { /* We can free it right away. */ @@ -103,28 +90,40 @@ private static void releaseThreadLocalBuffer(SamplerBuffer buffer) { } else { /* Put it in the stack with other unprocessed buffers. */ buffer.setFreeable(true); - SubstrateSigprofHandler.fullBuffers().pushBuffer(buffer); + SubstrateSigprofHandler.singleton().fullBuffers().pushBuffer(buffer); } VMError.guarantee(bufferCount > 0); bufferCount--; } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static void allocateAndPush() { + VMError.guarantee(bufferCount >= 0); + SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(THREAD_BUFFER_SIZE)); + if (buffer.isNull()) { + return; + } + SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); + bufferCount++; + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean popAndFree() { VMError.guarantee(bufferCount > 0); - SamplerBuffer buffer = SubstrateSigprofHandler.availableBuffers().popBuffer(); + SamplerBuffer buffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer(); if (buffer.isNonNull()) { SamplerBufferAccess.free(buffer); bufferCount--; + return true; + } else { return false; } - return true; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static long diff() { - double diffD = SubstrateSigprofHandler.getSubstrateThreadMXBean().getThreadCount() * 1.5 - bufferCount; + double diffD = SubstrateSigprofHandler.singleton().substrateThreadMXBean().getThreadCount() * 1.5 - bufferCount; return (long) (diffD + 0.5); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java index db7a82e44577..229e942b6892 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferStack.java @@ -50,7 +50,7 @@ class SamplerBufferStack { } /** - * Push a profiler buffer into the global linked list. + * Push the buffer into the linked-list. */ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public void pushBuffer(SamplerBuffer buffer) { @@ -64,7 +64,7 @@ public void pushBuffer(SamplerBuffer buffer) { } /** - * Pop a profiler buffer from the global linked list. Returns {@code null} if the list is empty. + * Pop the buffer from the linked-list. Returns {@code null} if the list is empty. */ @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public SamplerBuffer popBuffer() { @@ -81,9 +81,6 @@ public SamplerBuffer popBuffer() { } } - /** - * Returns the lock that this stack is using. - */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean isLockedByCurrentThread() { return spinLock.isOwner(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java index 28b00171363f..bd25d3101b71 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerIsolateLocal.java @@ -58,12 +58,12 @@ public void afterCreateIsolate(Isolate isolate) { @Override @Uninterruptible(reason = "The isolate teardown is in progress.") public void onIsolateTeardown() { - if (SubstrateSigprofHandler.isProfilingEnabled() && isKeySet()) { + if (SubstrateSigprofHandler.singleton().isProfilingEnabled() && isKeySet()) { /* Invalidate the isolate-specific key. */ UnsignedWord oldKey = key; key = WordFactory.unsigned(0); - /* We need to manually call teardown because isKeySet will now return false. */ + /* Manually disable sampling for the current thread (no other threads are remaining). */ SamplerThreadLocal.teardown(CurrentIsolate.getCurrentThread()); /* Now, it's safe to delete the isolate-specific key. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java index 89802833d164..7290fea6c6c6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriter.java @@ -32,11 +32,14 @@ import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.util.VMError; -class SamplerSampleWriter { +final class SamplerSampleWriter { private static final int END_MARKER_SIZE = Long.BYTES; private static final long END_MARKER = -1; + private SamplerSampleWriter() { + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean putLong(SamplerSampleWriterData data, long value) { if (ensureSize(data, Long.BYTES)) { @@ -86,7 +89,7 @@ private static boolean accommodate(SamplerSampleWriterData data, UnsignedWord un } /* Pop first free buffer from the pool. */ - SamplerBuffer newBuffer = SubstrateSigprofHandler.availableBuffers().popBuffer(); + SamplerBuffer newBuffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer(); if (newBuffer.isNull()) { /* No available buffers on the pool. Fallback! */ SamplerThreadLocal.increaseMissedSamples(); @@ -99,7 +102,7 @@ private static boolean accommodate(SamplerSampleWriterData data, UnsignedWord un /* Put in the stack with other unprocessed buffers. */ SamplerBuffer oldBuffer = data.getSamplerBuffer(); - SubstrateSigprofHandler.fullBuffers().pushBuffer(oldBuffer); + SubstrateSigprofHandler.singleton().fullBuffers().pushBuffer(oldBuffer); /* Reinitialize data structure. */ data.setSamplerBuffer(newBuffer); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java index 97ed3f3165e5..85dc89e26577 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSpinLock.java @@ -25,7 +25,6 @@ package com.oracle.svm.core.sampler; -import com.oracle.svm.core.util.VMError; import org.graalvm.compiler.nodes.PauseNode; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; @@ -35,13 +34,15 @@ import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.util.VMError; /** * The custom implementation of spin lock that is async signal safe. * * In some specific situations, the signal handler can interrupt execution while the same thread - * already has the lock. Other spin lock implementations can deadlock in such a case. So it is - * essential to check if the current thread is the owner of the lock, before acquiring it. + * already has the lock. This implementation will check and fatally fail while other spin locks + * implementations can deadlock in this case. So it is essential to check if the current thread is + * the owner of the lock, before acquiring it. */ class SamplerSpinLock { private final UninterruptibleUtils.AtomicPointer owner; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java index 66c83f47ab54..6cd132cda1fe 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java @@ -50,7 +50,7 @@ class SamplerThreadLocal implements ThreadListener { @Override @Uninterruptible(reason = "Only uninterruptible code may be executed before Thread.run.") public void beforeThreadRun(IsolateThread isolateThread, Thread javaThread) { - if (SubstrateSigprofHandler.isProfilingEnabled()) { + if (SubstrateSigprofHandler.singleton().isProfilingEnabled()) { initialize(isolateThread); } } @@ -75,7 +75,7 @@ static void initialize(IsolateThread isolateThread) { @Override @Uninterruptible(reason = "Only uninterruptible code may be executed after Thread.exit.") public void afterThreadExit(IsolateThread isolateThread, Thread javaThread) { - if (SubstrateSigprofHandler.isProfilingEnabled() && SamplerIsolateLocal.isKeySet()) { + if (SubstrateSigprofHandler.singleton().isProfilingEnabled() && SamplerIsolateLocal.isKeySet()) { teardown(isolateThread); } } @@ -94,7 +94,7 @@ static void teardown(IsolateThread isolateThread) { /* Adjust the number of buffers (including the thread-local buffer). */ SamplerBuffer threadLocalBuffer = localBuffer.get(isolateThread); - SamplerBufferPool.adjustBufferCount(threadLocalBuffer); + SamplerBufferPool.releaseBufferAndAdjustCount(threadLocalBuffer); localBuffer.set(isolateThread, WordFactory.nullPointer()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java index 09daa64dfd85..08a0d4b72b36 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java @@ -185,23 +185,23 @@ static boolean isProfilingSupported() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - static boolean isProfilingEnabled() { - return singleton().enabled; + boolean isProfilingEnabled() { + return enabled; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - static SamplerBufferStack availableBuffers() { - return singleton().availableBuffers; + SamplerBufferStack availableBuffers() { + return availableBuffers; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - static SamplerBufferStack fullBuffers() { - return singleton().fullBuffers; + SamplerBufferStack fullBuffers() { + return fullBuffers; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - static SubstrateThreadMXBean getSubstrateThreadMXBean() { - return singleton().threadMXBean; + SubstrateThreadMXBean substrateThreadMXBean() { + return threadMXBean; } /** @@ -287,7 +287,7 @@ private static void doUninterruptibleStackWalk(RegisterDumper.Context uContext, @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean prepareStackWalk(SamplerSampleWriterData data) { - if (availableBuffers().isLockedByCurrentThread() || fullBuffers().isLockedByCurrentThread()) { + if (singleton().availableBuffers().isLockedByCurrentThread() || singleton().fullBuffers().isLockedByCurrentThread()) { /* * The current thread already holds the stack lock, so we can't access it. It's way * better to lose one sample, then potentially the whole buffer. @@ -299,7 +299,7 @@ private static boolean prepareStackWalk(SamplerSampleWriterData data) { SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer(); if (buffer.isNull()) { /* Pop first free buffer from the pool. */ - buffer = availableBuffers().popBuffer(); + buffer = singleton().availableBuffers().popBuffer(); if (buffer.isNull()) { /* No available buffers on the pool. Fallback! */ SamplerThreadLocal.increaseMissedSamples(); @@ -369,13 +369,13 @@ protected void operate() { */ @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void initialize() { - enabled = true; /* * Iterate all over all thread and initialize the thread-local storage of each thread. */ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { SamplerThreadLocal.initialize(thread); } + enabled = true; } } } From c6a7989ade3650b4265c48a6669f7a50c7999d87 Mon Sep 17 00:00:00 2001 From: jovanstevanovic Date: Thu, 12 May 2022 13:23:48 +0200 Subject: [PATCH 3/3] Small fixes. --- .../oracle/svm/core/sampler/SamplerBuffer.java | 2 +- .../svm/core/sampler/SamplerBufferPool.java | 16 ++++++++++------ .../core/sampler/SubstrateSigprofHandler.java | 13 ++++--------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java index d7c1dfa20c2e..5f1f57f15cbc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffer.java @@ -45,7 +45,7 @@ interface SamplerBuffer extends PointerBase { SamplerBuffer getNext(); /** - * Sets the successor to this node in the {@link SamplerBufferStack}. + * Sets the successor to this buffer in the {@link SamplerBufferStack}. */ @RawField void setNext(SamplerBuffer buffer); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java index e50ebc6533b4..96af063d0e6b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java @@ -63,7 +63,9 @@ private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) { long diff = diff(); if (diff > 0) { for (int i = 0; i < diff; i++) { - allocateAndPush(); + if (!allocateAndPush()) { + break; + } } } else { for (long i = diff; i < 0; i++) { @@ -98,14 +100,16 @@ private static void releaseThreadLocalBuffer(SamplerBuffer buffer) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static void allocateAndPush() { + private static boolean allocateAndPush() { VMError.guarantee(bufferCount >= 0); SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(THREAD_BUFFER_SIZE)); - if (buffer.isNull()) { - return; + if (buffer.isNonNull()) { + SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); + bufferCount++; + return true; + } else { + return false; } - SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); - bufferCount++; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java index 08a0d4b72b36..b3751bce98dc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java @@ -233,7 +233,6 @@ void install() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected abstract IsolateThread getThreadLocalKeyValue(UnsignedWord key); - /** Is sigprof handler called from native code? */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean isIPInJavaCode(RegisterDumper.Context uContext) { Pointer ip = (Pointer) RegisterDumper.singleton().getIP(uContext); @@ -243,9 +242,6 @@ private static boolean isIPInJavaCode(RegisterDumper.Context uContext) { return ip.aboveOrEqual(codeStart) && ip.belowOrEqual(codeStart.add(codeSize)); } - /** - * Walk the current stacktrace using isolate thread local area and isolate heap base. - */ @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true) private static void doUninterruptibleStackWalk(RegisterDumper.Context uContext, boolean isIPInJavaCode) { CodePointer ip; @@ -320,11 +316,6 @@ private static boolean prepareStackWalk(SamplerSampleWriterData data) { /** Called from the platform dependent sigprof handler to enter isolate. */ @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true) protected static void tryEnterIsolateAndDoWalk(RegisterDumper.Context uContext) { - if (!SamplerIsolateLocal.isKeySet()) { - /* The key is set for initial isolate only. */ - return; - } - Isolate isolate = SamplerIsolateLocal.getIsolate(); if (isolate.isNull()) { /* It may happen that the initial isolate exited. */ @@ -335,6 +326,10 @@ protected static void tryEnterIsolateAndDoWalk(RegisterDumper.Context uContext) WriteHeapBaseNode.writeCurrentVMHeapBase(isolate); /* We are keeping reference to isolate thread inside OS thread local area. */ + if (!SamplerIsolateLocal.isKeySet()) { + /* The key becomes invalid during initial isolate teardown. */ + return; + } UnsignedWord key = SamplerIsolateLocal.getKey(); IsolateThread thread = singleton().getThreadLocalKeyValue(key); if (thread.isNull()) {