/*
* 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 org.jikesrvm.VM;
import static org.jikesrvm.runtime.SysCall.sysCall;
import org.jikesrvm.runtime.RuntimeEntrypoints;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.pragma.Interruptible;
import org.vmmagic.pragma.Unpreemptible;
import org.vmmagic.pragma.Untraced;
/**
* A light-weigh condition variable and lock, like Monitor, but this
* one is movable and can be garbage collected. Note that this lock is
* heavier than an object monitor, but has the advantage of being usable
* within GC (this lock never allocates in its methods, and never uses
* read or write barriers, either).
*/
@Uninterruptible
public final class LightMonitor {
ThreadQueue waiting;
ThreadQueue entering;
SpinLock mutex;
//NB, this can only be Untraced as RVMThreads are unmoveable
@Untraced RVMThread holder;
int recCount;
public LightMonitor() {
waiting = new ThreadQueue();
entering = new ThreadQueue();
mutex = new SpinLock();
}
@Unpreemptible
public void lockWithHandshake() {
RVMThread me = RVMThread.getCurrentThread();
if (holder == me) {
recCount++;
} else {
mutex.lock();
while (holder != null) {
entering.enqueue(me);
mutex.unlock();
me.monitor().lockNoHandshake();
while (entering.isQueued(me)) {
me.monitor().waitWithHandshake();
}
me.monitor().unlock();
mutex.lock();
}
holder = me;
mutex.unlock();
recCount = 1;
}
}
public void unlock() {
if (recCount > 1) {
recCount--;
} else {
if (VM.VerifyAssertions) VM._assert(recCount == 1);
if (VM.VerifyAssertions) VM._assert(holder == RVMThread.getCurrentThread());
mutex.lock();
RVMThread toAwaken = entering.dequeue();
holder = null;
recCount = 0;
mutex.unlock();
if (toAwaken != null) {
toAwaken.monitor().lockedBroadcastNoHandshake();
}
}
}
@Interruptible
private void waitImpl(long whenAwake) {
if (VM.VerifyAssertions) VM._assert(recCount >= 1);
if (VM.VerifyAssertions) VM._assert(holder == RVMThread.getCurrentThread());
RVMThread me = RVMThread.getCurrentThread();
boolean throwInterrupt = false;
Throwable throwThis = null;
mutex.lock();
waiting.enqueue(me);
mutex.unlock();
int myRecCount = recCount;
recCount = 1;
unlock();
me.monitor().lockNoHandshake();
while (waiting.isQueued(me) &&
(whenAwake != 0 || sysCall.sysNanoTime() < whenAwake) &&
!me.hasInterrupt && me.asyncThrowable == null) {
if (whenAwake == 0) {
me.monitor().waitWithHandshake();
} else {
me.monitor().timedWaitAbsoluteWithHandshake(whenAwake);
}
}
if (me.hasInterrupt) {
throwInterrupt = true;
me.hasInterrupt = false;
}
if (me.asyncThrowable != null) {
throwThis = me.asyncThrowable;
me.asyncThrowable = null;
}
me.monitor().unlock();
mutex.lock();
waiting.remove(me);
mutex.unlock();
lockWithHandshake();
recCount = myRecCount;
// check if we should exit in a special way
if (throwThis != null) {
RuntimeEntrypoints.athrow(throwThis);
}
if (throwInterrupt) {
RuntimeEntrypoints.athrow(new InterruptedException("sleep interrupted"));
}
}
@Interruptible
public void waitInterruptibly() {
waitImpl(0);
}
@Interruptible
public void timedWaitAbsoluteInterruptibly(long whenAwakeNanos) {
waitImpl(whenAwakeNanos);
}
@Interruptible
public void timedWaitRelativeInterruptibly(long delayNanos) {
waitImpl(sysCall.sysNanoTime() + delayNanos);
}
public void broadcast() {
for (;;) {
mutex.lock();
RVMThread toAwaken = waiting.dequeue();
mutex.unlock();
if (toAwaken == null) break;
toAwaken.monitor().lockedBroadcastNoHandshake();
}
}
@Unpreemptible
public void lockedBroadcastWithHandshake() {
lockWithHandshake();
broadcast();
unlock();
}
}