• Filipe Manana's avatar
    Btrfs: fix deadlock when enabling quotas due to concurrent snapshot creation · 9a6f209e
    Filipe Manana authored
    If the quota enable and snapshot creation ioctls are called concurrently
    we can get into a deadlock where the task enabling quotas will deadlock
    on the fs_info->qgroup_ioctl_lock mutex because it attempts to lock it
    twice, or the task creating a snapshot tries to commit the transaction
    while the task enabling quota waits for the former task to commit the
    transaction while holding the mutex. The following time diagrams show how
    both cases happen.
    
    First scenario:
    
               CPU 0                                    CPU 1
    
     btrfs_ioctl()
      btrfs_ioctl_quota_ctl()
       btrfs_quota_enable()
        mutex_lock(fs_info->qgroup_ioctl_lock)
        btrfs_start_transaction()
    
                                                 btrfs_ioctl()
                                                  btrfs_ioctl_snap_create_v2
                                                   create_snapshot()
                                                    --> adds snapshot to the
                                                        list pending_snapshots
                                                        of the current
                                                        transaction
    
        btrfs_commit_transaction()
         create_pending_snapshots()
           create_pending_snapshot()
            qgroup_account_snapshot()
             btrfs_qgroup_inherit()
    	   mutex_lock(fs_info->qgroup_ioctl_lock)
    	    --> deadlock, mutex already locked
    	        by this task at
    		btrfs_quota_enable()
    
    Second scenario:
    
               CPU 0                                    CPU 1
    
     btrfs_ioctl()
      btrfs_ioctl_quota_ctl()
       btrfs_quota_enable()
        mutex_lock(fs_info->qgroup_ioctl_lock)
        btrfs_start_transaction()
    
                                                 btrfs_ioctl()
                                                  btrfs_ioctl_snap_create_v2
                                                   create_snapshot()
                                                    --> adds snapshot to the
                                                        list pending_snapshots
                                                        of the current
                                                        transaction
    
                                                    btrfs_commit_transaction()
                                                     --> waits for task at
                                                         CPU 0 to release
                                                         its transaction
                                                         handle
    
        btrfs_commit_transaction()
         --> sees another task started
             the transaction commit first
         --> releases its transaction
             handle
         --> waits for the transaction
             commit to be completed by
             the task at CPU 1
    
                                                     create_pending_snapshot()
                                                      qgroup_account_snapshot()
                                                       btrfs_qgroup_inherit()
                                                        mutex_lock(fs_info->qgroup_ioctl_lock)
                                                         --> deadlock, task at CPU 0
                                                             has the mutex locked but
                                                             it is waiting for us to
                                                             finish the transaction
                                                             commit
    
    So fix this by setting the quota enabled flag in fs_info after committing
    the transaction at btrfs_quota_enable(). This ends up serializing quota
    enable and snapshot creation as if the snapshot creation happened just
    before the quota enable request. The quota rescan task, scheduled after
    committing the transaction in btrfs_quote_enable(), will do the accounting.
    
    Fixes: 6426c7ad
    
     ("btrfs: qgroup: Fix qgroup accounting when creating snapshot")
    Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
    Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
    9a6f209e