ptolemy.kernel.util
Class Workspace

java.lang.Object
  extended by ptolemy.kernel.util.Workspace
All Implemented Interfaces:
java.io.Serializable, Nameable

public final class Workspace
extends java.lang.Object
implements Nameable, java.io.Serializable

An instance of Workspace is used for synchronization and version tracking of interdependent groups of objects. These objects are said to be in the workspace. This is not the same as the container association in Ptolemy II. A workspace is never returned by a getContainer() method.

The workspace provides a rudimentary directory service that can be used to keep track of the objects within it. It is not required to use it in order to use the workspace for synchronization. Items are added to the directory by calling add(). The names of the items in the directory are not required to be unique.

The synchronization model of the workspace is a multiple-reader, single-writer model. Any number of threads can simultaneously read the workspace. Only one thread at a time can have write access to the workspace, and while the write access is held, no other thread can get read access.

When reading the state of objects in the workspace, a thread must ensure that no other thread is simultaneously modifying the objects in the workspace. To read-synchronize on a workspace, use the following code:

 try {
 _workspace.getReadAccess();
 // ... code that reads
 } finally {
 _workspace.doneReading();
 }
 
We assume that the _workspace variable references the workspace, as for example in the NamedObj class. The getReadAccess() method suspends the current thread if another thread is currently modifying the workspace, and otherwise returns immediately. Note that multiple readers can simultaneously have read access. The finally clause is executed even if an exception occurs. This is essential because without the call to doneReading(), the workspace will never again allow any thread to modify it.

To make safe changes to the objects in a workspace, a thread must write-synchronize using the following code:

 try {
 _workspace.getWriteAccess();
 // ... code that writes
 } finally {
 _workspace.doneWriting();
 }
 
Again, the call to doneWriting() is essential, or the workspace will remain permanently locked to either reading or writing.

Note that it is not necessary to obtain a write lock just to add an item to the workspace directory. The methods for accessing the directory are all synchronized, so there is no risk of any thread reading an inconsistent state.

A major subtlety in using this class concerns acquiring a write lock while holding a read lock. If a thread holds a read lock and calls getWriteAccess(), if any other thread holds a read lock, then the call to getWriteAccess() will block the calling thread until those other read accesses are released. However, while the thread is blocked, it yields its read permissions. This prevents a deadlock from occurring, but it means that the another thread may acquire write permission while the thread is stalled and modify the model. Specifically, the pattern is:

     try {
        _workspace.getReadAccess();
        ... do things ...
        try {
           _workspace.getWriteAccess();
           ... at this point, the structure of a model may have changed! ...
           ... make my own changes knowing that the structure may have changed...
        } finally {
           _workspace.doneWriting();
        }
        ... continue doing things, knowing the model may have changed...
     } finally {
        _workspace.doneReading();
     }
  
Unfortunately, a user may acquire a read access and invoke a method that, unknown to the user, acquires write access. For this reason, it is very important to document methods that acquire write access, and to avoid invoking them within blocks that hold read access. Note that there is no difficulty acquiring read access from within a block holding write access.

Since:
Ptolemy II 0.2
Version:
$Id: Workspace.java 57040 2010-01-27 20:52:32Z cxh $
Author:
Edward A. Lee, Mudit Goel, Lukito Muliadi, Xiaojun Liu
See Also:
Serialized Form
Accepted Rating:
Green (liuxj)
Proposed Rating:
Green (liuxj)

Nested Class Summary
private static class Workspace.AccessRecord
           
 
Field Summary
private  java.util.LinkedList _directory
           
private  java.lang.Thread _lastReader
           
private  Workspace.AccessRecord _lastReaderRecord
           
private  java.lang.String _name
           
private  long _numReaders
           
private  java.util.HashMap _readerRecords
           
private  long _version
           
private  int _waitingWriteRequests
           
private  int _writeDepth
           
private  java.lang.Thread _writer
           
 
Constructor Summary
Workspace()
          Create a workspace with an empty string as its name.
Workspace(java.lang.String name)
          Create a workspace with the specified name.
 
Method Summary
protected  java.lang.String _description(int detail, int indent, int bracket)
          Return a description of the workspace.
private  void _doneWriting(boolean incrementWorkspaceVersion)
          Indicate that the calling thread is finished writing.
private  Workspace.AccessRecord _getAccessRecord(java.lang.Thread current, boolean createNew)
          Return the AccessRecord object for the specified thread.
private  void _reacquireReadPermissions(int count)
           
private  int _releaseAllReadPermissions()
          Frees the thread of all the readAccesses on the workspace held by the current thread.
 void add(NamedObj item)
          Add an item to the directory.
 java.lang.String description()
          Return a full description of the workspace and everything in its directory.
 java.lang.String description(int detail)
          Return a description of the workspace.
 java.util.Enumeration directory()
          Deprecated. Use directoryList() instead.
 java.util.List directoryList()
          Return an unmodifiable list of the items in the directory, in the order in which they were added.
 void doneReading()
          Indicate that the calling thread is finished reading.
 void doneTemporaryWriting()
          Indicate that the calling thread is finished writing.
 void doneWriting()
          Indicate that the calling thread is finished writing.
 NamedObj getContainer()
          Get the container.
 java.lang.String getDisplayName()
          Return a name to present to the user, which is the same as what is returned by getName().
 java.lang.String getFullName()
          Get the full name.
 java.lang.String getName()
          Get the name.
 java.lang.String getName(NamedObj relativeTo)
          Get the name.
 void getReadAccess()
          Obtain permission to read objects in the workspace.
 long getVersion()
          Get the version number.
 void getWriteAccess()
          Obtain permission to write to objects in the workspace.
 boolean handleModelError(NamedObj context, IllegalActionException exception)
          Handle a model error by throwing the specified exception.
 void incrVersion()
          Increment the version number by one.
 void reacquireReadPermission(int depth)
          Reacquire read permission on the workspace for the current thread.
 int releaseReadPermission()
          Release read permission on the workspace held by the current thread, and return the depth of the nested calls to getReadAccess().
 void remove(NamedObj item)
          Remove the specified item from the directory.
 void removeAll()
          Remove all items from the directory.
 void setName(java.lang.String name)
          Set or change the name.
 java.lang.String toString()
          Return a concise description of the object.
 void wait(java.lang.Object obj)
          Release all the read accesses held by the current thread and suspend the thread by calling Object.wait() on the specified object.
 void wait(java.lang.Object obj, long timeout)
          This method is equivalent to the single argument version except that you can specify a timeout, which is in milliseconds.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
 

Field Detail

_directory

private java.util.LinkedList _directory

_name

private java.lang.String _name

_version

private long _version

_writer

private transient java.lang.Thread _writer

_waitingWriteRequests

private int _waitingWriteRequests

_writeDepth

private int _writeDepth

_lastReader

private transient java.lang.Thread _lastReader

_lastReaderRecord

private transient Workspace.AccessRecord _lastReaderRecord

_readerRecords

private transient java.util.HashMap _readerRecords

_numReaders

private long _numReaders
Constructor Detail

Workspace

public Workspace()
Create a workspace with an empty string as its name.


Workspace

public Workspace(java.lang.String name)
Create a workspace with the specified name. This name will form the prefix of the full name of all contained objects. If the name argument is null, then an empty string "" is used as the name.

Parameters:
name - Name of the workspace.
Method Detail

add

public void add(NamedObj item)
         throws IllegalActionException
Add an item to the directory. The names of the objects in the directory are not required to be unique. Only items with no container can be added. Items with a container are still viewed as being within the workspace, but they are not explicitly listed in the directory. Instead, their top-level container is expected to be listed (although this is not enforced). Increment the version number.

Parameters:
item - Item to list in the directory.
Throws:
IllegalActionException - If the item has a container, is already in the directory, or is not in this workspace.

description

public java.lang.String description()
                             throws IllegalActionException
Return a full description of the workspace and everything in its directory. This is accomplished by calling the description method with an argument for full detail.

Specified by:
description in interface Nameable
Returns:
A description of the workspace.
Throws:
IllegalActionException

description

public java.lang.String description(int detail)
                             throws IllegalActionException
Return a description of the workspace. The level of detail depends on the argument, which is an or-ing of the static final constants defined in the NamedObj class. This method returns an empty string (not null) if there is nothing to report. If the contents are requested, then the items in the directory are also described.

Parameters:
detail - The level of detail.
Returns:
A description of the workspace.
Throws:
IllegalActionException

directory

public java.util.Enumeration directory()
Deprecated. Use directoryList() instead.

Enumerate the items in the directory, in the order in which they were added.

Returns:
An enumeration of NamedObj objects.

directoryList

public java.util.List directoryList()
Return an unmodifiable list of the items in the directory, in the order in which they were added.

Returns:
A list of instances of NamedObj.

doneReading

public final void doneReading()
Indicate that the calling thread is finished reading. If this thread is completely done reading (it has no other read access to the workspace), then notify all threads that are waiting to get read/write access to this workspace so that they may contend for access.

Throws:
InvalidStateException - If this method is called before a corresponding call to getReadAccess() by the same thread.

doneTemporaryWriting

public final void doneTemporaryWriting()
Indicate that the calling thread is finished writing. If this thread is completely done writing (it has no other write access to the workspace), then notify all threads that are waiting to get read/write access to this workspace so that they may contend for access. This method does not increment the version number of the workspace. This method is used to temporarily add an attribute to the workspace without increment the version number, or otherwise to gain exclusive access to the workspace without changing the structure of the model.

Throws:
InvalidStateException - If this method is called before a corresponding call to getWriteAccess() by the same thread.
See Also:
Attribute.Attribute(NamedObj, String, boolean), doneWriting()

doneWriting

public final void doneWriting()
Indicate that the calling thread is finished writing. If this thread is completely done writing (it has no other write access to the workspace), then notify all threads that are waiting to get read/write access to this workspace so that they may contend for access. It also increments the version number of the workspace.

Throws:
InvalidStateException - If this method is called before a corresponding call to getWriteAccess() by the same thread.

getContainer

public NamedObj getContainer()
Get the container. Always return null since a workspace has no container.

Specified by:
getContainer in interface Nameable
Returns:
null.

getDisplayName

public java.lang.String getDisplayName()
Return a name to present to the user, which is the same as what is returned by getName().

Specified by:
getDisplayName in interface Nameable
Returns:
The name.
See Also:
getName()

getFullName

public java.lang.String getFullName()
Get the full name.

Specified by:
getFullName in interface Nameable
Returns:
The name of the workspace.

getName

public java.lang.String getName()
Get the name.

Specified by:
getName in interface Nameable
Returns:
The name of the workspace.
See Also:
setName(String)

getName

public java.lang.String getName(NamedObj relativeTo)
Get the name. Since this can have no container, the relative name is always the same as the name.

Specified by:
getName in interface Nameable
Parameters:
relativeTo - This argument is ignored.
Returns:
The name of the workspace.
See Also:
setName(String)

getReadAccess

public final void getReadAccess()
Obtain permission to read objects in the workspace. This method suspends the calling thread until read access has been obtained. Read access is granted unless either another thread has write access, or there are threads that have requested write access and not gotten it yet. If this thread already has read access, then access is granted irrespective of other write requests. If the calling thread is interrupted while waiting to get read access, an InternalErrorException is thrown, and the thread does not have read permission to the workspace. It is essential that a call to this method is matched by a call to doneReading(), regardless of whether this method returns normally or an exception is thrown. This is to ensure that the workspace is in a consistent state, otherwise write access may never again be granted in this workspace. If while holding read access the thread attempts to get write access and is blocked, then upon attempting to get write access, the read lock is released, and it is not reacquired until the write access is granted. Therefore, upon any call to any method that gets write access within a block holding read access, the model structure can change. You should not assume that the model is the same prior to the call as after.

Throws:
InternalErrorException - If the calling thread is interrupted while waiting to get read access.
See Also:
doneReading()

getVersion

public final long getVersion()
Get the version number. The version number is incremented on each call to doneWriting() and also on calls to incrVersion(). It is meant to track changes to the objects in the workspace.

Returns:
A non-negative long integer.

getWriteAccess

public final void getWriteAccess()
Obtain permission to write to objects in the workspace. Write access is granted if there are no other threads that currently have read or write access. In particular, it is granted if this thread already has write access, or if it is the only thread with read access. This method suspends the calling thread until such access has been obtained. If the calling thread is interrupted while waiting to get write access, an InternalErrorException is thrown, and the thread does not have write permission to the workspace. It is essential that a call to this method is matched by a call to doneWriting(), regardless of whether this method returns normally or an exception is thrown. This is to ensure that the workspace is in a consistent state, otherwise read or write access may never again be granted in this workspace.

Throws:
InternalErrorException - If the calling thread is interrupted while waiting to get write access.
See Also:
doneWriting()

handleModelError

public boolean handleModelError(NamedObj context,
                                IllegalActionException exception)
                         throws IllegalActionException
Handle a model error by throwing the specified exception.

Parameters:
context - The object in which the error occurred.
exception - An exception that represents the error.
Returns:
Never returns.
Throws:
IllegalActionException - The exception passed as an argument is always thrown.
Since:
Ptolemy II 2.1

incrVersion

public final void incrVersion()
Increment the version number by one.


reacquireReadPermission

public void reacquireReadPermission(int depth)
Reacquire read permission on the workspace for the current thread. Call this after a call to releaseReadPermissions().

Parameters:
depth - The depth of the permissions to reacquire.
See Also:
releaseReadPermission()

releaseReadPermission

public int releaseReadPermission()
Release read permission on the workspace held by the current thread, and return the depth of the nested calls to getReadAccess(). It is essential that after calling this, you also call reacquireReadPermission(int), passing it as an argument the value returned by this method. Hence, you should use this as follows:
    int depth = releaseReadPermission();
    try {
       ... do whatever here ...
    } finally {
       reacquireReadPermission(depth);
    }
  
Note that this is done automatically by the wait(Object) method, so if you can use that instead, please do.

Returns:
The depth of read permissions held by the current thread.
See Also:
reacquireReadPermission(int), wait(Object), wait(Object, long)

remove

public void remove(NamedObj item)
Remove the specified item from the directory. Note that that item will still refer to this workspace as its workspace (its workspace is immutable). If the object is not in the directory, do nothing. Increment the version number.

Parameters:
item - The NamedObj to be removed.

removeAll

public void removeAll()
Remove all items from the directory. Note that those items will still refer to this workspace as their workspace (their workspace is immutable). Increment the version number.


setName

public void setName(java.lang.String name)
Set or change the name. If a null argument is given the name is set to an empty string. Increment the version number.

Specified by:
setName in interface Nameable
Parameters:
name - The new name.
See Also:
getName()

toString

public java.lang.String toString()
Return a concise description of the object.

Overrides:
toString in class java.lang.Object
Returns:
The class name and name.

wait

public void wait(java.lang.Object obj)
          throws java.lang.InterruptedException
Release all the read accesses held by the current thread and suspend the thread by calling Object.wait() on the specified object. When the call returns, re-acquire all the read accesses held earlier by the thread and return. If the calling thread is interrupted while waiting to re-acquire read accesses, an InternalErrorException is thrown, and the thread no longer has read access to the workspace. This method helps prevent deadlocks caused when a thread that waits for another thread to do something prevents it from doing that something by holding read access on the workspace. IMPORTANT: The calling thread should not hold a lock on obj when calling this method, unlike a direct call to obj.wait(). Holding such a lock can lead to deadlock because this method can block for an indeterminate amount of time while trying to reacquire read permissions that it releases. Moreover, holding such a lock is pointless since this method internally calls obj.wait() (within its own synchronized(obj) block, so the calling method cannot assume that the lock on obj was held during the entire execution of this method. If the calling thread needs to hold a lock on obj until obj.wait() is called, then you should manually release read permissions and release the obj before reacquiring them, as follows:
    int depth = 0;
    try {
       synchrononized(obj) {
           ...
           depth = releaseReadPermission();
           obj.wait();
        }
    } finally {
       if (depth > 0) {
          reacquireReadPermission(depth);
       }
    }
  

Parameters:
obj - The object that the thread wants to wait on.
Throws:
java.lang.InterruptedException - If the calling thread is interrupted while waiting on the specified object and all the read accesses held earlier by the thread are re-acquired.
InternalErrorException - If re-acquiring the read accesses held earlier by the thread fails.

wait

public void wait(java.lang.Object obj,
                 long timeout)
          throws java.lang.InterruptedException
This method is equivalent to the single argument version except that you can specify a timeout, which is in milliseconds. If value of the timeout argument is zero, then the method is exactly equivalent to the single argument version, and no timeout is implemented. If the value is larger than zero, then the method returns if either the thread is notified by another thread or the timeout expires. IMPORTANT: The calling thread should not hold a lock on obj when calling this method, unlike a direct call to obj.wait(). Holding such a lock can lead to deadlock because this method can block for an indeterminate amount of time while trying to reacquire read permissions that it releases. Moreover, holding such a lock is pointless since this method internally calls obj.wait() (within its own synchronized(obj) block, so the calling method cannot assume that the lock on obj was held during the entire execution of this method.

Parameters:
obj - The object that the thread wants to wait on.
timeout - The maximum amount of time to wait, in milliseconds, or zero to not specify a timeout.
Throws:
java.lang.InterruptedException - If the calling thread is interrupted while waiting on the specified object and all the read accesses held earlier by the thread are re-acquired.
InternalErrorException - If re-acquiring the read accesses held earlier by the thread fails.
See Also:
wait(Object)

_description

protected java.lang.String _description(int detail,
                                        int indent,
                                        int bracket)
                                 throws IllegalActionException
Return a description of the workspace. The level of detail depends on the argument, which is an or-ing of the static final constants defined in the NamedObj class. If the contents are requested, then the items in the directory are also described. Zero, one or two brackets can be specified to surround the returned description. If one is specified it is the the leading bracket. This is used by derived classes that will append to the description. Those derived classes are responsible for the closing bracket. An argument other than 0, 1, or 2 is taken to be equivalent to 0.

Parameters:
detail - The level of detail.
indent - The amount of indenting.
bracket - The number of surrounding brackets (0, 1, or 2).
Returns:
A description of the workspace.
Throws:
IllegalActionException

_doneWriting

private final void _doneWriting(boolean incrementWorkspaceVersion)
Indicate that the calling thread is finished writing. If this thread is completely done writing (it has no other write access to the workspace), then notify all threads that are waiting to get read/write access to this workspace so that they may contend for access. It also increments the version number of the workspace if the incrementWorkspaceVersion parameter is true.

Parameters:
incrementWorkspaceVersion - True if we should increment the version. The incrementWorkspaceVersion parameter should almost always be true, if it is set to false, then perhaps a temporary variable is being added.
Throws:
InvalidStateException - If this method is called before a corresponding call to getWriteAccess() by the same thread.

_getAccessRecord

private final Workspace.AccessRecord _getAccessRecord(java.lang.Thread current,
                                                      boolean createNew)
Return the AccessRecord object for the specified thread. If the flag createNew is true and the current thread does not have an access record, then create a new one and return it. Set _lastReaderRecord to be the record returned.


_reacquireReadPermissions

private void _reacquireReadPermissions(int count)

_releaseAllReadPermissions

private int _releaseAllReadPermissions()
Frees the thread of all the readAccesses on the workspace held by the current thread. The method _reacquireAllReadAccesses should be called after this method is called.

Returns:
The number of readAccess that the thread possessed on the workspace