/* * Copyright (c) 2012, 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. * * 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.sun.max.vm.ext.jvmti; import static com.sun.max.vm.ext.jvmti.JVMTIConstants.*; import static com.sun.max.vm.ext.jvmti.JVMTIVmThreadLocal.*; import static com.sun.max.vm.intrinsics.MaxineIntrinsicIDs.*; import com.sun.max.annotate.*; import com.sun.max.memory.*; import com.sun.max.unsafe.*; import com.sun.max.vm.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.compiler.*; import com.sun.max.vm.reference.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.thread.*; /** * Implementation of the JVMTI Raw Monitor functionality. * This is a hybrid implementation that does some synchronization * explicitly and some using native mutex/condition variables. * * The primary reason for not simply using native mutex/condition variables directly * is to finesse a deadlock that was observed in JDWP relating to the way * Maxine threads are suspended. For example: * * <ul> * <li>Thread 1 calls RawMonitorEnter(M) * <li>Thread 1 calls RawMonitorWait(M) * <li>Thread 2 calls SuspendThreadList({M,...}) * <li>Thread 2 calls RawMonitorEnter(M) * <li>Thread 2 calls RawMonitorNotify(M) * <li>Thread 2 calls RawMonitorExit(M) * <li>Thread 1 wakes up in native thread library, re-acquires M then suspends on return * <li>Thread 2 or other thread calls RawMonitorEnter(M) and blocks => deadlock if that thread * was the one about to do the ResumeThreadList({M,...}); * </ul> * Evidently the suspend really needs to happen "inside" the native threads library, but this * is not possible to achieve in general. Maxine suspends threads that are in native code * if and only if they return from the native method while the suspend is in effect, which * means that actions (like re-acquiring a mutex) in the native method cannot be prevented. * * This implementation finesses this by using spinlocks and denoting monitor acquisition by an "owner" field. * Native mutex/condition variables are only used internally for blocking/notification purposes. * Therefore a RawMonitorNotify to a suspended thread in native will only cause re-acquisition of the internal mutex * and not the actual RawMonitor. This internal re-acquisition is harmless. It can only effect another thread * trying to enter the monitor; that thread will block on the internal mutex before waiting on the ENTER condition. * * Note on use nativeConditionNotify. This is always called with {@code notifyAll==true} because although we pick * which thread is notified, we would have no actual control on which thread the OS layer would actually choose to release * if we just notified one thread. So we have to release all of them, even though only one will end up matching the * "while" condition. Since there are only few agent threads, this is not a big performance deal. */ public class JVMTIRawMonitor { private static final long RM_MAGIC = ('T' << 24) + ('I' << 16) + ('R' << 8) + 'M'; public static class Monitor { long magic = RM_MAGIC; @INSPECTED Pointer name; volatile int spinLock; @INSPECTED volatile VmThread owner; int rcount; VmThread[] entryWaiters = new VmThread[8]; VmThread[] waitWaiters = new VmThread[8]; Word enterMutex; Word enterCondition; Word waitMutex; Word waitCondition; } private static int spinLockOffset = ClassActor.fromJava(Monitor.class).findLocalInstanceFieldActor("spinLock").offset(); private static Monitor[] monitors = new Monitor[32]; static { for (int i = 0; i < monitors.length; i++) { monitors[i] = new Monitor(); } } static { new CriticalMethod(JVMTIRawMonitor.class, "create", null, CallEntryPoint.OPTIMIZED_ENTRY_POINT); new CriticalMethod(JVMTIRawMonitor.class, "destroy", null, CallEntryPoint.OPTIMIZED_ENTRY_POINT); new CriticalMethod(JVMTIRawMonitor.class, "enter", null, CallEntryPoint.OPTIMIZED_ENTRY_POINT); new CriticalMethod(JVMTIRawMonitor.class, "exit", null, CallEntryPoint.OPTIMIZED_ENTRY_POINT); new CriticalMethod(JVMTIRawMonitor.class, "wait", null, CallEntryPoint.OPTIMIZED_ENTRY_POINT); new CriticalMethod(JVMTIRawMonitor.class, "notify", null, CallEntryPoint.OPTIMIZED_ENTRY_POINT); new CriticalMethod(JVMTIRawMonitor.class, "notifyAll", null, CallEntryPoint.OPTIMIZED_ENTRY_POINT); } private static Monitor getFreeMonitor() { // TODO sync for (int i = 0; i < monitors.length; i++) { if (monitors[i].name.isZero()) { return monitors[i]; } } return null; } static int create(Pointer namePtr, Pointer rawMonitorPtr) { Monitor m = getFreeMonitor(); if (m == null) { return JVMTI_ERROR_OUT_OF_MEMORY; } Pointer namePtrCopy = CString.copy(namePtr); if (namePtrCopy.isZero()) { return JVMTI_ERROR_OUT_OF_MEMORY; } m.name = namePtrCopy; int ms = OSMonitor.nativeMutexSize(); int cs = OSMonitor.nativeConditionSize(); Pointer base = Memory.allocate(Size.fromInt(2 * (ms + cs))); Pointer enterMutex = base; Pointer enterCondition = enterMutex.plus(ms); Pointer waitMutex = enterCondition.plus(cs); Pointer waitCondition = waitMutex.plus(ms); OSMonitor.nativeMutexInitialize(enterMutex); OSMonitor.nativeConditionInitialize(enterCondition); OSMonitor.nativeMutexInitialize(waitMutex); OSMonitor.nativeConditionInitialize(waitCondition); m.enterMutex = enterMutex; m.enterCondition = enterCondition; m.waitMutex = waitMutex; m.waitCondition = waitCondition; rawMonitorPtr.setWord(Reference.fromJava(m).toOrigin()); return JVMTI_ERROR_NONE; } @INTRINSIC(UNSAFE_CAST) private static native Monitor asMonitor(Object o); private static Monitor validate(Word rawMonitor) { Monitor m = asMonitor(Reference.fromOrigin(rawMonitor.asPointer()).toJava()); if (m.magic == RM_MAGIC) { return m; } else { return null; } } static int destroy(Word rawMonitor) { // TODO check ownership Monitor m = validate(rawMonitor); if (m == null) { return JVMTI_ERROR_INVALID_MONITOR; } Memory.deallocate(m.name); m.name = Pointer.zero(); return JVMTI_ERROR_NONE; } static int enter(Word rawMonitor) { return enter(rawMonitor, true); } static int enter(Word rawMonitor, boolean unlock) { Monitor m = validate(rawMonitor); if (m == null) { return JVMTI_ERROR_INVALID_MONITOR; } VmThread self = VmThread.current(); spinLock(m); if (m.owner == null) { // not owned before, now we own it m.owner = self; } else if (m.owner == self) { // we already owned it, inc recursion count m.rcount++; } else { // someone else owns it, block on enter condition addWaiter(m.entryWaiters, self); spinUnlock(m); // having released the spin lock (which is required since we are expecting to block) // must use OS mutex protection OSMonitor.nativeMutexLock(m.enterMutex); while (m.owner != self) { OSMonitor.nativeConditionWait(m.enterMutex, m.enterCondition, 0); } OSMonitor.nativeMutexUnlock(m.enterMutex); // now we own it unlock = false; } if (unlock) { spinUnlock(m); } return JVMTI_ERROR_NONE; } static int exit(Word rawMonitor) { Monitor m = validate(rawMonitor); if (m == null) { return JVMTI_ERROR_INVALID_MONITOR; } int result = JVMTI_ERROR_NONE; spinLock(m); if (notOwner(m)) { result = JVMTI_ERROR_NOT_MONITOR_OWNER; } else if (m.rcount > 0) { m.rcount--; } else { // final release // anyone waiting? releaseEnterWaiter(m); } spinUnlock(m); return result; } private static void releaseEnterWaiter(Monitor m) { VmThread newOwner = null; OSMonitor.nativeMutexLock(m.enterMutex); for (int i = 0; i < m.entryWaiters.length; i++) { VmThread r = m.entryWaiters[i]; if (r != null) { for (int j = i; j < m.entryWaiters.length - 1; j++) { m.entryWaiters[j] = m.entryWaiters[j + 1]; } m.entryWaiters[m.entryWaiters.length - 1] = null; newOwner = r; break; } } m.owner = newOwner; OSMonitor.nativeConditionNotify(m.enterCondition, true); OSMonitor.nativeMutexUnlock(m.enterMutex); } private static void addWaiter(VmThread[] waiters, VmThread vmThread) { for (int i = 0; i < waiters.length; i++) { if (waiters[i] == null) { waiters[i] = vmThread; return; } } Log.println("JVMTIRawMonitor too many waiting threads"); MaxineVM.native_exit(1); } static int wait(Word rawMonitor, long millis) { Monitor m = validate(rawMonitor); if (m == null) { return JVMTI_ERROR_INVALID_MONITOR; } int result = JVMTI_ERROR_NONE; spinLock(m); if (notOwner(m)) { result = JVMTI_ERROR_NOT_MONITOR_OWNER; } else { if (millis == -1) { // JDWP seems to use -1 when it means 0 millis = 0; } addWaiter(m.waitWaiters, VmThread.current()); // save recursion count int rcount = m.rcount; releaseEnterWaiter(m); spinUnlock(m); // wait for notify OSMonitor.nativeMutexLock(m.waitMutex); Pointer tla = VmThread.currentTLA(); while (!JVMTIVmThreadLocal.bitIsSet(tla, JVMTI_RAW_NOTIFY)) { OSMonitor.nativeConditionWait(m.waitMutex, m.waitCondition, millis); } JVMTIVmThreadLocal.unsetBit(tla, JVMTI_RAW_NOTIFY); OSMonitor.nativeMutexUnlock(m.waitMutex); // re-acquire monitor but hold spinlock enter(rawMonitor, false); // reset our recursion count m.rcount = rcount; } spinUnlock(m); return result; } static int notify(Word rawMonitor) { return notify(rawMonitor, false); } static int notifyAll(Word rawMonitor) { return notify(rawMonitor, true); } static int notify(Word rawMonitor, boolean all) { Monitor m = validate(rawMonitor); if (m == null) { return JVMTI_ERROR_INVALID_MONITOR; } int result = JVMTI_ERROR_NONE; spinLock(m); if (notOwner(m)) { return JVMTI_ERROR_NOT_MONITOR_OWNER; } else { boolean listChanged = false; OSMonitor.nativeMutexLock(m.waitMutex); for (int i = 0; i < m.waitWaiters.length; i++) { VmThread r = m.waitWaiters[i]; if (r != null) { listChanged = true; // mark as notified JVMTIVmThreadLocal.setBit(r.tla(), JVMTI_RAW_NOTIFY); m.waitWaiters[i] = null; if (!all) { break; } } } OSMonitor.nativeConditionNotify(m.waitCondition, true); OSMonitor.nativeMutexUnlock(m.waitMutex); if (listChanged) { // collapse list for (int i = 0; i < m.waitWaiters.length - 1; i++) { if (m.waitWaiters[i] == null) { m.waitWaiters[i] = m.waitWaiters[i + 1]; } } m.waitWaiters[m.waitWaiters.length - 1] = null; } } spinUnlock(m); return result; } /** * Check that this thread owns the monitor. * @param rawMonitor */ private static boolean notOwner(Monitor m) { return m.owner != VmThread.current(); } @INLINE private static void spinLock(Monitor m) { while (Reference.fromJava(m).compareAndSwapInt(spinLockOffset, 0, 1) != 0) { Intrinsics.pause(); } } @INLINE private static void spinUnlock(Monitor m) { m.spinLock = 0; } }