/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.vm.scheduler;
import org.jnode.annotation.Internal;
import org.jnode.annotation.MagicPermission;
import org.jnode.annotation.Uninterruptible;
import org.jnode.util.NumberUtils;
import org.jnode.vm.Unsafe;
import org.jnode.vm.VmMagic;
import org.jnode.vm.classmgr.ObjectFlags;
import org.jnode.vm.classmgr.ObjectLayout;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.ObjectReference;
import org.vmmagic.unboxed.Word;
/**
* @author epr
*/
@MagicPermission
@Uninterruptible
public final class MonitorManager {
/**
* A fast implementation of the monitorEnter opcode. This implementation is
* based on a thin-lock, present if the status word of the header of each
* object.
*
* @param object
*/
public static void monitorEnter(Object object) {
if (object == null) {
throw new NullPointerException();
}
// Prepare
final Word tid = Word.fromIntZeroExtend(VmMagic.currentProcessor().getCurrentThread().getId());
final Address objectPtr = ObjectReference.fromObject(object).toAddress();
final Address statusPtr = objectPtr.add(ObjectLayout.FLAGS_SLOT * Address.size());
for (;;) {
// attempt fast path: object is not locked.
final Word oldlockword = statusPtr.prepareWord();
final Word statusFlags = oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.STATUS_FLAGS_MASK));
if (statusPtr.attempt(statusFlags, statusFlags.or(tid))) {
// fast path succeeded, the object was not locked and
// has been locked by the current thread.
return;
}
// object is locked or has an inflated lock.
if (!oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_EXPANDED)).isZero()) {
// slow path 2: high bit of lock word is set --> inflated lock
final Monitor m = getMonitor(oldlockword);
m.enter();
return;
} else if (oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.THREAD_ID_MASK)).EQ(tid)) {
// Current thread owns the thinlock
final Word counter = oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_COUNT_MASK));
if (counter.EQ(Word.fromIntZeroExtend(ObjectFlags.LOCK_COUNT_MASK))) {
// thin lock entry counter == max, so we need to inflate
// ourselves.
installInflatedLock(object, null).enter();
return;
} else {
// not-quite-so-fast path: locked by current thread.
// increment counter.
// Try to update lock, it may be inflated by some other
// thread, so
// be cautious
final Word newlockword;
newlockword = oldlockword.add(Word.fromIntZeroExtend(ObjectFlags.LOCK_COUNT_INC));
// Try to update lock, it may be inflated by some other
// thread, so
// be cautious
if (statusPtr.attempt(oldlockword, newlockword)) {
return;
}
}
} else {
// Another thread owns the lock
// thin lock owned by another thread.
int ownerId = oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.THREAD_ID_MASK)).toInt();
VmThread thread = VmMagic.currentProcessor().getScheduler().getThreadById(ownerId);
if (thread == null) {
//the owner of the lock was destroyed
//aquire the lock in fast fashion
statusPtr.store(statusFlags.or(tid));
return;
} else {
// install an inflated lock.
installInflatedLock(object, thread).enter();
}
return;
}
Unsafe.debug("@monitorEnter loop@");
}
}
/**
* Monitorexit runtime routine. Checks for thin lock usage, otherwise falls
* back to inflated locks.
*
* @param object
*/
public static void monitorExit(Object object) {
if (object == null) {
throw new NullPointerException();
}
// Prepare
final Word tid = Word.fromIntZeroExtend(VmMagic.currentProcessor().getCurrentThread().getId());
final Address objectPtr = ObjectReference.fromObject(object).toAddress();
final Address statusPtr = objectPtr.add(ObjectLayout.FLAGS_SLOT * Address.size());
for (;;) {
final Word oldlockword = statusPtr.prepareWord();
// Unsafe.debug(" exit:");
// proc.getArchitecture().getStackReader().debugStackTrace();
// Unsafe.debug(oldlockword); Unsafe.debug(tid); Unsafe.debug('}');
// if not inflated and tid matches, this contains status flags and
// counter
if (!oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_EXPANDED)).isZero()) {
// inflated lock
final Monitor m = getMonitor(oldlockword);
m.exit();
return;
} else if (oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.THREAD_ID_MASK)).EQ(tid)) {
// owned by current thread
final Word counter = oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_COUNT_MASK));
final Word newlockword;
if (counter.isZero()) {
// Count is zero, clear tid field
newlockword = oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.STATUS_FLAGS_MASK));
} else {
// count is non-zero, decrement count
newlockword = oldlockword.sub(Word.fromIntSignExtend(ObjectFlags.LOCK_COUNT_INC));
}
if (statusPtr.attempt(oldlockword, newlockword)) {
return;
}
} else {
// lock not owned by us!
String exMsg = "Lock not owned by us:";
Unsafe.debug(exMsg);
Unsafe.debug(object.getClass().getName());
// Extra debug info
Unsafe.debug("\n");
Unsafe.debug(oldlockword);
Unsafe.debug(objectPtr);
Unsafe.debug(statusPtr);
Unsafe.debug(oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_COUNT_MASK)));
Unsafe.debug(tid);
Unsafe.debug(statusPtr.prepareWord());
VmMagic.currentProcessor().getArchitecture().getStackReader().debugStackTrace();
Unsafe.die("Please report this problem with the above values to epr@jnode.org");
throw new IllegalMonitorStateException(exMsg);
}
}
}
/**
* Wait on the given object, until interrupted or notified
*
* @param object
* @param timeout
* @throws InterruptedException
*/
public static void wait(Object object, long timeout) throws InterruptedException {
// Test interrupted state of current thread
if (VmThread.currentThread().isInterrupted(true)) {
throw new InterruptedException();
}
getOwnedInflatedMonitor(object).Wait(timeout);
}
/**
* Notify the first thread waiting on the given object
*
* @param object
*/
public static void notify(Object object) {
getOwnedInflatedMonitor(object).Notify(false);
}
/**
* Notify all threads waiting on the given object
*
* @param object
*/
public static void notifyAll(Object object) {
getOwnedInflatedMonitor(object).Notify(true);
}
/**
* Gets the inflated monitor of the given object. The object must have been
* locked by the current thread, otherwise an IllegalMonitorStateException
* is thrown.
*
* @param object
* @return The monitor
* @throws IllegalMonitorStateException
* @throw IllegalMonitorStateException
*/
private static Monitor getOwnedInflatedMonitor(Object object)
throws IllegalMonitorStateException {
final Monitor m = installInflatedLock(object, null);
if (!m.isOwner(VmMagic.currentProcessor().getCurrentThread())) {
// lock not owned by us!
String exMsg = "Object not locked by current thread";
Unsafe.debug(exMsg);
throw new IllegalMonitorStateException(exMsg);
}
return m;
}
/**
* Make sure that object K has an inflated monitor.
*
* @param k
*/
static void setupInflatedLock(Object k) {
installInflatedLock(k, null);
}
/**
* Installs an inflated lock on the given object. Uses a spin-loop to wait
* until the object is unlocked or inflated.
*
* @param k the object for which the inflated lock is installed
* @param thread
* @return the Monitor object representing the lock
*/
private static Monitor installInflatedLock(Object k, VmThread thread) {
Monitor m = null;
Word monAddr = null;
final Address kPtr = ObjectReference.fromObject(k).toAddress();
final Address statusPtr = kPtr.add(ObjectLayout.FLAGS_SLOT * Address.size());
for (;;) {
final Word oldlockword = statusPtr.prepareWord();
if (!oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_EXPANDED)).isZero()) {
// inflated by another thread, use that one.
return getMonitor(oldlockword);
}
if (m == null) {
m = new Monitor(VmMagic.currentProcessor().getCurrentThread(), 1);
monAddr = ObjectReference.fromObject(m).toAddress().toWord();
if (!monAddr.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_EXPANDED
| ObjectFlags.STATUS_FLAGS_MASK)).isZero()) {
throw new InternalError("Monitor object has address that conflicts with header flags 0x"
+ NumberUtils.hex(monAddr.toInt()));
}
}
// Put entry count & owner in monitor
int lockcount = 1 + oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_COUNT_MASK)).
rshl(ObjectFlags.LOCK_COUNT_SHIFT).toInt();
int ownerId = oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.THREAD_ID_MASK)).toInt();
if (thread == null) {
thread = VmMagic.currentProcessor().getScheduler().getThreadById(ownerId);
}
m.initialize(thread, lockcount);
final Word statusFlags = oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.STATUS_FLAGS_MASK));
final Word newlockword = monAddr.or(statusFlags).or(Word.fromIntZeroExtend(ObjectFlags.LOCK_EXPANDED));
if (statusPtr.attempt(oldlockword, newlockword)) {
// successfully obtained inflated lock.
return m;
}
}
}
/**
* Get the Monitor object associated with this lockword.
*
* @param lockword
* @return The monitor
*/
private static Monitor getMonitor(Word lockword) {
final Address address = lockword.and(Word.fromIntZeroExtend(
~(ObjectFlags.LOCK_EXPANDED | ObjectFlags.STATUS_FLAGS_MASK))).toAddress();
if (address.isZero()) {
String exMsg = "Inflated monitor is null????";
Unsafe.debug(exMsg);
throw new IllegalMonitorStateException(exMsg);
}
return (Monitor) address.toObjectReference().toObject();
}
/**
* Gets the inflated monitor of an object (if any).
*
* @param object
* @return The inflated monitor of the given object, or null if the given
* object has no inflated monitor.
*/
@Internal
public static Monitor getInflatedMonitor(Object object) {
final Word oldlockword = ObjectReference.fromObject(object).toAddress().
add(ObjectLayout.FLAGS_SLOT * Address.size()).loadWord();
if (!oldlockword.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_EXPANDED)).isZero()) {
return getMonitor(oldlockword);
} else {
return null;
}
}
/**
* Checks whether the current thread holds the monitor on a given object.
* This allows you to do <code>assert Thread.holdsLock(obj)</code>.
*
* @param obj the object to test lock ownership on.
* @return true if the current thread is currently synchronized on obj
* @throws NullPointerException if obj is null
* @since 1.4
*/
public static boolean holdsLock(Object obj) {
final VmThread current = VmThread.currentThread();
final Word lockword = ObjectReference.fromObject(obj).toAddress().
add(ObjectLayout.FLAGS_SLOT * Address.size()).prepareWord();
if (!lockword.and(Word.fromIntZeroExtend(ObjectFlags.LOCK_EXPANDED)).isZero()) {
return getMonitor(lockword).isOwner(current);
} else {
final Word tid = Word.fromIntZeroExtend(current.getId());
return lockword.and(Word.fromIntZeroExtend(ObjectFlags.THREAD_ID_MASK)).EQ(tid);
}
}
/**
* Make sure the given thread id does fit into the space reserved for it by
* the thinlock stuff.
*
* @param tid
*/
static void testThreadId(int tid) {
if ((tid & ~ObjectFlags.THREAD_ID_MASK) != 0) {
throw new InternalError("Invalid thread id " + tid);
}
if ((tid & ObjectFlags.THREAD_ID_MASK) == 0) {
throw new InternalError("Invalid thread id " + tid);
}
}
}