Skip to content

Commit 869b6ea

Browse files
committed
quota: Fix slow quotaoff
Eric has reported that commit dabc8b2 ("quota: fix dqput() to follow the guarantees dquot_srcu should provide") heavily increases runtime of generic/270 xfstest for ext4 in nojournal mode. The reason for this is that ext4 in nojournal mode leaves dquots dirty until the last dqput() and thus the cleanup done in quota_release_workfn() has to write them all. Due to the way quota_release_workfn() is written this results in synchronize_srcu() call for each dirty dquot which makes the dquot cleanup when turning quotas off extremely slow. To be able to avoid synchronize_srcu() for each dirty dquot we need to rework how we track dquots to be cleaned up. Instead of keeping the last dquot reference while it is on releasing_dquots list, we drop it right away and mark the dquot with new DQ_RELEASING_B bit instead. This way we can we can remove dquot from releasing_dquots list when new reference to it is acquired and thus there's no need to call synchronize_srcu() each time we drop dq_list_lock. References: https://lore.kernel.org/all/ZRytn6CxFK2oECUt@debian-BULLSEYE-live-builder-AMD64 Reported-by: Eric Whitney <[email protected]> Fixes: dabc8b2 ("quota: fix dqput() to follow the guarantees dquot_srcu should provide") CC: [email protected] Signed-off-by: Jan Kara <[email protected]>
1 parent 8a749fd commit 869b6ea

File tree

3 files changed

+43
-29
lines changed

3 files changed

+43
-29
lines changed

fs/quota/dquot.c

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -233,19 +233,18 @@ static void put_quota_format(struct quota_format_type *fmt)
233233
* All dquots are placed to the end of inuse_list when first created, and this
234234
* list is used for invalidate operation, which must look at every dquot.
235235
*
236-
* When the last reference of a dquot will be dropped, the dquot will be
237-
* added to releasing_dquots. We'd then queue work item which would call
236+
* When the last reference of a dquot is dropped, the dquot is added to
237+
* releasing_dquots. We'll then queue work item which will call
238238
* synchronize_srcu() and after that perform the final cleanup of all the
239-
* dquots on the list. Both releasing_dquots and free_dquots use the
240-
* dq_free list_head in the dquot struct. When a dquot is removed from
241-
* releasing_dquots, a reference count is always subtracted, and if
242-
* dq_count == 0 at that point, the dquot will be added to the free_dquots.
239+
* dquots on the list. Each cleaned up dquot is moved to free_dquots list.
240+
* Both releasing_dquots and free_dquots use the dq_free list_head in the dquot
241+
* struct.
243242
*
244-
* Unused dquots (dq_count == 0) are added to the free_dquots list when freed,
245-
* and this list is searched whenever we need an available dquot. Dquots are
246-
* removed from the list as soon as they are used again, and
247-
* dqstats.free_dquots gives the number of dquots on the list. When
248-
* dquot is invalidated it's completely released from memory.
243+
* Unused and cleaned up dquots are in the free_dquots list and this list is
244+
* searched whenever we need an available dquot. Dquots are removed from the
245+
* list as soon as they are used again and dqstats.free_dquots gives the number
246+
* of dquots on the list. When dquot is invalidated it's completely released
247+
* from memory.
249248
*
250249
* Dirty dquots are added to the dqi_dirty_list of quota_info when mark
251250
* dirtied, and this list is searched when writing dirty dquots back to
@@ -321,15 +320,18 @@ static inline void put_dquot_last(struct dquot *dquot)
321320
static inline void put_releasing_dquots(struct dquot *dquot)
322321
{
323322
list_add_tail(&dquot->dq_free, &releasing_dquots);
323+
set_bit(DQ_RELEASING_B, &dquot->dq_flags);
324324
}
325325

326326
static inline void remove_free_dquot(struct dquot *dquot)
327327
{
328328
if (list_empty(&dquot->dq_free))
329329
return;
330330
list_del_init(&dquot->dq_free);
331-
if (!atomic_read(&dquot->dq_count))
331+
if (!test_bit(DQ_RELEASING_B, &dquot->dq_flags))
332332
dqstats_dec(DQST_FREE_DQUOTS);
333+
else
334+
clear_bit(DQ_RELEASING_B, &dquot->dq_flags);
333335
}
334336

335337
static inline void put_inuse(struct dquot *dquot)
@@ -581,12 +583,6 @@ static void invalidate_dquots(struct super_block *sb, int type)
581583
continue;
582584
/* Wait for dquot users */
583585
if (atomic_read(&dquot->dq_count)) {
584-
/* dquot in releasing_dquots, flush and retry */
585-
if (!list_empty(&dquot->dq_free)) {
586-
spin_unlock(&dq_list_lock);
587-
goto restart;
588-
}
589-
590586
atomic_inc(&dquot->dq_count);
591587
spin_unlock(&dq_list_lock);
592588
/*
@@ -605,6 +601,15 @@ static void invalidate_dquots(struct super_block *sb, int type)
605601
* restart. */
606602
goto restart;
607603
}
604+
/*
605+
* The last user already dropped its reference but dquot didn't
606+
* get fully cleaned up yet. Restart the scan which flushes the
607+
* work cleaning up released dquots.
608+
*/
609+
if (test_bit(DQ_RELEASING_B, &dquot->dq_flags)) {
610+
spin_unlock(&dq_list_lock);
611+
goto restart;
612+
}
608613
/*
609614
* Quota now has no users and it has been written on last
610615
* dqput()
@@ -696,6 +701,13 @@ int dquot_writeback_dquots(struct super_block *sb, int type)
696701
dq_dirty);
697702

698703
WARN_ON(!dquot_active(dquot));
704+
/* If the dquot is releasing we should not touch it */
705+
if (test_bit(DQ_RELEASING_B, &dquot->dq_flags)) {
706+
spin_unlock(&dq_list_lock);
707+
flush_delayed_work(&quota_release_work);
708+
spin_lock(&dq_list_lock);
709+
continue;
710+
}
699711

700712
/* Now we have active dquot from which someone is
701713
* holding reference so we can safely just increase
@@ -809,18 +821,18 @@ static void quota_release_workfn(struct work_struct *work)
809821
/* Exchange the list head to avoid livelock. */
810822
list_replace_init(&releasing_dquots, &rls_head);
811823
spin_unlock(&dq_list_lock);
824+
synchronize_srcu(&dquot_srcu);
812825

813826
restart:
814-
synchronize_srcu(&dquot_srcu);
815827
spin_lock(&dq_list_lock);
816828
while (!list_empty(&rls_head)) {
817829
dquot = list_first_entry(&rls_head, struct dquot, dq_free);
818-
/* Dquot got used again? */
819-
if (atomic_read(&dquot->dq_count) > 1) {
820-
remove_free_dquot(dquot);
821-
atomic_dec(&dquot->dq_count);
822-
continue;
823-
}
830+
WARN_ON_ONCE(atomic_read(&dquot->dq_count));
831+
/*
832+
* Note that DQ_RELEASING_B protects us from racing with
833+
* invalidate_dquots() calls so we are safe to work with the
834+
* dquot even after we drop dq_list_lock.
835+
*/
824836
if (dquot_dirty(dquot)) {
825837
spin_unlock(&dq_list_lock);
826838
/* Commit dquot before releasing */
@@ -834,7 +846,6 @@ static void quota_release_workfn(struct work_struct *work)
834846
}
835847
/* Dquot is inactive and clean, now move it to free list */
836848
remove_free_dquot(dquot);
837-
atomic_dec(&dquot->dq_count);
838849
put_dquot_last(dquot);
839850
}
840851
spin_unlock(&dq_list_lock);
@@ -875,6 +886,7 @@ void dqput(struct dquot *dquot)
875886
BUG_ON(!list_empty(&dquot->dq_free));
876887
#endif
877888
put_releasing_dquots(dquot);
889+
atomic_dec(&dquot->dq_count);
878890
spin_unlock(&dq_list_lock);
879891
queue_delayed_work(system_unbound_wq, &quota_release_work, 1);
880892
}
@@ -963,7 +975,7 @@ struct dquot *dqget(struct super_block *sb, struct kqid qid)
963975
dqstats_inc(DQST_LOOKUPS);
964976
}
965977
/* Wait for dq_lock - after this we know that either dquot_release() is
966-
* already finished or it will be canceled due to dq_count > 1 test */
978+
* already finished or it will be canceled due to dq_count > 0 test */
967979
wait_on_dquot(dquot);
968980
/* Read the dquot / allocate space in quota file */
969981
if (!dquot_active(dquot)) {

include/linux/quota.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,9 @@ static inline void dqstats_dec(unsigned int type)
285285
#define DQ_FAKE_B 3 /* no limits only usage */
286286
#define DQ_READ_B 4 /* dquot was read into memory */
287287
#define DQ_ACTIVE_B 5 /* dquot is active (dquot_release not called) */
288-
#define DQ_LASTSET_B 6 /* Following 6 bits (see QIF_) are reserved\
288+
#define DQ_RELEASING_B 6 /* dquot is in releasing_dquots list waiting
289+
* to be cleaned up */
290+
#define DQ_LASTSET_B 7 /* Following 6 bits (see QIF_) are reserved\
289291
* for the mask of entries set via SETQUOTA\
290292
* quotactl. They are set under dq_data_lock\
291293
* and the quota format handling dquot can\

include/linux/quotaops.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ static inline bool dquot_is_busy(struct dquot *dquot)
5757
{
5858
if (test_bit(DQ_MOD_B, &dquot->dq_flags))
5959
return true;
60-
if (atomic_read(&dquot->dq_count) > 1)
60+
if (atomic_read(&dquot->dq_count) > 0)
6161
return true;
6262
return false;
6363
}

0 commit comments

Comments
 (0)