/* * $Id$ * * Copyright (C) 2003-2015 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.vm.Unsafe; import org.jnode.vm.VmMagic; import org.jnode.vm.VmSystem; import org.jnode.annotation.Inline; import org.jnode.annotation.KernelSpace; import org.jnode.annotation.MagicPermission; import org.jnode.annotation.NoFieldAlignments; import org.jnode.annotation.NoInline; import org.jnode.annotation.Uninterruptible; import org.vmmagic.unboxed.Address; import org.vmmagic.unboxed.ObjectReference; /** * @author epr */ @NoFieldAlignments @Uninterruptible @MagicPermission public final class Monitor { /** * Number of locks on this monitor THIS FIELD MUST BE THE FIRST!! */ private int lockCount; /** * Lock counter of the monitor itself. THIS FIELD MUST BE THE SECOND!! */ private int monitorLock; /** * Thread that owns the monitor */ private VmThread owner; /** * Thread queue for monitorenter/exit */ private final VmThreadQueue.ScheduleQueue enterQueue; /** * Thread queue for wait/notify/notifyAll */ private final VmThreadQueue.ScheduleQueue notifyQueue; /** * The previous monitor in a thread bound monitor chain */ private Monitor previous; /** * Create a new instance */ public Monitor() { this.lockCount = 0; this.monitorLock = 0; this.owner = null; this.enterQueue = new VmThreadQueue.ScheduleQueue("mon-enter"); this.notifyQueue = new VmThreadQueue.ScheduleQueue("mon-notify"); } /** * Create a new instance that has already been locked. * * @param owner * @param lockCount */ Monitor(VmThread owner, int lockCount) { this.monitorLock = 0; this.owner = owner; if (owner != null) addToOwner(); this.lockCount = lockCount; if (lockCount < 1) { throw new IllegalArgumentException("LockCount must be >= 1"); } this.enterQueue = new VmThreadQueue.ScheduleQueue("mon-enter"); this.notifyQueue = new VmThreadQueue.ScheduleQueue("mon-notify"); } /** * Initialize this monitor. Only called from MonitorManager. * * @param owner * @param lockcount */ final void initialize(VmThread owner, int lockcount) { dropFromOwner(); this.owner = owner; if (owner != null) addToOwner(); this.lockCount = lockcount; } /** * Enter the given monitor. This method will block until the monitor is * locked by the current thread. * * @throws org.vmmagic.pragma.UninterruptiblePragma * */ @Inline public final void enter() { // Am I already owner of this lock? if (this.owner == VmProcessor.current().getCurrentThread()) { // Yes, already owner, just increment lock counter lockCount++; } else { // No yet owner, try to obtain the lock enterSlowPath(); } } /** * Slow path of enter (current thread is not the owner). This is a seperate * method to control the inlining of the native code compiler. */ @NoInline private final void enterSlowPath() { // No yet owner, try to obtain the lock boolean loop = true; final Address lcAddr = getLCAddress(); while (loop) { // Get current thread final VmThread current = VmMagic.currentProcessor().getCurrentThread(); // Try to claim this monitor if (lcAddr.attempt(0, 1)) { loop = false; dropFromOwner(); this.owner = current; addToOwner(); } else { // Claim the lock for this monitor lock(); try { VmMagic.currentProcessor().disableReschedule(true); prepareWait(current, enterQueue, VmThread.WAITING_ENTER, "mon-enter"); } finally { unlock(); } // Release the monitor lock VmMagic.currentProcessor().suspend(true); // When we return here, another thread has given up // this monitor. } } } /** * Giveup this monitor. * * @throws org.vmmagic.pragma.UninterruptiblePragma * */ public final void exit() { String exMsg = null; if (owner != VmProcessor.current().currentThread) { exMsg = "Current thread is not the owner of this monitor"; } else if (lockCount <= 0) { lockCount = 0; exMsg = "Monitor is not locked"; } else if (lockCount > 1) { // Monitor is locked by current thread, decrement lockcount lockCount--; } else { // Monitor is locked by current thread and will decrement to 0. lock(); try { wakeupWaitingThreads(enterQueue, true); dropFromOwner(); owner = null; lockCount = 0; } finally { unlock(); } } if (exMsg != null) { Unsafe.debug(exMsg); throw new IllegalMonitorStateException(exMsg); } } /** * Giveup this monitor. * Called from VmThread on thread stop. * * @throws org.vmmagic.pragma.UninterruptiblePragma * */ public final void release(VmThread thread) { if (owner != thread) { //todo enable this debug message. How can this happen??? //Unsafe.debug("Current thread is not the owner of this monitor\n"); return; } if (lockCount <= 0) { //todo enable this debug message. How can this happen??? //Unsafe.debug("Monitor is not locked\n"); return; } // Monitor is locked by current thread, decrement lockcount lockCount = 0; lock(); try { wakeupWaitingThreads(enterQueue, true); dropFromOwner(); owner = null; lockCount = 0; } finally { unlock(); } } /** * Causes current thread to wait until another thread invokes the notify() * method or the notifyAll() method for this monitor. In other words, this * method behaves exactly as if it simply performs the call wait(0). The * current thread must own this monitor. The thread releases ownership of * this monitor and waits until another thread notifies threads waiting on * this object's monitor to wake up either through a call to the notify * method or the notifyAll method. The thread then waits until it can * re-obtain ownership of the monitor and resumes execution. This method * should only be called by a thread that is the owner of this monitor. See * the notify method for a description of the ways in which a thread can * become the owner of a monitor. * * @param timeout * @throws org.vmmagic.pragma.UninterruptiblePragma * * @throws InterruptedException */ public final void Wait(long timeout) throws InterruptedException { final VmThread current = VmMagic.currentProcessor().getCurrentThread(); // final int id = current.getId(); String exMsg = null; if (owner != current) { exMsg = "Current thread is not the owner of this monitor"; } else if (lockCount == 0) { exMsg = "Monitor is not locked"; } else { final int oldLockCount = lockCount; final int waitState = (timeout > 0) ? VmThread.WAITING_NOTIFY_TIMEOUT : VmThread.WAITING_NOTIFY; lock(); try { prepareWait(current, notifyQueue, waitState, "mon-notify"); VmMagic.currentProcessor().disableReschedule(true); } finally { unlock(); } // If there is a timeout, also add the current thread to the // sleep queue. if (timeout > 0) { current.wakeupTime = VmSystem.currentKernelMillis() + timeout; VmMagic.currentProcessor().getScheduler().addToSleepQueue(current); } dropFromOwner(); owner = null; lockCount = 0; wakeupWaitingThreads(enterQueue, true); VmMagic.currentProcessor().suspend(true); // When we return here, we have been notified or there // was a timeout. if (!current.isRunning()) { Unsafe.debug("Back from wait, but state != running"); Unsafe.debug("state="); Unsafe.debug(current.getThreadState()); Unsafe.die("Wait"); } if (timeout > 0) { // Screen.debug("<backfromwait-"); Screen.debug(id); // Screen.debug("/>"); // Remove the current thread from the notifyQueue. // There is no need to remove myself from the sleep queue, // because this is done either by the scheduler or // indirect by wakeupWaitingThreads. lock(); try { notifyQueue.remove(current); } finally { unlock(); } } enter(); this.lockCount = oldLockCount; } if (exMsg != null) { Unsafe.debug(exMsg); throw new IllegalMonitorStateException(exMsg); } // Check for InterruptedException current.testAndClearInterruptStatus(); } /** * Notify threads waiting on this monitor. * * @throws org.vmmagic.pragma.UninterruptiblePragma * */ public final void NotifyAll() { Notify(true); } /** * Notify the first or all waiting threads on this monitor. * * @param all * @throws org.vmmagic.pragma.UninterruptiblePragma * */ final void Notify(boolean all) { final VmProcessor proc = VmProcessor.current(); final VmThread current = proc.getCurrentThread(); String exMsg = null; if (owner != current) { exMsg = "Current thread is not the owner of this monitor"; } else if (lockCount == 0) { exMsg = "Monitor is not locked"; } else { lock(); try { wakeupWaitingThreads(notifyQueue, all); } finally { unlock(); } } if (exMsg != null) { Unsafe.debug(exMsg); throw new IllegalMonitorStateException(exMsg); } } /** * Notify the all waiting threads on this monitor. This method does not * require the current thread to be the owner of the monitor, nor is an * exception thrown if the monitor is not locked. * * @throws org.vmmagic.pragma.UninterruptiblePragma * */ final boolean unsynchronizedNotifyAll() { if (lockNoWait()) { try { wakeupWaitingThreads(notifyQueue, true); } finally { unlock(); } return true; } else { return false; } } /** * Is the given thread owner of this monitor? * * @param thread * @return boolean * @throws org.vmmagic.pragma.UninterruptiblePragma * */ @Inline final boolean isOwner(VmThread thread) { return (owner == thread); } /** * Gets the owner of this monitor, or null if not owned. * * @return The owner of this monitor, or null if not owned. */ @KernelSpace @Uninterruptible @Inline final VmThread getOwner() { return owner; } /** * Is this monitor locked? * * @return boolean * @throws org.vmmagic.pragma.UninterruptiblePragma * */ @Inline final boolean isLocked() { return (lockCount > 0); } /** * Prepare the given thread for a waiting state. * * @param thread * @param queue * @param queueName * @return The queue * @throws org.vmmagic.pragma.UninterruptiblePragma * */ private final void prepareWait(VmThread thread, VmThreadQueue.ScheduleQueue queue, int waitState, String queueName) { if (monitorLock != 1) { Unsafe.debug("MonitorLock not claimed"); Unsafe.die("prepareWait"); } thread.prepareWait(this, waitState); queue.add(thread, false, "mon.prepareWait"); } /** * Notify a single thread. The thread is remove from the notifyQueue and its * <code>wakeupAfterMonitor</code> method is called. * * @param thread * @throws org.vmmagic.pragma.UninterruptiblePragma * */ @NoInline private final void notifyThread(VmThread thread) { final VmThreadQueue.ScheduleQueue eq = this.enterQueue; if (eq != null) { eq.remove(thread); } final VmThreadQueue.ScheduleQueue nq = this.notifyQueue; if (nq != null) { nq.remove(thread); } thread.wakeupAfterMonitor(this); } /** * The given thread is removed from the notifyQueue. * * @param thread */ @KernelSpace @Uninterruptible final void removeThreadFromQueues(VmThread thread) { final VmThreadQueue.ScheduleQueue eq = this.enterQueue; if (eq != null) { eq.remove(thread); } final VmThreadQueue.ScheduleQueue nq = this.notifyQueue; if (nq != null) { nq.remove(thread); } } /** * Wakeup all waiting threads. * * @param queue * @param all * @throws org.vmmagic.pragma.UninterruptiblePragma * */ @Inline private final void wakeupWaitingThreads(VmThreadQueue.ScheduleQueue queue, boolean all) { if (queue != null) { while (!queue.isEmpty()) { final VmThread thread = queue.first(); notifyThread(thread); if (!all) { break; } } } } /** * Gets the address of the lockCount variable. It is assumed that this * variable is at offset 0 within this object! * * @return The address of lockCount */ @Inline private final Address getLCAddress() { return ObjectReference.fromObject(this).toAddress(); } /** * Claim access to this monitor. A monitor may only be locked for a small * amount of time, since this method uses a spinlock. * * @see #unlock() * @see #monitorLock */ @Inline private final void lock() { //final VmProcessor proc = VmProcessor.current(); final Address mlAddr = ObjectReference.fromObject(this).toAddress().add(4); while (!mlAddr.attempt(0, 1)) { //proc.yield(true); // Yield breaks the Uninterruptible idea, so don't use it! } } /** * Claim access to this monitor. Return true on success, false on failure * * @see #unlock() * @see #monitorLock */ @Inline private final boolean lockNoWait() { final Address mlAddr = ObjectReference.fromObject(this).toAddress().add(4); return mlAddr.attempt(0, 1); } /** * Release access to this monitor. A monitor may only be locked for a small * amount of time, since this method uses a spinlock. * * @see #lock() * @see #monitorLock */ @Inline private final void unlock() { monitorLock = 0; } //monitor chaining to handle thread stop /** * Returns the monitor previously owned by the owner thread of this monitor. * * @return the previous monitor */ Monitor getPrevious() { return previous; } @Inline private void addToOwner() { Monitor lom = owner.getLastOwnedMonitor(); if (lom == null) { //the first monitor owner.setLastOwnedMonitor(this); } else { if (lom.owner != this.owner) { //todo error return; } else { if (lom == this) { //no need to add it return; } else { //add it this.previous = lom; owner.setLastOwnedMonitor(this); } } } } @Inline private void dropFromOwner() { if (owner == null) { //error return; } Monitor lom = owner.getLastOwnedMonitor(); if (lom == null) return; if (lom != this) return; owner.setLastOwnedMonitor(lom.previous); lom.previous = null; } }