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);