/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.scheduler;
import static org.jikesrvm.runtime.SysCall.sysCall;
import org.jikesrvm.VM;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.pragma.Unpreemptible;
import org.vmmagic.pragma.NonMoving;
import org.vmmagic.pragma.NoInline;
import org.vmmagic.pragma.NoOptCompile;
import org.vmmagic.pragma.BaselineSaveLSRegisters;
import org.vmmagic.unboxed.Word;
/**
* Implementation of a heavy lock and condition variable implemented using
* the primitives available from the operating system. Currently we use
* a pthread_mutex_t and pthread_cond_it. When instantiated, the mutex
* and cond are allocated. There is currently no way to destroy either
* (thus, pool and reuse accordingly).
* <p>
* It is perfectly safe to use this throughout the VM for locking. It is
* meant to provide roughly the same functionality as Java monitors,
* except:
* <ul>
* <li>This class provides a faster slow path than Java monitors.</li>
* <li>This class provides a slower fast path than Java monitors.</li>
* <li>This class does not have any interaction with Thread.interrupt()
* or Thread.stop(). Thus, if you block on a lock or a wait and the
* calling thread is stopped or interrupted, nothing will happen
* until the lock is acquired or the wait is notified.</li>
* <li>This class will work in the inner guts of the RVM runtime because
* it gives you the ability to lock and unlock, as well as wait and
* notify, without using any other VM runtime functionality.</li>
* <li>This class allows you to optionally block without letting the thread
* system know that you are blocked. The benefit is that you can
* perform synchronization without depending on RVM thread subsystem functionality.
* However, most of the time, you should use the methods that inform
* the thread system that you are blocking. Methods that have the
* "Nicely" suffix will inform the thread system if you are blocked,
* while methods that do not have the suffix will either not block
* (as is the case with unlock and broadcast) or will block without
* letting anyone know (like lock and wait). Not letting the threading
* system know that you are blocked may cause things like GC to stall
* until you unblock.</li>
* </ul>
*/
@Uninterruptible
@NonMoving
public class Monitor {
Word monitor;
int holderSlot=-1; // use the slot so that we're even more GC safe
int recCount;
public int acquireCount;
/**
* Allocate a heavy condition variable and lock. This involves
* allocating stuff in C that never gets deallocated. Thus, don't
* instantiate too many of these.
*/
public Monitor() {
monitor = sysCall.sysMonitorCreate();
}
/**
* Wait until it is possible to acquire the lock and then acquire it.
* There is no bound on how long you might wait, if someone else is
* holding the lock and there is no bound on how long they will hold it.
* As well, even if there is a bound on how long a thread might hold a
* lock but there are multiple threads contending on its acquisition,
* there will not necessarily be a bound on how long any particular
* thread will wait until it gets its turn.
* <p>
* This blocking method method does not notify the threading subsystem
* that it is blocking. Thus, if someone (like, say, the GC) requests
* that the thread is blocked then their request will block until this
* method unblocks. If this sounds like it might be undesirable, call
* lockNicely instead.
*/
@NoInline
@NoOptCompile
public void lockNoHandshake() {
int mySlot = RVMThread.getCurrentThreadSlot();
if (mySlot != holderSlot) {
sysCall.sysMonitorEnter(monitor);
if (VM.VerifyAssertions) VM._assert(holderSlot==-1);
if (VM.VerifyAssertions) VM._assert(recCount==0);
holderSlot = mySlot;
}
recCount++;
acquireCount++;
}
/**
* Relock the mutex after using unlockCompletely.
*/
@NoInline
@NoOptCompile
public void relockNoHandshake(int recCount) {
sysCall.sysMonitorEnter(monitor);
if (VM.VerifyAssertions) VM._assert(holderSlot==-1);
if (VM.VerifyAssertions) VM._assert(this.recCount==0);
holderSlot=RVMThread.getCurrentThreadSlot();
this.recCount=recCount;
acquireCount++;
}
/**
* Wait until it is possible to acquire the lock and then acquire it.
* There is no bound on how long you might wait, if someone else is
* holding the lock and there is no bound on how long they will hold it.
* As well, even if there is a bound on how long a thread might hold a
* lock but there are multiple threads contending on its acquisition,
* there will not necessarily be a bound on how long any particular
* thread will wait until it gets its turn.
* <p>
* This blocking method method notifies the threading subsystem that it
* is blocking. Thus, it may be safer than calling lock. But,
* its reliance on threading subsystem accounting methods may mean that
* it cannot be used in certain contexts (say, the threading subsystem
* itself).
* <p>
* This method will ensure that if it blocks, it does so with the
* mutex not held. This is useful for cases where the subsystem that
* requested you to block needs to acquire the lock you were trying to
* acquire when the blocking request came.
* <p>
* It is usually not necessary to call this method instead of lock(),
* since most VM locks are held for short periods of time.
*/
@Unpreemptible("If the lock cannot be acquired, this method will allow the thread to be asynchronously blocked")
@NoInline
@NoOptCompile
public void lockWithHandshake() {
int mySlot = RVMThread.getCurrentThreadSlot();
if (mySlot != holderSlot) {
lockWithHandshakeNoRec();
if (VM.VerifyAssertions) VM._assert(holderSlot==-1);
if (VM.VerifyAssertions) VM._assert(recCount==0);
holderSlot = mySlot;
}
recCount++;
acquireCount++;
}
@NoInline
@NoOptCompile
@BaselineSaveLSRegisters
@Unpreemptible
private void lockWithHandshakeNoRec() {
RVMThread.saveThreadState();
lockWithHandshakeNoRecImpl();
}
@NoInline
@Unpreemptible
@NoOptCompile
private void lockWithHandshakeNoRecImpl() {
for (;;) {
RVMThread.enterNative();
sysCall.sysMonitorEnter(monitor);
if (RVMThread.attemptLeaveNativeNoBlock()) {
return;
} else {
sysCall.sysMonitorExit(monitor);
RVMThread.leaveNative();
}
}
}
/**
* Relock the mutex after using unlockCompletely, but do so "nicely".
*/
@NoInline
@NoOptCompile
@BaselineSaveLSRegisters
@Unpreemptible("If the lock cannot be reacquired, this method may allow the thread to be asynchronously blocked")
public void relockWithHandshake(int recCount) {
RVMThread.saveThreadState();
relockWithHandshakeImpl(recCount);
}
@NoInline
@Unpreemptible
@NoOptCompile
private void relockWithHandshakeImpl(int recCount) {
for (;;) {
RVMThread.enterNative();
sysCall.sysMonitorEnter(monitor);
if (RVMThread.attemptLeaveNativeNoBlock()) {
break;
} else {
sysCall.sysMonitorExit(monitor);
RVMThread.leaveNative();
}
}
if (VM.VerifyAssertions) VM._assert(holderSlot==-1);
if (VM.VerifyAssertions) VM._assert(this.recCount==0);
holderSlot=RVMThread.getCurrentThreadSlot();
this.recCount=recCount;
}
/**
* Release the lock. This method should (in principle) be non-blocking,
* and, as such, it does not notify the threading subsystem that it is
* blocking.
*/
@NoInline
@NoOptCompile
public void unlock() {
if (--recCount==0) {
holderSlot=-1;
sysCall.sysMonitorExit(monitor);
}
}
/**
* Completely release the lock, ignoring recursion. Returns the
* recursion count.
*/
@NoInline
@NoOptCompile
public int unlockCompletely() {
int result=recCount;
recCount=0;
holderSlot=-1;
sysCall.sysMonitorExit(monitor);
return result;
}
/**
* Wait until someone calls broadcast.
* <p>
* This blocking method method does not notify the threading subsystem
* that it is blocking. Thus, if someone (like, say, the GC) requests
* that the thread is blocked then their request will block until this
* method unblocks. If this sounds like it might be undesirable, call
* waitNicely instead.
*/
@NoInline
@NoOptCompile
public void waitNoHandshake() {
int recCount=this.recCount;
this.recCount=0;
holderSlot=-1;
sysCall.sysMonitorWait(monitor);
if (VM.VerifyAssertions) VM._assert(holderSlot==-1);
if (VM.VerifyAssertions) VM._assert(this.recCount==0);
this.recCount=recCount;
holderSlot=RVMThread.getCurrentThreadSlot();
}
/**
* Wait until someone calls broadcast, or until the clock reaches the
* given time.
* <p>
* This blocking method method does not notify the threading subsystem
* that it is blocking. Thus, if someone (like, say, the GC) requests
* that the thread is blocked then their request will block until this
* method unblocks. If this sounds like it might be undesirable, call
* timedWaitAbsoluteNicely instead.
*/
@NoInline
@NoOptCompile
public void timedWaitAbsoluteNoHandshake(long whenWakeupNanos) {
int recCount=this.recCount;
this.recCount=0;
holderSlot=-1;
sysCall.sysMonitorTimedWaitAbsolute(monitor, whenWakeupNanos);
if (VM.VerifyAssertions) VM._assert(holderSlot==-1);
if (VM.VerifyAssertions) VM._assert(this.recCount==0);
this.recCount=recCount;
holderSlot=RVMThread.getCurrentThreadSlot();
}
/**
* Wait until someone calls broadcast, or until at least the given
* number of nanoseconds pass.
* <p>
* This blocking method method does not notify the threading subsystem
* that it is blocking. Thus, if someone (like, say, the GC) requests
* that the thread is blocked then their request will block until this
* method unblocks. If this sounds like it might be undesirable, call
* timedWaitRelativeNicely instead.
*/
@NoInline
@NoOptCompile
public void timedWaitRelativeNoHandshake(long delayNanos) {
long now=sysCall.sysNanoTime();
timedWaitAbsoluteNoHandshake(now+delayNanos);
}
/**
* Wait until someone calls broadcast.
* <p>
* This blocking method notifies the threading subsystem that it
* is blocking. Thus, it is generally safer than calling wait. But,
* its reliance on threading subsystem accounting methods may mean that
* it cannot be used in certain contexts (say, the threading subsystem
* itself).
* <p>
* This method will ensure that if it blocks, it does so with the
* mutex not held. This is useful for cases where the subsystem that
* requested you to block needs to acquire the lock you were trying to
* acquire when the blocking request came.
*/
@NoInline
@NoOptCompile
@BaselineSaveLSRegisters
@Unpreemptible("While the thread is waiting, this method may allow the thread to be asynchronously blocked")
public void waitWithHandshake() {
RVMThread.saveThreadState();
waitWithHandshakeImpl();
}
@NoInline
@Unpreemptible
@NoOptCompile
private void waitWithHandshakeImpl() {
RVMThread.enterNative();
waitNoHandshake();
int recCount=unlockCompletely();
RVMThread.leaveNative();
relockWithHandshakeImpl(recCount);
}
/**
* Wait until someone calls broadcast, or until the clock reaches the
* given time.
* <p>
* This blocking method method notifies the threading subsystem that it
* is blocking. Thus, it is generally safer than calling
* timedWaitAbsolute. But, its reliance on threading subsystem accounting
* methods may mean that it cannot be used in certain contexts (say, the
* threading subsystem itself).
* <p>
* This method will ensure that if it blocks, it does so with the
* mutex not held. This is useful for cases where the subsystem that
* requested you to block needs to acquire the lock you were trying to
* acquire when the blocking request came.
*/
@NoInline
@NoOptCompile
@BaselineSaveLSRegisters
@Unpreemptible("While the thread is waiting, this method may allow the thread to be asynchronously blocked")
public void timedWaitAbsoluteWithHandshake(long whenWakeupNanos) {
RVMThread.saveThreadState();
timedWaitAbsoluteWithHandshakeImpl(whenWakeupNanos);
}
@NoInline
@Unpreemptible
@NoOptCompile
private void timedWaitAbsoluteWithHandshakeImpl(long whenWakeupNanos) {
RVMThread.enterNative();
timedWaitAbsoluteNoHandshake(whenWakeupNanos);
int recCount=unlockCompletely();
RVMThread.leaveNative();
relockWithHandshakeImpl(recCount);
}
/**
* Wait until someone calls broadcast, or until at least the given
* number of nanoseconds pass.
* <p>
* This blocking method method notifies the threading subsystem that it
* is blocking. Thus, it is generally safer than calling
* timedWaitRelative. But, its reliance on threading subsystem accounting
* methods may mean that it cannot be used in certain contexts (say, the
* threading subsystem itself).
* <p>
* This method will ensure that if it blocks, it does so with the
* mutex not held. This is useful for cases where the subsystem that
* requested you to block needs to acquire the lock you were trying to
* acquire when the blocking request came.
*/
@NoInline
@NoOptCompile
@BaselineSaveLSRegisters
@Unpreemptible("While the thread is waiting, this method may allow the thread to be asynchronously blocked")
public void timedWaitRelativeWithHandshake(long delayNanos) {
RVMThread.saveThreadState();
timedWaitRelativeWithHandshakeImpl(delayNanos);
}
@NoInline
@Unpreemptible
@NoOptCompile
private void timedWaitRelativeWithHandshakeImpl(long delayNanos) {
RVMThread.enterNative();
timedWaitRelativeNoHandshake(delayNanos);
int recCount=unlockCompletely();
RVMThread.leaveNative();
relockWithHandshakeImpl(recCount);
}
/**
* Send a broadcast, which should awaken anyone who is currently blocked
* in any of the wait methods. This method should (in principle) be
* non-blocking, and, as such, it does not notify the threading subsystem
* that it is blocking.
*/
@NoInline
@NoOptCompile
public void broadcast() {
sysCall.sysMonitorBroadcast(monitor);
}
/**
* Send a broadcast after first acquiring the lock. Release the lock
* after sending the broadacst. In most cases where you want to send
* a broadcast but you don't need to acquire the lock to set the
* condition that the other thread(s) are waiting on, you want to call
* this method instead of <code>broadcast</code>.
*/
@NoInline
@NoOptCompile
public void lockedBroadcastNoHandshake() {
lockNoHandshake();
broadcast();
unlock();
}
@NoInline
public static boolean lockNoHandshake(Monitor l) {
if (l==null) {
return false;
} else {
l.lockNoHandshake();
return true;
}
}
@NoInline
public static void unlock(boolean b, Monitor l) {
if (b) l.unlock();
}
@NoInline
@NoOptCompile
@Unpreemptible
public static void lockWithHandshake(Monitor m1,Word priority1,
Monitor m2,Word priority2) {
if (priority1.LE(priority2)) {
m1.lockWithHandshake();
m2.lockWithHandshake();
} else {
m2.lockWithHandshake();
m1.lockWithHandshake();
}
}
}