001/* An object for synchronization and version tracking of groups of objects. 002 003 Copyright (c) 1997-2018 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 Made _writer, _lastReader, _lastReaderRecord, and _readerRecords 028 transient so that object would be serializable. However, serialization 029 is probably not right if there are outstanding read or write permissions. 030 -- eal 031 032 */ 033package ptolemy.kernel.util; 034 035import java.lang.ref.WeakReference; 036import java.util.Collections; 037import java.util.Enumeration; 038import java.util.Iterator; 039import java.util.LinkedList; 040import java.util.List; 041import java.util.WeakHashMap; 042 043/////////////////////////////////////////////////////////////////// 044//// Workspace 045 046/** 047 An instance of Workspace is used for synchronization and version tracking 048 of interdependent groups of objects. These objects are said to be in the 049 workspace. This is not the same as the <i>container</i> association 050 in Ptolemy II. A workspace is never returned by a getContainer() method. 051 <p> 052 The workspace provides a rudimentary directory service that can 053 be used to keep track of the objects within it. It is not required to use 054 it in order to use the workspace for synchronization. Items are added 055 to the directory by calling add(). 056 The names of the items in the directory are not required to be unique. 057 <p> 058 The synchronization model of the workspace is a multiple-reader, 059 single-writer model. Any number of threads can simultaneously read the 060 workspace. Only one thread at a time can have write access to the workspace, 061 and while the write access is held, no other thread can get read access. 062 <p> 063 When reading the state of objects in the workspace, a thread must 064 ensure that no other thread is simultaneously modifying the objects in the 065 workspace. To read-synchronize on a workspace, use the following code: 066 <pre> 067 try { 068 _workspace.getReadAccess(); 069 // ... code that reads 070 } finally { 071 _workspace.doneReading(); 072 } 073 </pre> 074 We assume that the _workspace variable references the workspace, as for example 075 in the NamedObj class. The getReadAccess() method suspends the current thread 076 if another thread is currently modifying the workspace, and otherwise 077 returns immediately. Note that multiple readers can simultaneously have 078 read access. The finally clause is executed even if 079 an exception occurs. This is essential because without the call 080 to doneReading(), the workspace will never again allow any thread 081 to modify it. 082 <p> 083 To make safe changes to the objects in a workspace, a thread must 084 write-synchronize using the following code: 085 <pre> 086 try { 087 _workspace.getWriteAccess(); 088 // ... code that writes 089 } finally { 090 _workspace.doneWriting(); 091 } 092 </pre> 093 Again, the call to doneWriting() is essential, or the workspace 094 will remain permanently locked to either reading or writing. 095 <p> 096 Note that it is not necessary to obtain a write lock just to add 097 an item to the workspace directory. The methods for accessing 098 the directory are all synchronized, so there is no risk of any 099 thread reading an inconsistent state. 100 <p> 101 A major subtlety in using this class concerns acquiring a write 102 lock while holding a read lock. If a thread holds a read lock 103 and calls getWriteAccess(), if any other thread holds a read lock, 104 then the call to getWriteAccess() will block the calling thread 105 until those other read accesses are released. However, while 106 the thread is blocked, it yields its read permissions. This 107 prevents a deadlock from occurring, but it means that the 108 another thread may acquire write permission while the thread 109 is stalled and modify the model. Specifically, the pattern 110 is: 111 <pre> 112 try { 113 _workspace.getReadAccess(); 114 ... do things ... 115 try { 116 _workspace.getWriteAccess(); 117 ... at this point, the structure of a model may have changed! ... 118 ... make my own changes knowing that the structure may have changed... 119 } finally { 120 _workspace.doneWriting(); 121 } 122 ... continue doing things, knowing the model may have changed... 123 } finally { 124 _workspace.doneReading(); 125 } 126 </pre> 127 Unfortunately, a user may acquire a read access and invoke a method 128 that, unknown to the user, acquires write access. For this reason, it 129 is very important to document methods that acquire write access, and 130 to avoid invoking them within blocks that hold read access. Note that 131 there is no difficulty acquiring read access from within a block 132 holding write access. 133 134 @author Edward A. Lee, Mudit Goel, Lukito Muliadi, Xiaojun Liu 135 @version $Id$ 136 @since Ptolemy II 0.2 137 @Pt.ProposedRating Green (liuxj) 138 @Pt.AcceptedRating Green (liuxj) 139 */ 140public final class Workspace implements Nameable { 141 // Note that Nameable extends ModelErrorHandler, so this class 142 // need not declare that it directly implements ModelErrorHandler. 143 144 // NOTE: it would make sense to have Workspace extend Observable 145 // in order to notify observers of changes (marten 29-10-2012). 146 147 /** Create a workspace with an empty string as its name. 148 */ 149 public Workspace() { 150 super(); 151 setName(""); 152 } 153 154 /** Create a workspace with the specified name. This name will form the 155 * prefix of the full name of all contained objects. If the name 156 * argument is null, then an empty string "" is used as the name. 157 * @param name Name of the workspace. 158 */ 159 public Workspace(String name) { 160 super(); 161 setName(name); 162 } 163 164 /////////////////////////////////////////////////////////////////// 165 //// public methods //// 166 167 /** Add an item to the directory. The names of the objects 168 * in the directory are not required to be unique. 169 * Only items with no container can be added. Items with 170 * a container are still viewed as being within the workspace, but 171 * they are not explicitly listed in the directory. Instead, 172 * their top-level container is expected to be listed (although this 173 * is not enforced). Increment the version number. 174 * @param item Item to list in the directory. 175 * @exception IllegalActionException If the item has a container, is 176 * already in the directory, or is not in this workspace. 177 */ 178 public synchronized void add(NamedObj item) throws IllegalActionException { 179 if (item.workspace() != this) { 180 throw new IllegalActionException(this, item, 181 "Cannot add an item to the directory of a workspace " 182 + "that it is not in."); 183 } 184 185 if (item.getContainer() != null) { 186 throw new IllegalActionException(this, item, 187 "Cannot add an object with a container to a workspace " 188 + "directory."); 189 } 190 191 if (_directory.indexOf(item) >= 0) { 192 throw new IllegalActionException(this, item, 193 "Object is already listed in the workspace directory."); 194 } 195 196 _directory.add(item); 197 incrVersion(); 198 } 199 200 /** Return a full description of the workspace and everything in its 201 * directory. This is accomplished 202 * by calling the description method with an argument for full detail. 203 * @return A description of the workspace. 204 * @exception IllegalActionException If thrown while getting the 205 * description of subcomponents. 206 */ 207 @Override 208 public synchronized String description() throws IllegalActionException { 209 // NOTE: It is not strictly needed for this method to be 210 // synchronized, since _description is. However, by making it 211 // synchronized, the documentation shows this on the public 212 // interface, not just the protected one. 213 return description(NamedObj.COMPLETE); 214 } 215 216 /** Return a description of the workspace. The level of detail depends 217 * on the argument, which is an or-ing of the static final constants 218 * defined in the NamedObj class. This method returns an empty 219 * string (not null) if there is nothing to report. If the contents 220 * are requested, then the items in the directory are also described. 221 * @param detail The level of detail. 222 * @return A description of the workspace. 223 * @exception IllegalActionException If thrown while getting the 224 * description of subcomponents. 225 */ 226 public synchronized String description(int detail) 227 throws IllegalActionException { 228 // NOTE: It is not strictly needed for this method to be 229 // synchronized, since _description is. However, by making it 230 // synchronized, the documentation shows this on the public 231 // interface, not just the protected one. 232 return _description(detail, 0, 0); 233 } 234 235 /** Enumerate the items in the directory, in the order in which 236 * they were added. 237 * @deprecated Use directoryList() instead. 238 * @return An enumeration of NamedObj objects. 239 */ 240 @Deprecated 241 public synchronized Enumeration directory() { 242 return Collections.enumeration(_directory); 243 } 244 245 /** Return an unmodifiable list of the items in the directory, 246 * in the order in which they were added. 247 * @return A list of instances of NamedObj. 248 */ 249 public synchronized List<NamedObj> directoryList() { 250 return Collections.unmodifiableList(_directory); 251 } 252 253 /** Indicate that the calling thread is finished reading. 254 * If this thread is completely done reading (it has no other 255 * read access to the workspace), then notify all threads that are 256 * waiting to get read/write access to this 257 * workspace so that they may contend for access. 258 * 259 * @exception InvalidStateException If this method is called 260 * before a corresponding call to getReadAccess() by the same thread. 261 * @see #getReadAccess() 262 */ 263 public final synchronized void doneReading() { 264 Thread current = Thread.currentThread(); 265 AccessRecord record = null; 266 267 record = _lastReaderRecordOrCurrentAccessRecord(current, false); 268 269 if (record == null) { 270 throw new InvalidStateException(this, 271 "Workspace: doneReading() called without a prior " 272 + "matching call to getReadAccess()!"); 273 } 274 275 if (record.readDepth > 0) { 276 record.readDepth--; 277 278 if (record.readDepth == 0) { 279 // the current thread is no longer a reader 280 _numReaders--; 281 282 // notify waiting writers 283 // these writers may have read access, so this notification 284 // cannot be conditioned on _numReaders == 0 285 // possible condition: _writeReq >= _numReaders 286 notifyAll(); 287 } 288 } else if (record.failedReadAttempts > 0) { 289 record.failedReadAttempts--; 290 } else { 291 throw new InvalidStateException(this, 292 "Workspace: doneReading() called without a prior " 293 + "matching call to getReadAccess()!"); 294 } 295 } 296 297 /** Indicate that the calling thread is finished writing. 298 * If this thread is completely done writing (it has no other 299 * write access to the workspace), then notify all threads 300 * that are waiting to get read/write access to this workspace 301 * so that they may contend for access. 302 * This method <b>does not</b> increment the version number 303 * of the workspace. This method is used to temporarily add 304 * an attribute to the workspace without increment the version number, 305 * or otherwise to gain exclusive access to the workspace without 306 * changing the structure of the model. 307 * @exception InvalidStateException If this method is called before 308 * a corresponding call to getWriteAccess() by the same thread. 309 * @see ptolemy.kernel.util.Attribute#Attribute(NamedObj, String, boolean) 310 * @see #doneWriting() 311 */ 312 public final synchronized void doneTemporaryWriting() { 313 _doneWriting(false); 314 } 315 316 /** Indicate that the calling thread is finished writing. 317 * If this thread is completely done writing (it has no other 318 * write access to the workspace), then notify all threads 319 * that are waiting to get read/write access to this workspace 320 * so that they may contend for access. 321 * It also increments the version number of the workspace. 322 * @exception InvalidStateException If this method is called before 323 * a corresponding call to getWriteAccess() by the same thread. 324 */ 325 public final synchronized void doneWriting() { 326 _doneWriting(true); 327 } 328 329 /** Get the container. Always return null since a workspace 330 * has no container. 331 * @return null. 332 */ 333 @Override 334 public NamedObj getContainer() { 335 return null; 336 } 337 338 /** Return a name to present to the user, which is the 339 * same as what is returned by getName(). 340 * @return The name. 341 * @see #getName() 342 */ 343 @Override 344 public String getDisplayName() { 345 return getName(); 346 } 347 348 /** Get the full name. 349 * @return The name of the workspace. 350 */ 351 @Override 352 public String getFullName() { 353 return _name; 354 } 355 356 /** Get the name. 357 * @return The name of the workspace. 358 * @see #setName(String) 359 */ 360 @Override 361 public String getName() { 362 return _name; 363 } 364 365 /** Get the name. Since this can have no container, the relative 366 * name is always the same as the name. 367 * @param relativeTo This argument is ignored. 368 * @return The name of the workspace. 369 * @see #setName(String) 370 */ 371 @Override 372 public String getName(NamedObj relativeTo) { 373 return _name; 374 } 375 376 /** Obtain permission to read objects in the workspace. 377 * This method suspends the calling thread until read access 378 * has been obtained. Read access is granted unless either another 379 * thread has write access, or there are threads that 380 * have requested write access and not gotten it yet. If this thread 381 * already has read access, then access is granted irrespective of 382 * other write requests. 383 * If the calling thread is interrupted while waiting to get read 384 * access, an InternalErrorException is thrown, and the thread does 385 * not have read permission to the workspace. 386 * It is essential that a call to this method is matched by a call to 387 * doneReading(), regardless of whether this method returns normally or 388 * an exception is thrown. This is to ensure that the workspace is in a 389 * consistent state, otherwise write access may never again be 390 * granted in this workspace. 391 * If while holding read access the thread attempts to get write access 392 * and is blocked, then upon attempting to get write access, the read 393 * lock is released, and it is not reacquired until the write access 394 * is granted. Therefore, upon any call to any method that gets write 395 * access within a block holding read access, the model structure can 396 * change. You should not assume that the model is the same prior to 397 * the call as after. 398 * @exception InternalErrorException If the calling thread is interrupted 399 * while waiting to get read access. 400 * @see #doneReading() 401 */ 402 public final synchronized void getReadAccess() { 403 // This method should throw an InterruptedException when the 404 // calling thread is interrupted. InterruptedException is a 405 // checked exception, so changing this will lead to changes 406 // everywhere this method is called, which is a huge amount 407 // of work. 408 Thread current = Thread.currentThread(); 409 AccessRecord record = _lastReaderRecordOrCurrentAccessRecord(current, 410 true); 411 412 if (record.readDepth > 0) { 413 // If the current thread has read permission, then grant 414 // it read permission 415 record.readDepth++; 416 return; 417 } else if (current == _writer) { 418 record.readDepth++; 419 420 // The current thread has write permission, so we grant 421 // read permission. 422 // This is a new reader, so we increment the number 423 // of readers. 424 _numReaders++; 425 return; 426 } 427 428 // Possibly need to wait for read access. 429 // First increment this to make the record not empty, so as to 430 // prevent the record from being deleted from the _readerRecords 431 // table by other threads. 432 record.failedReadAttempts++; 433 434 // Go into a loop, and at each iteration check whether the current 435 // thread can get read access. If not then do a wait() on the 436 // workspace. Otherwise, exit the loop. 437 while (_waitingWriteRequests != 0 || _writer != null) { 438 try { 439 wait(); 440 } catch (InterruptedException ex) { 441 /* Throwing an exception is not correct here. 442 * CompositeActor may interrupt this thread upon a change request. 443 * We should just continue waiting. 444 * 445 throw new InternalErrorException(this, ex, 446 current.getName() 447 + ": Thread interrupted while waiting to get " 448 + "read access: " + ex.getMessage()); 449 */ 450 } 451 } 452 453 record.failedReadAttempts--; 454 455 // Now there is no writer, and no thread waiting to get write access. 456 record.readDepth++; 457 458 // This is a new reader, so we increment the number 459 // of readers. 460 _numReaders++; 461 return; 462 } 463 464 /** Get the version number. The version number is incremented on 465 * each call to doneWriting() and also on calls to incrVersion(). 466 * It is meant to track changes to the objects in the workspace. 467 * @return A non-negative long integer. 468 */ 469 public synchronized final long getVersion() { 470 return _version; 471 } 472 473 /** Obtain permission to write to objects in the workspace. 474 * Write access is granted if there are no other threads that currently 475 * have read or write access. In particular, it <i>is</i> granted 476 * if this thread already has write access, or if it is the only 477 * thread with read access. Note that if this method blocks, a side 478 * effect is that other threads will block when trying to acquire read 479 * access so as to ensure that write access will eventually be granted. 480 * This makes it very risky to hold any locks while trying to acquire 481 * read or write access. 482 * <p> 483 * This method suspends the calling thread until such access 484 * has been obtained. 485 * If the calling thread is interrupted while waiting to get write 486 * access, an InternalErrorException is thrown, and the thread does 487 * not have write permission to the workspace. 488 * It is essential that a call to this method is matched by a call to 489 * doneWriting(), regardless of whether this method returns normally or 490 * an exception is thrown. This is to ensure that the workspace is in a 491 * consistent state, otherwise read or write access may never again 492 * be granted in this workspace. 493 * @exception InternalErrorException If the calling thread is interrupted 494 * while waiting to get write access. 495 * @see #doneWriting() 496 */ 497 public final synchronized void getWriteAccess() { 498 // This method should throw an InterruptedException when the 499 // calling thread is interrupted. InterruptedException is a 500 // checked exception, so changing this will lead to changes 501 // everywhere this method is called, which is a huge amount 502 // of work. 503 Thread current = Thread.currentThread(); 504 505 if (current == _writer) { 506 // Already have write permission. 507 _writeDepth++; 508 return; 509 } 510 511 AccessRecord record = _lastReaderRecordOrCurrentAccessRecord(current, 512 true); 513 514 // Probably need to wait for write access. 515 // First increment this to make the record not empty, so as to 516 // prevent the record from being deleted from the _readerRecords 517 // table by other threads. 518 record.failedWriteAttempts++; 519 520 // Go into an infinite 'while (true)' loop and check if this thread 521 // can get a write access. If yes, then return, if not then perform 522 // a wait() on the workspace. 523 while (true) { 524 if (_writer == null) { 525 // There are no writers. Are there any readers? 526 if (_numReaders == 0 527 || _numReaders == 1 && record.readDepth > 0) { 528 // No readers 529 // or the only reader is the current thread 530 _writer = current; 531 _writeDepth = 1; 532 record.failedWriteAttempts--; 533 return; 534 } 535 } 536 int depth = 0; 537 try { 538 // If there is already another waiting write request, then 539 // we have to release any read permissions that we hold or a 540 // deadlock could occur. There is no need to release those 541 // read permissions if this is the only pending write 542 // request. If another write request shows up during the 543 // wait(), then in effect this first arrived write request 544 // will have priority because it holds a read permission. 545 // If the other thread also holds a read permission, it 546 // will have to release it upon issuing the write request. 547 // Thus, the subtlety described in the class comment 548 // will only occur if the second thread to attempt a 549 // write access has the problematic 550 // pattern of acquiring write requests inside of read 551 // permission blocks. This doesn't eliminate the problems 552 // associated with this subtlety, it just makes it 553 // somewhat less likely that they will occur. 554 if (_waitingWriteRequests > 0) { 555 // Bert Rodier suggests that we should throw an 556 // exception in this circumstance instead of just 557 // releasing read requests. This would have the 558 // advantage that the problematic cases cited in 559 // class comment may be identified (but only if 560 // the exception is actually thrown, which depends 561 // on an accident of thread scheduling). I would 562 // prefer to use static analysis to identify cases 563 // where write permissions are accessed within 564 // read permission blocks, and analyze those 565 // cases for correct usage. EAL 11/12/08. 566 depth = _releaseAllReadPermissions(); 567 } 568 _waitingWriteRequests++; 569 wait(); 570 } catch (InterruptedException ex) { 571 throw new InternalErrorException(this, ex, 572 current.getName() 573 + ": Thread interrupted while waiting to get " 574 + "write access: " + ex.getMessage()); 575 } finally { 576 _waitingWriteRequests--; 577 if (depth > 0) { 578 _reacquireReadPermissions(depth); 579 } 580 } 581 } 582 } 583 584 /** Handle a model error by throwing the specified exception. 585 * @param context The object in which the error occurred. 586 * @param exception An exception that represents the error. 587 * @return Never returns. 588 * @exception IllegalActionException The exception passed 589 * as an argument is always thrown. 590 * @since Ptolemy II 2.1 591 */ 592 public boolean handleModelError(NamedObj context, 593 IllegalActionException exception) throws IllegalActionException { 594 throw exception; 595 } 596 597 /** Increment the version number by one. 598 */ 599 public final synchronized void incrVersion() { 600 _version++; 601 } 602 603 /** Reacquire read permission on the workspace for 604 * the current thread. Call this after a call to 605 * releaseReadPermissions(). 606 * @param depth The depth of the permissions to reacquire. 607 * @see #releaseReadPermission() 608 */ 609 public void reacquireReadPermission(int depth) { 610 _reacquireReadPermissions(depth); 611 } 612 613 /** Release read permission on the workspace 614 * held by the current thread, and return the depth of the 615 * nested calls to getReadAccess(). It is essential that 616 * after calling this, you also call 617 * reacquireReadPermission(int), passing it as an argument 618 * the value returned by this method. Hence, you should 619 * use this as follows: 620 * <pre> 621 * int depth = releaseReadPermission(); 622 * try { 623 * ... do whatever here ... 624 * } finally { 625 * reacquireReadPermission(depth); 626 * } 627 * </pre> 628 * Note that this is done automatically by the 629 * wait(Object) method, so if you can use that instead, 630 * please do. 631 * @return The depth of read permissions held by the current 632 * thread. 633 * @see #reacquireReadPermission(int) 634 * @see #wait(Object) 635 * @see #wait(Object, long) 636 */ 637 public synchronized int releaseReadPermission() { 638 return _releaseAllReadPermissions(); 639 } 640 641 /** Remove the specified item from the directory. 642 * Note that that item will still refer to this workspace as 643 * its workspace (its workspace is immutable). If the object is 644 * not in the directory, do nothing. 645 * Increment the version number. 646 * @param item The NamedObj to be removed. 647 */ 648 public synchronized void remove(NamedObj item) { 649 _directory.remove(item); 650 incrVersion(); 651 } 652 653 /** Remove all items from the directory. 654 * Note that those items will still refer to this workspace as 655 * their workspace (their workspace is immutable). 656 * Increment the version number. 657 */ 658 public synchronized void removeAll() { 659 _directory.clear(); 660 incrVersion(); 661 } 662 663 /** Set or change the name. If a null argument is given the 664 * name is set to an empty string. 665 * Increment the version number. 666 * @param name The new name. 667 * @see #getName() 668 */ 669 @Override 670 public synchronized void setName(String name) { 671 if (name == null) { 672 name = ""; 673 } 674 675 _name = name; 676 incrVersion(); 677 } 678 679 /** Return a concise description of the object. 680 * @return The class name and name. 681 */ 682 @Override 683 public String toString() { 684 return getClass().getName() + " {" + getFullName() + "}"; 685 } 686 687 /** Release all the read accesses held by the current thread and suspend 688 * the thread by calling Object.wait() on the specified object. When the 689 * call returns, re-acquire all the read accesses held earlier by the 690 * thread and return. 691 * If the calling thread is interrupted while waiting to re-acquire read 692 * accesses, an InternalErrorException is thrown, and the thread no longer 693 * has read access to the workspace. 694 * This method helps prevent deadlocks caused when a thread that 695 * waits for another thread to do something prevents it from doing 696 * that something by holding read access on the workspace. 697 * <b>IMPORTANT</b>: The calling thread should <i>not</i> hold a lock 698 * on <i>obj</i> when calling this method, unlike a direct call to 699 * <i>obj</i>.wait(). Holding such a lock can lead to deadlock because 700 * this method can block for an indeterminate amount of time while trying 701 * to reacquire read permissions that it releases. Moreover, holding such 702 * a lock is pointless since this method internally calls 703 * <i>obj</i>.wait() (within its own synchronized(<i>obj</i>) block, 704 * so the calling method cannot assume that the lock on <i>obj</i> was 705 * held during the entire execution of this method. 706 * If the calling thread needs to hold a lock on <i>obj</i> until 707 * <i>obj</i>.wait() is called, then you should manually release 708 * read permissions and release the <i>obj</i> before reacquiring them, 709 * as follows: 710 * 711 * <pre> 712 * int depth = 0; 713 * try { 714 * synchronized(obj) { 715 * ... 716 * depth = releaseReadPermission(); 717 * obj.wait(); 718 * } 719 * } finally { 720 * if (depth > 0) { 721 * reacquireReadPermission(depth); 722 * } 723 * } 724 * </pre> 725 * 726 * @param obj The object that the thread wants to wait on. 727 * @exception InterruptedException If the calling thread is interrupted 728 * while waiting on the specified object and all the read accesses held 729 * earlier by the thread are re-acquired. 730 * @exception InternalErrorException If re-acquiring the read accesses 731 * held earlier by the thread fails. 732 */ 733 public void wait(Object obj) throws InterruptedException { 734 int depth = 0; 735 depth = _releaseAllReadPermissions(); 736 737 try { 738 synchronized (obj) { 739 obj.wait(); 740 } 741 } finally { 742 // NOTE: If obj != this, and this method is called 743 // inside a synchronized(obj) block, then the following 744 // call can lead to deadlock because it does a wait() on 745 // this workspace. It has to, since notification that will 746 // free the reacquire occurs on the workspace. But it holds 747 // a lock on obj during that wait. Meanwhile, another thread 748 // with write permission may attempt to acquire a lock on obj. 749 // Deadlock. Hence the warning in the method comment. 750 _reacquireReadPermissions(depth); 751 } 752 } 753 754 /** This method is equivalent to the single argument version except that 755 * you can specify a timeout, which is in milliseconds. If value of the 756 * timeout argument is zero, then the method is exactly equivalent 757 * to the single argument version, and no timeout is implemented. If 758 * the value is larger than zero, then the method returns if either 759 * the thread is notified by another thread or the timeout expires. 760 * <b>IMPORTANT</b>: The calling thread should <i>not</i> hold a lock 761 * on <i>obj</i> when calling this method, unlike a direct call to 762 * <i>obj</i>.wait(). Holding such a lock can lead to deadlock because 763 * this method can block for an indeterminate amount of time while trying 764 * to reacquire read permissions that it releases. Moreover, holding such 765 * a lock is pointless since this method internally calls 766 * <i>obj</i>.wait() (within its own synchronized(<i>obj</i>) block, 767 * so the calling method cannot assume that the lock on <i>obj</i> was 768 * held during the entire execution of this method. 769 * @param obj The object that the thread wants to wait on. 770 * @param timeout The maximum amount of time to wait, in milliseconds, 771 * or zero to not specify a timeout. 772 * @exception InterruptedException If the calling thread is interrupted 773 * while waiting on the specified object and all the read accesses held 774 * earlier by the thread are re-acquired. 775 * @exception InternalErrorException If re-acquiring the read accesses 776 * held earlier by the thread fails. 777 * @see #wait(Object) 778 */ 779 public void wait(Object obj, long timeout) throws InterruptedException { 780 int depth = 0; 781 depth = _releaseAllReadPermissions(); 782 783 try { 784 synchronized (obj) { 785 obj.wait(timeout); 786 } 787 } finally { 788 _reacquireReadPermissions(depth); 789 } 790 } 791 792 /////////////////////////////////////////////////////////////////// 793 //// protected methods //// 794 795 /** Return a description of the workspace. The level of detail depends 796 * on the argument, which is an or-ing of the static final constants 797 * defined in the NamedObj class. If the contents are requested, 798 * then the items in the directory are also described. 799 * Zero, one or two brackets can be specified to surround the returned 800 * description. If one is specified it is the the leading bracket. 801 * This is used by derived classes that will append to the description. 802 * Those derived classes are responsible for the closing bracket. 803 * An argument other than 0, 1, or 2 is taken to be equivalent to 0. 804 * @param detail The level of detail. 805 * @param indent The amount of indenting. 806 * @param bracket The number of surrounding brackets (0, 1, or 2). 807 * @return A description of the workspace. 808 * @exception IllegalActionException If thrown while getting the 809 * description of subcomponents. 810 */ 811 protected synchronized String _description(int detail, int indent, 812 int bracket) throws IllegalActionException { 813 StringBuffer result = new StringBuffer( 814 NamedObj._getIndentPrefix(indent)); 815 816 if (bracket == 1 || bracket == 2) { 817 result.append("{"); 818 } 819 820 if ((detail & NamedObj.CLASSNAME) != 0) { 821 result.append(getClass().getName()); 822 823 if ((detail & NamedObj.FULLNAME) != 0) { 824 result.append(" "); 825 } 826 } 827 828 if ((detail & NamedObj.FULLNAME) != 0) { 829 result.append("{" + getFullName() + "}"); 830 } 831 832 if ((detail & NamedObj.CONTENTS) != 0) { 833 if ((detail & (NamedObj.CLASSNAME | NamedObj.FULLNAME)) != 0) { 834 result.append(" "); 835 } 836 837 result.append("directory {\n"); 838 839 List<NamedObj> directoryList = directoryList(); 840 for (NamedObj obj : directoryList) { 841 842 // If deep is not set, then zero-out the contents flag 843 // for the next round. 844 if ((detail & NamedObj.DEEP) == 0) { 845 detail &= ~NamedObj.CONTENTS; 846 } 847 848 String description = obj._description(detail, indent + 1, 2) 849 + "\n"; 850 result.append(description); 851 } 852 853 result.append("}"); 854 } 855 856 if (bracket == 2) { 857 result.append("}"); 858 } 859 860 return result.toString(); 861 } 862 863 /////////////////////////////////////////////////////////////////// 864 //// private methods //// 865 866 /** Indicate that the calling thread is finished writing. 867 * If this thread is completely done writing (it has no other 868 * write access to the workspace), then notify all threads 869 * that are waiting to get read/write access to this workspace 870 * so that they may contend for access. 871 * It also increments the version number of the workspace 872 * if the incrementWorkspaceVersion parameter is true. 873 * @param incrementWorkspaceVersion True if we should increment 874 * the version. The incrementWorkspaceVersion parameter should 875 * almost always be true, if it is set to false, then perhaps a 876 * temporary variable is being added. 877 * @exception InvalidStateException If this method is called before 878 * a corresponding call to getWriteAccess() by the same thread. 879 */ 880 private final synchronized void _doneWriting( 881 boolean incrementWorkspaceVersion) { 882 Thread current = Thread.currentThread(); 883 AccessRecord record = _lastReaderRecordOrCurrentAccessRecord(current, 884 false); 885 886 if (incrementWorkspaceVersion) { 887 incrVersion(); 888 } 889 890 if (current != _writer) { 891 if (record != null && record.failedWriteAttempts > 0) { 892 record.failedWriteAttempts--; 893 } else { 894 throw new InvalidStateException(this, 895 "Workspace: doneWriting called without a prior " 896 + "matching call to getWriteAccess()."); 897 } 898 } else { 899 if (_writeDepth > 0) { 900 _writeDepth--; 901 902 if (_writeDepth == 0) { 903 _writer = null; 904 notifyAll(); 905 } 906 } else { 907 throw new InvalidStateException(this, 908 "Workspace: doneWriting called without a prior " 909 + "matching call to getWriteAccess()."); 910 } 911 } 912 } 913 914 /** If the thread is the same as the _lastReader, then return 915 * the _lastReaderRecord. Otherwise, get the AccessRecord for the thread. 916 * @param thread The thread to check against _lastReader 917 * @param createNew True if a new thread is to be created. 918 * @return The AccessRecord 919 */ 920 private final AccessRecord _lastReaderRecordOrCurrentAccessRecord( 921 Thread thread, boolean createNew) { 922 AccessRecord record = null; 923 // This is a separate method so as to avoid code duplication. 924 if (_lastReader != null && thread == _lastReader.get()) { 925 record = _lastReaderRecord; 926 } else { 927 record = _getAccessRecord(thread, createNew); 928 } 929 return record; 930 } 931 932 /** Return the AccessRecord object for the specified thread. 933 * If the flag createNew is true and the current thread does not 934 * have an access record, then create a new one and return it. 935 * Set _lastReaderRecord to be the record returned. 936 */ 937 private final AccessRecord _getAccessRecord(Thread current, 938 boolean createNew) { 939 //System.out.println("-- look up access record for " 940 // + current.getName()); 941 // If this object has been serialized and deserialized, then 942 // _readerRecords could be null. 943 if (_readerRecords == null) { 944 _readerRecords = new WeakHashMap<Thread, AccessRecord>(); 945 } 946 947 AccessRecord record = _readerRecords.get(current); 948 949 if (record == null) { 950 // delete any record that contains no history information 951 // AND is not the last reader's record 952 Iterator records = _readerRecords.values().iterator(); 953 while (records.hasNext()) { 954 AccessRecord aRecord = (AccessRecord) records.next(); 955 956 if (aRecord.failedReadAttempts == 0 957 && aRecord.failedWriteAttempts == 0 958 && aRecord.readDepth == 0 959 && aRecord != _lastReaderRecord) { 960 //System.out.println("-- delete record for thread " 961 // + aRecord.thread 962 // + " in " + current.getName()); 963 records.remove(); 964 } 965 } 966 967 if (createNew) { 968 record = new AccessRecord(); 969 _readerRecords.put(current, record); 970 } 971 } 972 973 if (record != null) { 974 _lastReader = new WeakReference<Thread>(current); 975 _lastReaderRecord = record; 976 } 977 978 return record; 979 } 980 981 // Obtain permissions to read objects in the workspace. This obtains 982 // many permissions on the read access and should be called in 983 // conjunction with _releaseAllReadPermissions. 984 // This method suspends the calling thread until such permission 985 // has been obtained. Permission is granted unless either another 986 // thread has write permission, or there are threads that 987 // have requested write permission and not gotten it yet. 988 // @param count This is the number of read permissions desired on the 989 // workspace. 990 // @exception InternalErrorException If the calling thread is interrupted 991 // while waiting to re-acquire read permissions. 992 private synchronized void _reacquireReadPermissions(int count) { 993 // If the count argument is equal to zero, which means we would like 994 // the current thread to has read depth equal to 0, i.e. not a reader, 995 // then it's already trivially done, since this method call is always 996 // preceded by _releaseAllReadPermissions. 997 if (count == 0) { 998 return; 999 } 1000 1001 Thread current = Thread.currentThread(); 1002 AccessRecord record = _lastReaderRecordOrCurrentAccessRecord(current, 1003 false); 1004 1005 if (record == null || count > record.failedReadAttempts) { 1006 throw new InvalidStateException(this, 1007 "Trying to reacquire " + "read permission not in record."); 1008 } 1009 1010 // Go into an infinite 'while (true)' loop, and each time through 1011 // the loop, check if the condition is satisfied to have the current 1012 // thread as a writer. If not, then wait on the workspace. Upon 1013 // re-awakening, iterate in the loop again to check if the condition 1014 // is now satisfied. 1015 while (true) { 1016 // If the current thread has write permission, or if there 1017 // are no pending write requests, then grant read permission. 1018 if (current == _writer 1019 || _waitingWriteRequests == 0 && _writer == null) { 1020 _numReaders++; 1021 record.failedReadAttempts -= count; 1022 record.readDepth = count; 1023 return; 1024 } 1025 1026 try { 1027 wait(); 1028 } catch (InterruptedException ex) { 1029 throw new InternalErrorException(this, ex, 1030 current.getName() 1031 + ": Thread interrupted while waiting to get " 1032 + " read access: " + ex.getMessage()); 1033 } 1034 } 1035 } 1036 1037 /** Frees the thread of all the readAccesses on the workspace 1038 * held by the current thread. The method 1039 * _reacquireAllReadAccesses should be called after this method is 1040 * called. 1041 * @return The number of readAccess that the thread possessed on the 1042 * workspace 1043 */ 1044 private synchronized int _releaseAllReadPermissions() { 1045 // Find the current thread. 1046 AccessRecord record = _lastReaderRecordOrCurrentAccessRecord( 1047 Thread.currentThread(), false); 1048 1049 if (record == null || record.readDepth == 0) { 1050 // current thread is not a reader 1051 return 0; 1052 } else { 1053 _numReaders--; 1054 notifyAll(); 1055 1056 int result = record.readDepth; 1057 record.failedReadAttempts += result; 1058 record.readDepth = 0; 1059 return result; 1060 } 1061 } 1062 1063 /////////////////////////////////////////////////////////////////// 1064 //// private variables //// 1065 1066 /** @serial List of contained objects. */ 1067 private LinkedList _directory = new LinkedList(); 1068 1069 /** @serial The name. */ 1070 private String _name; 1071 1072 /** @serial Version number. */ 1073 private long _version = 0; 1074 1075 /** @serial The currently writing thread (if any). */ 1076 private transient Thread _writer; 1077 1078 /** @serial The number of pending write requests. 1079 */ 1080 private int _waitingWriteRequests = 0; 1081 1082 /** @serial The number of active write permissions 1083 * (all to the same thread). 1084 */ 1085 private int _writeDepth = 0; 1086 1087 /** @serial The last thread that acquires/releases read permission. 1088 */ 1089 private transient WeakReference<Thread> _lastReader = null; 1090 1091 private transient AccessRecord _lastReaderRecord = null; 1092 1093 /** A WeakHashMap of threads to AccessRecords. 1094 * A WeakHashMap is used because otherwise getting the effigy 1095 * adds Manager._thread to the Map and if it is a regular 1096 * HashMap, then when the window containing the model is closed, 1097 * a reference to the Manager will remain. 1098 */ 1099 private transient WeakHashMap<Thread, AccessRecord> _readerRecords = new WeakHashMap<Thread, AccessRecord>(); 1100 1101 /** @serial The number of readers. 1102 * The use of this field is to increment it every time we have a new 1103 * reader and decrement it whenever a reader relinquishes ALL its read 1104 * access. 1105 */ 1106 private long _numReaders = 0; 1107 1108 /////////////////////////////////////////////////////////////////// 1109 //// inner classes //// 1110 1111 private static final class AccessRecord { 1112 1113 // FindBugs suggested making this class a static inner class: 1114 // 1115 // "This class is an inner class, but does not use its embedded 1116 // reference to the object which created it. This reference makes 1117 // the instances of the class larger, and may keep the reference 1118 // to the creator object alive longer than necessary. If 1119 // possible, the class should be made into a static inner class." 1120 1121 // the number of failed calls to getReadAccess() performed 1122 // by a thread and not yet matched by a call to doneReading() 1123 public int failedReadAttempts = 0; 1124 1125 // the number of failed calls to getWriteAccess() performed 1126 // by a thread and not yet matched by a call to doneWriting() 1127 public int failedWriteAttempts = 0; 1128 1129 // the number of successful calls to getReadAccess() performed 1130 // by a thread and not yet matched by a call to doneReading() 1131 public int readDepth = 0; 1132 1133 //public Thread thread = null; 1134 //public boolean inUse; 1135 } 1136}