SquirrelJME/SquirrelJME

View on GitHub
emulators/springcoat-vm/src/main/java/cc/squirreljme/vm/springcoat/SpringMonitor.java

Summary

Maintainability
A
1 hr
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package cc.squirreljme.vm.springcoat;

import cc.squirreljme.emulator.profiler.ProfiledFrame;
import cc.squirreljme.jvm.mle.constants.MonitorResultType;
import cc.squirreljme.jvm.mle.constants.ThreadStatusType;
import cc.squirreljme.vm.springcoat.exceptions.SpringIllegalMonitorStateException;

/**
 * This is a monitor which is associated with an object.
 *
 * @since 2018/09/15
 */
public final class SpringMonitor
{
    /** The thread which owns this monitor. */
    volatile SpringThread _owner;
    
    /** Number of threads which are waiting on this monitor. */
    volatile int _waitcount;
    
    /** The number of notifications happening. */
    volatile int _notifycount;
    
    /** The entry count on the monitor. */
    private int _count;
    
    /**
     * Enters the monitor.
     *
     * @param __t The thread trying to lock the monitor.
     * @throws NullPointerException On null arguments.
     * @since 2018/09/15
     */
    public final void enter(SpringThread __t)
        throws NullPointerException
    {
        if (__t == null)
            throw new NullPointerException("NARG");
        
        // Lock on the monitor lock
        for (;;)
            synchronized (this)
            {
                // We take possession of this monitor
                SpringThread owner = this._owner;
                if (owner == null)
                {
                    this._owner = __t;
                    this._count = 1;
                    return;
                }
                
                // We own the monitor, so increase the count
                else if (owner == __t)
                {
                    this._count++;
                    return;
                }
                
                // Need to wait for it to be cleared
                else
                {
                    // Wait for lock to be freed
                    try
                    {
                        SpringMonitor.__changeState(__t, true);
                        
                        this.wait();
                    }
                    catch (InterruptedException e)
                    {
                        // Ignore
                    }
                    finally
                    {
                        SpringMonitor.__changeState(__t, false);
                    }
                }
            }
    }
    
    /**
     * Exits the monitor.
     *
     * @param __t The thread exiting the monitor.
     * @param __notify Should threads be notified that an unlock happened?
     * @throws NullPointerException On null arguments.
     * @throws SpringIllegalMonitorStateException If the monitor is not owned
     * by this thread.
     * @since 2018/09/15
     */
    public final void exit(SpringThread __t, boolean __notify)
        throws NullPointerException, SpringIllegalMonitorStateException
    {
        if (__t == null)
            throw new NullPointerException("NARG");
        
        // Lock on the monitor lock
        synchronized (this)
        {
            /* {@squirreljme.error BK1c This thread does not own the
            monitor.} */
            if (this._owner != __t)
                throw new SpringIllegalMonitorStateException("BK1c");    
            
            /* {@squirreljme.error BK1d No previous entry call was made.
            (The monitor entry count)} */
            int count = this._count;
            if (count <= 0)
                throw new SpringIllegalMonitorStateException(
                    String.format("BK1d %d", count));
            
            // If the count reaches zero, no thread owns this now
            this._count = --count;
            if (count <= 0)
            {
                this._owner = null;
                
                // Wake up all threads so that they try and lock on the lock
                // so whoever gets that chance
                if (__notify)
                    this.notifyAll();
            }
        }
    }
    
    /**
     * Checks if this monitor is held by the given thread.
     * 
     * @param __vmThread The virtual machine thread.
     * @return If this is held by the given thread.
     * @throws NullPointerException On null arguments.
     * @since 2020/06/27
     */
    public boolean isHeldBy(SpringThread __vmThread)
        throws NullPointerException
    {
        if (__vmThread == null)
            throw new NullPointerException("NARG");
        
        synchronized (this)
        {
            return this._owner == __vmThread;
        }
    }
    
    /**
     * Notifies on this monitor and returns the status.
     *
     * @param __by The thread that is doing the notify.
     * @param __all Notify all threads?
     * @return The {@link MonitorResultType}.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/20
     */
    public final int monitorNotify(SpringThread __by, boolean __all)
        throws NullPointerException
    {
        if (__by == null)
            throw new NullPointerException("NARG");
        
        // Lock on the monitor lock
        synchronized (this)
        {
            // Wrong thread?
            if (this._owner != __by)
                return MonitorResultType.NOT_OWNED;
            
            // Nothing is waiting, do not bother at all!
            int waitcount = this._waitcount;
            if (waitcount == 0)
                return 0;
            
            // Notify all threads or just one?
            // Never let the notify count exceed the wait count as well
            int notifycount = this._notifycount;
            this._notifycount = Math.min(waitcount,
                (__all ? waitcount : notifycount + 1));
            
            // Notify all threads that something happened with the lock
            this.notifyAll();
            
            return 0;
        }
    }
    
    /**
     * Waits on the monitor.
     *
     * @param __by The thread doing the wait.
     * @param __ms The milliseconds to wait.
     * @param __ns The nanoseconds to wait.
     * @return The {@link MonitorResultType}.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/21
     */
    public final int monitorWait(SpringThread __by, long __ms, int __ns)
        throws NullPointerException
    {
        if (__by == null)
            throw new NullPointerException("NARG");
        
        // Lock on the monitor lock
        synchronized (this)
        {
            // Wrong thread?
            if (this._owner != __by)
                return MonitorResultType.NOT_OWNED;
            
            // Increase our wait count
            this._waitcount++;
            
            // Relinquish control of this monitor, so that way when we actually
            // internally do the wait we check notify counts and such.
            this.exit(__by, true);
            
            // Do looped wait, but it may be timed
            boolean waitforever = (__ms == 0 && __ns == 0),
                interrupted = false,
                expired = false;
            long end = (waitforever ? Long.MAX_VALUE :
                System.nanoTime() + (__ms * 1_000_000L) + __ns);
            for (;;)
            {
                // read our wait and notify counts to determine if we
                // should take control here
                int nownotifycount = this._notifycount,
                    nowwaitcount = this._waitcount;
                
                // We were notified, interrupted, or expired, take it and leave
                if (interrupted || expired || nownotifycount > 0)
                {
                    // Reduce the notify count, but not when interrupted or
                    // expired
                    if (!interrupted && !expired)
                        this._notifycount--;
                    
                    // Reduce wait count
                    int waitcount = this._waitcount;
                    this._waitcount = --waitcount;
                    
                    // Never let the notification count exceed the wait count
                    if (this._notifycount > waitcount)
                        this._notifycount = waitcount;
                    
                    // Re-enter the monitor
                    this.enter(__by);
                    
                    // Whatever state we ended up in
                    if (interrupted)
                        return MonitorResultType.INTERRUPTED;
                    return MonitorResultType.NOT_INTERRUPTED;
                }
                
                // Otherwise wait for notification to happen
                else
                {
                    // Could be interrupted
                    try
                    {
                        // Indicate that we are waiting on a monitor
                        SpringMonitor.__changeState(__by, true);
                        
                        // Check if time expired
                        if (!waitforever)
                        {
                            long rem = end - System.nanoTime();
                            if (rem <= 0)
                            {
                                expired = true;
                                continue;
                            }
                            
                            // Wait for this time
                            this.wait(rem / 1_000_000L,
                                (int)(rem % 1_000_000L));
                        }
                        
                        // Wait forever
                        else
                        {
                            this.wait();
                        }
                    }
                    
                    // Was interrupted
                    catch (InterruptedException e)
                    {
                        interrupted = true;
                    }
                    
                    // Go back to the running state
                    finally
                    {
                        SpringMonitor.__changeState(__by, false);
                    }
                }
            }
        }
    }
    
    /**
     * Changes the state of the thread to indicate monitor status.
     * 
     * @param __thread The thread to change the state of.
     * @param __wait Are we waiting?
     * @since 2021/04/25
     */
    private static void __changeState(SpringThread __thread, boolean __wait)
    {
        // Get the profiler information
        SpringThreadFrame currentFrame = __thread.currentFrame();
        ProfiledFrame profiler = (currentFrame == null ? null :
            currentFrame._profiler);
        
        // Is now waiting
        if (__wait)
        {
            // Set as waiting
            __thread.setStatus(ThreadStatusType.MONITOR_WAIT);
            
            // Do not count CPU time
            if (profiler != null)
                profiler.sleep(true, System.nanoTime());
        }
        
        // No longer waiting
        else
        {
            // Is now running
            __thread.setStatus(ThreadStatusType.RUNNING);
            
            // Continue counting CPU time
            if (profiler != null)
                profiler.sleep(false, System.nanoTime());
        }
    }
}