Print this page
XXXXX tmpfs can be induced to deadlock
        
*** 53,62 ****
--- 53,67 ----
  
  
  #define T_HASH_SIZE     8192            /* must be power of 2 */
  #define T_MUTEX_SIZE    64
  
+ /* Non-static so compilers won't constant-fold these away. */
+ clock_t tmpfs_rename_backoff_delay = 1;
+ unsigned int tmpfs_rename_backoff_tries = 0;
+ unsigned long tmpfs_rename_loops = 0;
+ 
  static struct tdirent   *t_hashtable[T_HASH_SIZE];
  static kmutex_t          t_hashmutex[T_MUTEX_SIZE];
  
  #define T_HASH_INDEX(a)         ((a) & (T_HASH_SIZE-1))
  #define T_MUTEX_INDEX(a)        ((a) & (T_MUTEX_SIZE-1))
*** 265,276 ****
          /*
           * For link and rename lock the source entry and check the link count
           * to see if it has been removed while it was unlocked.
           */
          if (op == DE_LINK || op == DE_RENAME) {
!                 if (tp != dir)
!                         rw_enter(&tp->tn_rwlock, RW_WRITER);
                  mutex_enter(&tp->tn_tlock);
                  if (tp->tn_nlink == 0) {
                          mutex_exit(&tp->tn_tlock);
                          if (tp != dir)
                                  rw_exit(&tp->tn_rwlock);
--- 270,338 ----
          /*
           * For link and rename lock the source entry and check the link count
           * to see if it has been removed while it was unlocked.
           */
          if (op == DE_LINK || op == DE_RENAME) {
!                 if (tp != dir) {
!                         unsigned int tries = 0;
! 
!                         /*
!                          * If we are acquiring tp->tn_rwlock (for SOURCE)
!                          * inside here, we must consider the following:
!                          *
!                          * - dir->tn_rwlock (TARGET) is already HELD (see
!                          * above ASSERT()).
!                          *
!                          * - It is possible our SOURCE is a parent of our
!                          * TARGET. Yes it's unusual, but it will return an
!                          * error below via tdircheckpath().
!                          *
!                          * - It is also possible that another thread,
!                          * concurrent to this one, is performing
!                          * rmdir(TARGET), which means it will first acquire
!                          * SOURCE's lock, THEN acquire TARGET's lock, which
!                          * could result in this thread holding TARGET and
!                          * trying for SOURCE, but the other thread holding
!                          * SOURCE and trying for TARGET.  This is deadlock,
!                          * and it's inducible.
!                          *
!                          * To prevent this, we borrow some techniques from UFS
!                          * and rw_tryenter(), delaying if we fail, and
!                          * if someone tweaks the number of backoff tries to be
!                          * nonzero, return EBUSY after that number of tries.
!                          */
!                         while (!rw_tryenter(&tp->tn_rwlock, RW_WRITER)) {
!                                 /*
!                                  * Sloppy, but this is a diagnostic so atomic
!                                  * increment would be overkill.
!                                  */
!                                 tmpfs_rename_loops++;
! 
!                                 if (tmpfs_rename_backoff_tries != 0) {
!                                         if (tries > tmpfs_rename_backoff_tries)
!                                                 return (EBUSY);
!                                         tries++;
!                                 }
!                                 /*
!                                  * NOTE: We're still holding dir->tn_rwlock,
!                                  * so drop it over the delay, so any other
!                                  * thread can get its business done.
!                                  *
!                                  * No state change or state inspection happens
!                                  * prior to here, so it is not wholly dangerous
!                                  * to release-and-reacquire dir->tn_rwlock.
!                                  *
!                                  * Hold the vnode of dir in case it gets
!                                  * released by another thread, though.
!                                  */
!                                 VN_HOLD(TNTOV(dir));
!                                 rw_exit(&dir->tn_rwlock);
!                                 delay(tmpfs_rename_backoff_delay);
!                                 rw_enter(&dir->tn_rwlock, RW_WRITER);
!                                 VN_RELE(TNTOV(dir));
!                         }
!                 }
                  mutex_enter(&tp->tn_tlock);
                  if (tp->tn_nlink == 0) {
                          mutex_exit(&tp->tn_tlock);
                          if (tp != dir)
                                  rw_exit(&tp->tn_rwlock);