Use atomic_t and wait_event to track dquot pincount
PATCH 1/1 Use atomic_t and wait_event to track dquot pincount
During a heavy workload with quota's enabled the system can stall with every process requesting log space, however the log is full of dquots. The aild is trying to push the tail of the log however every item in the log had previously been pushed by the aild and was marked as clean so the aild aborts pushing the items in the log.
The reason the log items are still in the log is because the iodone routine xfs_qm_dqflush_done detected that the log sequence number(lsn) had changed and assumed the log item had been re-dirtied so it should be left in the log. However the log item had not been re-dirtied so it was still marked as clean preventing it from being pushed again causing the log item to be stuck in the log. After a while the log eventually filled with these dquot log items.
The reason the lsn had changed was due to it not being initialized by the time a copy of the lsn was taken in xfs_qm_dqflush(). The lsn was then latter updated causing the test in xfs_qm_dqflush_done() to fail.
Synchronizations between the 2 functions is done by the pincount in struct xfs_dquot_t and xfs_qm_dqflush() calls xfs_qm_dqunpin_wait() to wait for the pincount == 0. However after been woken up it does not validate the pincount is actually 0, allowing a false wake up by the scheduler to cause xfs_qm_dqflush() to prematurely start processing the dquot.
So this patch uses an atomic_t to track the pincount which allows us to easily use the wait_event macro to wait, this will guarantee that when we return from xfs_qm_dqunpin_wait() that the pincount == 0. We also remove the global qi_pinlock from xfs_quotainfo which may also reduce contention when pinning dquot's.
fs/xfs/quota/xfs_dquot_item.c 2008-09-24 14:22:01.643627312 +1000 renamed from fs/xfs/quota/xfs_dquot_item.c 2008-09-24 12:02:41.987960702 +1000 +1 -3
@@ -98,9 +98,7 @@ xfs_qm_dquot_logitem_pin( dqp = logitem->qli_dquot; ASSERT(XFS_DQ_IS_LOCKED(dqp)); - spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); - dqp->q_pincount++; - spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); + atomic_inc(&dqp->q_pincount);
}
/*
@@ -117,13 +115,9 @@ xfs_qm_dquot_logitem_unpin(
xfs_dquot_t *dqp;
dqp = logitem->qli_dquot;
- ASSERT(dqp->q_pincount > 0);
- spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
- dqp->q_pincount--;
- if (dqp->q_pincount == 0) {
- sv_broadcast(&dqp->q_pinwait);
- }
- spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
+ ASSERT(atomic_read(&dqp->q_pincount) > 0);
+ if (atomic_dec_and_test(&dqp->q_pincount))+ wake_up(&dqp->q_pinwait);
}
/* ARGSUSED */
@@ -193,21 +187,14 @@ xfs_qm_dqunpin_wait(
xfs_dquot_t *dqp)
{
ASSERT(XFS_DQ_IS_LOCKED(dqp));
- if (dqp->q_pincount == 0) {
+ if (atomic_read(&dqp->q_pincount) == 0)
return;- }
/*
* Give the log a push so we don't wait here too long.
*/
xfs_log_force(dqp->q_mount, (xfs_lsn_t)0, XFS_LOG_FORCE);- spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
- if (dqp->q_pincount == 0) {
- spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
- return;
- }
- sv_wait(&(dqp->q_pinwait), PINOD,
- &(XFS_DQ_TO_QINF(dqp)->qi_pinlock), s);
+ wait_event(dqp->q_pinwait, (atomic_read(&dqp->q_pincount) == 0));
}
/*
@@ -310,7 +297,7 @@ xfs_qm_dquot_logitem_trylock(
uint retval;
dqp = qip->qli_dquot;- if (dqp->q_pincount > 0)
+ if (atomic_read(&dqp->q_pincount) > 0)
return (XFS_ITEM_PINNED);
if (! xfs_qm_dqlock_nowait(dqp))
fs/xfs/quota/xfs_dquot.h 2008-09-24 14:20:28.979820915 +1000 renamed from fs/xfs/quota/xfs_dquot.h 2008-09-24 12:02:41.991960179 +1000 +2 -2
@@ -83,8 +83,8 @@ typedef struct xfs_dquot { xfs_qcnt_t q_res_rtbcount;/* total realtime blks used+reserved */ mutex_t q_qlock; /* quota lock */ struct completion q_flush; /* flush completion queue */ - uint q_pincount; /* pin count for this dquot */ - sv_t q_pinwait; /* sync var for pinning */ + atomic_t q_pincount; /* dquot pin count */ + wait_queue_head_t q_pinwait; /* dquot pinning wait queue */
#ifdef XFS_DQUOT_TRACE
struct ktrace *q_trace; /* trace header structure */
#endif
fs/xfs/quota/xfs_dquot.c 2008-09-24 14:20:56.948140364 +1000 renamed from fs/xfs/quota/xfs_dquot.c 2008-09-24 12:09:14.025200071 +1000 +3 -3
@@ -101,7 +101,7 @@ xfs_qm_dqinit( if (brandnewdquot) { dqp->dq_flnext = dqp->dq_flprev = dqp; mutex_init(&dqp->q_qlock); - sv_init(&dqp->q_pinwait, SV_DEFAULT, "pdq"); + init_waitqueue_head(&dqp->q_pinwait); /* * Because we want to use a counting completion, complete
@@ -131,7 +131,7 @@ xfs_qm_dqinit( dqp->q_res_bcount = 0; dqp->q_res_icount = 0; dqp->q_res_rtbcount = 0; - dqp->q_pincount = 0; + atomic_set(&dqp->q_pincount, 0); dqp->q_hash = NULL; ASSERT(dqp->dq_flnext == dqp->dq_flprev);
@@ -1490,7 +1490,7 @@ xfs_qm_dqpurge( "xfs_qm_dqpurge: dquot %p flush failed", dqp); xfs_dqflock(dqp); } - ASSERT(dqp->q_pincount == 0); + ASSERT(atomic_read(&dqp->q_pincount) == 0); ASSERT(XFS_FORCED_SHUTDOWN(mp) || !(dqp->q_logitem.qli_item.li_flags & XFS_LI_IN_AIL));
fs/xfs/xfsidbg.c 2008-09-24 14:21:17.413447303 +1000 renamed from fs/xfs/xfsidbg.c 2008-09-24 12:02:42.115943985 +1000 +1 -1
@@ -6577,7 +6577,7 @@ xfsidbg_xqm_dquot(xfs_dquot_t *dqp) (unsigned long long)dqp->q_res_rtbcount); kdb_printf("qlock 0x%p &q_flush 0x%p (%d) pincount 0x%x\n", &dqp->q_qlock, &dqp->q_flush, - dqp->q_flush.done, dqp->q_pincount); + dqp->q_flush.done, atomic_read(&dqp->q_pincount));
#ifdef XFS_DQUOT_TRACE
qprintf("dqtrace 0x%p\n", dqp->q_trace);
#endif
@@ -6761,10 +6761,9 @@ xfsidbg_xqm_qinfo(xfs_mount_t *mp)
return;
}- kdb_printf("uqip 0x%p, gqip 0x%p, &pinlock 0x%p &dqlist 0x%p\n",
+ kdb_printf("uqip 0x%p, gqip 0x%p, &dqlist 0x%p\n",
mp->m_quotainfo->qi_uquotaip,
mp->m_quotainfo->qi_gquotaip,
- &mp->m_quotainfo->qi_pinlock,
&mp->m_quotainfo->qi_dqlist);
kdb_printf("btmlimit 0x%x, itmlimit 0x%x, RTbtmlim 0x%x\n",fs/xfs/quota/xfs_qm.h 2008-09-24 14:00:36.168448681 +1000 renamed from fs/xfs/quota/xfs_qm.h 2008-09-24 12:02:41.999959135 +1000 +0 -0
@@ -106,7 +106,6 @@ typedef struct xfs_qm {
typedef struct xfs_quotainfo {
xfs_inode_t *qi_uquotaip; /* user quota inode */
xfs_inode_t *qi_gquotaip; /* group quota inode */- spinlock_t qi_pinlock; /* dquot pinning lock */
xfs_dqlist_t qi_dqlist; /* all dquots in filesys */
int qi_dqreclaims; /* a change here indicates
a removal in the dqlist */
fs/xfs/quota/xfs_qm.c 2008-09-24 14:03:44.795703839 +1000 renamed from fs/xfs/quota/xfs_qm.c 2008-09-24 12:02:42.000000000 +1000 +0 -2
@@ -1137,7 +1137,6 @@ xfs_qm_init_quotainfo( return error; } - spin_lock_init(&qinf->qi_pinlock); xfs_qm_list_init(&qinf->qi_dqlist, "mpdqlist", 0); qinf->qi_dqreclaims = 0;
@@ -1234,7 +1233,6 @@ xfs_qm_destroy_quotainfo( */ xfs_qm_rele_quotafs_ref(mp); - spinlock_destroy(&qi->qi_pinlock); xfs_qm_list_destroy(&qi->qi_dqlist); if (qi->qi_uquotaip) {
[ Pete, please wrap your text at 68-72 columns]
Quoted 9 lines · Peter Leckie
The reason the lsn had changed was due to it not being initialized by the time a copy of the lsn was taken in
xfs_qm_dqflush(). The lsn was then latter updated causing the test inxfs_qm_dqflush_done()to fail.Synchronizations between the 2 functions is done by the pincount in struct
xfs_dquot_tandxfs_qm_dqflush()callsxfs_qm_dqunpin_wait()to wait for the pincount == 0. However after been woken up it does not validate the pincount is actually 0,
Sure - that's because we only ever send a wakeup when the pin count falls to zero. Because we have to be holding the dquot lock when we either pin a dquot or wait for it to be unpinned, the act of waiting for it to be unpinned with the dquot lock held guarantees that it is not pinned when we wake up.
IOWs, the pin count cannot be incremented while we are waiting for it to be unpinned, and hence it must be zero when we are woken......
Quoted 2 lines
allowing a false wake up by the scheduler to cause
xfs_qm_dqflush()to prematurely start processing the dquot.
.... which means I can't see how that would happen...
What am I missing here?
Quoted 5 lines
So this patch uses an
atomic_tto track the pincount which allows us to easily use thewait_eventmacro to wait, this will guarantee that when we return fromxfs_qm_dqunpin_wait()that the pincount == 0. We also remove the globalqi_pinlockfromxfs_quotainfowhich may also reduce contention when pinning dquot's.
I have an patch series that I've been running under test for the past two months that does exactly this - it's an optimisation, not a bug fix. I was actually planning on posting it this afternoon.
As to the patch, your mailer has whitespace damaged it so you need to be fix that up.
Quoted 7 lines
Index:
2.6.x-xfs/fs/xfs/quota/xfs_dquot_item.c
===================================================================
---2.6.x-xfs.orig/fs/xfs/quota/xfs_dquot_item.c2008-09-24
12:02:41.987960702 +1000
+++2.6.x-xfs/fs/xfs/quota/xfs_dquot_item.c2008-09-24
14:22:01.643627312 +1000
fs/xfs/quota/xfs_dquot_item.c 2008-09-24 14:22:01.643627312 +1000 xfs_qm_dquot_logitem_pin( · line 102
Quoted 8 lines
fs/xfs/quota/xfs_dquot_item.c 2008-09-24 14:22:01.643627312 +1000 renamed from fs/xfs/quota/xfs_dquot_item.c 2008-09-24 12:02:41.987960702 +1000 +1
-3@@ -98,9 +98,7 @@ xfs_qm_dquot_logitem_pin( dqp = logitem->qli_dquot; ASSERT(XFS_DQ_IS_LOCKED(dqp)); - spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); - dqp->q_pincount++; - spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); + atomic_inc(&dqp->q_pincount); }
The header comment on this function needs updating - it references the spinlock that just got removed. You can also do
xfs_dquot_t *dqp = logitem->qli_dquot;
Quoted 5 lines
(quoted diff) +0
-1@@ -117,13 +115,9 @@ xfs_qm_dquot_logitem_unpin( xfs_dquot_t *dqp; dqp = logitem->qli_dquot; - ASSERT(dqp->q_pincount > 0);
Review xfs_qm_dquot_logitem_unpin( · line 121
Quoted 10 lines
(quoted diff) +3
-6- spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); - dqp->q_pincount--; - if (dqp->q_pincount == 0) { - sv_broadcast(&dqp->q_pinwait); - } - spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); + ASSERT(atomic_read(&dqp->q_pincount) > 0); + if (atomic_dec_and_test(&dqp->q_pincount)) + wake_up(&dqp->q_pinwait); }
The header comment for this function references functions that don't exist. Needs updating. same again about logitem->qli_dquot...
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 2 lines · Dave Chinner
[ Pete, please wrap your text at 68-72 columns]
Ok will do in future
Quoted 23 lines · Peter Leckie
Quoted 11 lines
The reason the lsn had changed was due to it not being initialized by the time a copy of the lsn was taken in
xfs_qm_dqflush(). The lsn was then latter updated causing the test inxfs_qm_dqflush_done()to fail.Synchronizations between the 2 functions is done by the pincount in struct
xfs_dquot_tandxfs_qm_dqflush()callsxfs_qm_dqunpin_wait()to wait for the pincount == 0. However after been woken up it does not validate the pincount is actually 0,Sure - that's because we only ever send a wakeup when the pin count falls to zero. Because we have to be holding the dquot lock when we either pin a dquot or wait for it to be unpinned, the act of waiting for it to be unpinned with the dquot lock held guarantees that it is not pinned when we wake up.
IOWs, the pin count cannot be incremented while we are waiting for it to be unpinned, and hence it must be zero when we are woken......
Quoted 8 lines
Quoted 3 lines
allowing a false wake up by the scheduler to cause
xfs_qm_dqflush()to prematurely start processing the dquot..... which means I can't see how that would happen...
What am I missing here?
Yeah it's quite intriguing isn't it, I added the pincount to the dquot tracing code and sure enough it's 1 all the way through xfs_qm_dqflush() I can tell you that none of the XFS code is causing it to wake up. I was going to add some tracing code to the scheduler to determine who is causing us to wake up however I had other priorities to work on.
Reading the code it appears things like a threads dying or a system suspending can cause it. However none of these had happened, after reading other examples in the Linux kernel it appeared pretty standard to always re-evaluate the condition after being woken so I suspect that Linux simply does not guarantee that we will only be woken by the thread calling wake_up on us.
Any insight into this would be much appreciated as I'm also very curious as to why this is happening.
Quoted 3 lines
As to the patch, your mailer has whitespace damaged it so you need to be fix that up.
Ahh yeah copy and past from a terminal, I'll fix up the patch and re-send shortly,
Thanks Dave.
Pete
Quoted 31 lines · Peter Leckie
Quoted 2 lines · Dave Chinner
[ Pete, please wrap your text at 68-72 columns]
Ok will do in future
Quoted 27 lines · Peter Leckie
Quoted 11 lines
The reason the lsn had changed was due to it not being initialized by the time a copy of the lsn was taken in
xfs_qm_dqflush(). The lsn was then latter updated causing the test inxfs_qm_dqflush_done()to fail.Synchronizations between the 2 functions is done by the pincount in struct
xfs_dquot_tandxfs_qm_dqflush()callsxfs_qm_dqunpin_wait()to wait for the pincount == 0. However after been woken up it does not validate the pincount is actually 0,Sure - that's because we only ever send a wakeup when the pin count falls to zero. Because we have to be holding the dquot lock when we either pin a dquot or wait for it to be unpinned, the act of waiting for it to be unpinned with the dquot lock held guarantees that it is not pinned when we wake up.
IOWs, the pin count cannot be incremented while we are waiting for it to be unpinned, and hence it must be zero when we are woken......
Quoted 3 lines
allowing a false wake up by the scheduler to cause
xfs_qm_dqflush()to prematurely start processing the dquot.
Quoted 8 lines
Quoted 4 lines
.... which means I can't see how that would happen...
What am I missing here?
Yeah it's quite intriguing isn't it, I added the pincount to the dquot tracing code and sure enough it's 1 all the way through
xfs_qm_dqflush()I can tell you that none of the XFS code is causing it to wake up.
Only the XFS code can cause it to be woken....
Quoted 6 lines
I was going to add some tracing code to
the scheduler to determine who is causing us to wake up however I had other priorities to work on.Reading the code it appears things like a threads dying or a system suspending can cause it.
Wait queues are not affected by such events when they are configured to be uninterruptable. The sv_t:
#define sv_wait(sv, pri, lock, s) \
_sv_wait(sv, lock, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT)
Is as uninterruptible as it comes. Which means only an explicit wakeup will cause any waiters to wake up.
Quoted 6 lines
However none of these had
happened, after reading other examples in the Linux kernel it appeared pretty standard to always re-evaluate the condition after being woken so I suspect that Linux simply does not guarantee that we will only be woken by the thread callingwake_upon us.
Not true - wake_up() will only wake tasks on the wait queue it was called for.
Quoted 2 lines
Any insight into this would be much appreciated as I'm also very curious as to why this is happening.
Assert fail the machine when a dquot with a non-zero pincount is woken in xfs_qm_dqunpin_wait(). If the assert trips, then we need to work out how the pin count is getting elevated while we have a waiter....
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
PATCH v2 1/1 Use atomic_t and wait_event to track dquot pincount
In reply to Peter LeckieDuring a heavy workload with quota's enabled the system can stall
with every process requesting log space, however the log is full of dquots.The aild is trying to push the tail of the log however every item in the log had previously been pushed by the aild and was marked as clean so the aild aborts pushing the items in the log.
The reason the log items are still in the log is because the iodone routine xfs_qm_dqflush_done detected that the log sequence number(lsn) had changed and assumed the log item had been re-dirtied so it should be left in the log. However the log item had not been re-dirtied so it was still marked as clean preventing it from being pushed again causing the log item to be stuck in the log. After a while the log eventually filled with these dquot log items.
The reason the lsn had changed was due to it not being initialized by the time a copy of the lsn was taken in xfs_qm_dqflush(). The lsn was then latter updated causing the test in xfs_qm_dqflush_done() to fail.
Synchronizations between the 2 functions is done by the pincount in struct xfs_dquot_t and xfs_qm_dqflush() calls xfs_qm_dqunpin_wait() to wait for the pincount == 0. However after been woken up it does not validate the pincount is actually 0, allowing a false wake up by the scheduler to cause xfs_qm_dqflush() to prematurely start processing the dquot.
So this patch uses an atomic_t to track the pincount which allows us to easily use the wait_event macro to wait, this will guarantee that when we return from xfs_qm_dqunpin_wait() that the pincount == 0. We also remove the global qi_pinlock from xfs_quotainfo which may also reduce contention when pinning dquot's.
fs/xfs/quota/xfs_dquot_item.c +0 -0
@@ -88,25 +88,22 @@ xfs_qm_dquot_logitem_format(
/*
* Increment the pin count of the given dquot.
- * This value is protected by pinlock spinlock in the xQM structure. */
STATIC void
xfs_qm_dquot_logitem_pin(
xfs_dq_logitem_t *logitem)
{
- xfs_dquot_t *dqp;
+ xfs_dquot_t *dqp = logitem->qli_dquot;
- dqp = logitem->qli_dquot; ASSERT(XFS_DQ_IS_LOCKED(dqp));
- spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
- dqp->q_pincount++;
- spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
+ atomic_inc(dqp->q_pincount);
}
/*
* Decrement the pin count of the given dquot, and wake up
* anyone in xfs_dqwait_unpin() if the count goes to 0. The
- * dquot must have been previously pinned with a call to xfs_dqpin().
+ * dquot must have been previously pinned with a call to
+ * xfs_qm_dquot_logitem_pin().
*/
/* ARGSUSED */
STATIC void
@@ -114,16 +111,11 @@ xfs_qm_dquot_logitem_unpin(
xfs_dq_logitem_t *logitem,
int stale)
{
- xfs_dquot_t *dqp;
+ xfs_dquot_t *dqp = logitem->qli_dquot;
- dqp = logitem->qli_dquot;
- ASSERT(dqp->q_pincount > 0);
- spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
- dqp->q_pincount--;
- if (dqp->q_pincount == 0) {
- sv_broadcast(&dqp->q_pinwait);
- }
- spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
+ ASSERT(atomic_read(&dqp->q_pincount) > 0);
+ if (atomic_dec_and_test(&dqp->q_pincount))
+ wake_up(&dqp->q_pinwait);
}
/* ARGSUSED */
@@ -193,21 +185,14 @@ xfs_qm_dqunpin_wait(
xfs_dquot_t *dqp)
{
ASSERT(XFS_DQ_IS_LOCKED(dqp));
- if (dqp->q_pincount == 0) {
+ if (atomic_read(&dqp->q_pincount) == 0)
return;- }
/* * Give the log a push so we don't wait here too long. */ xfs_log_force(dqp->q_mount, (xfs_lsn_t)0, XFS_LOG_FORCE);
- spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
- if (dqp->q_pincount == 0) {
- spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock));
- return;
- }
- sv_wait(&(dqp->q_pinwait), PINOD,
- &(XFS_DQ_TO_QINF(dqp)->qi_pinlock), s);
+ wait_event(dqp->q_pinwait, (atomic_read(&dqp->q_pincount) == 0));
}
/*
@@ -310,7 +295,7 @@ xfs_qm_dquot_logitem_trylock(
uint retval; dqp = qip->qli_dquot; - if (dqp->q_pincount > 0) + if (atomic_read(&dqp->q_pincount) > 0) return (XFS_ITEM_PINNED); if (! xfs_qm_dqlock_nowait(dqp))
fs/xfs/quota/xfs_dquot.h +0 -0
@@ -83,8 +83,8 @@ typedef struct xfs_dquot {
xfs_qcnt_t q_res_rtbcount;/* total realtime blks used+reserved */
mutex_t q_qlock; /* quota lock */
struct completion q_flush; /* flush completion queue */
- uint q_pincount; /* pin count for this dquot */
- sv_t q_pinwait; /* sync var for pinning */
+ atomic_t q_pincount; /* dquot pin count */
+ wait_queue_head_t q_pinwait; /* dquot pinning wait queue */
#ifdef XFS_DQUOT_TRACE
struct ktrace *q_trace; /* trace header structure */
#endif
fs/xfs/quota/xfs_dquot.c +0 -0
@@ -101,7 +101,7 @@ xfs_qm_dqinit(
if (brandnewdquot) {
dqp->dq_flnext = dqp->dq_flprev = dqp;
mutex_init(&dqp->q_qlock);- sv_init(&dqp->q_pinwait, SV_DEFAULT, "pdq");
+ init_waitqueue_head(&dqp->q_pinwait);
/* * Because we want to use a counting completion, complete @@ -131,7 +131,7 @@ xfs_qm_dqinit( dqp->q_res_bcount = 0; dqp->q_res_icount = 0; dqp->q_res_rtbcount = 0; - dqp->q_pincount = 0; + atomic_set(&dqp->q_pincount, 0); dqp->q_hash = NULL; ASSERT(dqp->dq_flnext == dqp->dq_flprev);
@@ -1489,7 +1489,7 @@ xfs_qm_dqpurge(
"xfs_qm_dqpurge: dquot %p flush failed", dqp); xfs_dqflock(dqp); } - ASSERT(dqp->q_pincount == 0); + ASSERT(atomic_read(&dqp->q_pincount) == 0); ASSERT(XFS_FORCED_SHUTDOWN(mp) || !(dqp->q_logitem.qli_item.li_flags & XFS_LI_IN_AIL));
fs/xfs/xfsidbg.c +0 -0
@@ -6577,7 +6577,7 @@ xfsidbg_xqm_dquot(xfs_dquot_t *dqp)
(unsigned long long)dqp->q_res_rtbcount);
kdb_printf("qlock 0x%p &q_flush 0x%p (%d) pincount 0x%x\n",
&dqp->q_qlock, &dqp->q_flush,
- dqp->q_flush.done, dqp->q_pincount);
+ dqp->q_flush.done, atomic_read(&dqp->q_pincount));
#ifdef XFS_DQUOT_TRACE
qprintf("dqtrace 0x%p\n", dqp->q_trace);
#endif
@@ -6761,10 +6761,9 @@ xfsidbg_xqm_qinfo(xfs_mount_t *mp)
return;
}- kdb_printf("uqip 0x%p, gqip 0x%p, &pinlock 0x%p &dqlist 0x%p\n",
+ kdb_printf("uqip 0x%p, gqip 0x%p, &dqlist 0x%p\n",
mp->m_quotainfo->qi_uquotaip,
mp->m_quotainfo->qi_gquotaip,
- &mp->m_quotainfo->qi_pinlock,
&mp->m_quotainfo->qi_dqlist);
kdb_printf("btmlimit 0x%x, itmlimit 0x%x, RTbtmlim 0x%x\n",fs/xfs/quota/xfs_qm.h +0 -0
@@ -106,7 +106,6 @@ typedef struct xfs_qm {
typedef struct xfs_quotainfo {
xfs_inode_t *qi_uquotaip; /* user quota inode */
xfs_inode_t *qi_gquotaip; /* group quota inode */
- spinlock_t qi_pinlock; /* dquot pinning lock */
xfs_dqlist_t qi_dqlist; /* all dquots in filesys */
int qi_dqreclaims; /* a change here indicates
a removal in the dqlist */
fs/xfs/quota/xfs_qm.c +0 -0
@@ -1137,7 +1137,6 @@ xfs_qm_init_quotainfo(
return error; }
- spin_lock_init(&qinf->qi_pinlock); xfs_qm_list_init(&qinf->qi_dqlist, "mpdqlist", 0); qinf->qi_dqreclaims = 0;
@@ -1234,7 +1233,6 @@ xfs_qm_destroy_quotainfo(
*/ xfs_qm_rele_quotafs_ref(mp);
- spinlock_destroy(&qi->qi_pinlock);
xfs_qm_list_destroy(&qi->qi_dqlist);
if (qi->qi_uquotaip) {Looks good Pete.
Quoted 237 lines · Peter Leckie
During a heavy workload with quota's enabled the system can stall
with every process requesting log space, however the log is full of dquots.The aild is trying to push the tail of the log however every item in the log had previously been pushed by the aild and was marked as clean so the aild aborts pushing the items in the log.
The reason the log items are still in the log is because the iodone routine
xfs_qm_dqflush_donedetected that the log sequence number(lsn) had changed and assumed the log item had been re-dirtied so it should be left in the log. However the log item had not been re-dirtied so it was still marked as clean preventing it from being pushed again causing the log item to be stuck in the log. After a while the log eventually filled with these dquot log items.The reason the lsn had changed was due to it not being initialized by the time a copy of the lsn was taken in
xfs_qm_dqflush(). The lsn was then latter updated causing the test inxfs_qm_dqflush_done()to fail.Synchronizations between the 2 functions is done by the pincount in struct
xfs_dquot_tandxfs_qm_dqflush()callsxfs_qm_dqunpin_wait()to wait for the pincount == 0. However after been woken up it does not validate the pincount is actually 0, allowing a false wake up by the scheduler to causexfs_qm_dqflush()to prematurely start processing the dquot.So this patch uses an
atomic_tto track the pincount which allows us to easily use thewait_eventmacro to wait, this will guarantee that when we return fromxfs_qm_dqunpin_wait()that the pincount == 0. We also remove the globalqi_pinlockfromxfs_quotainfowhich may also reduce contention when pinning dquot's.Signed-off-by Peter Leckie <[email protected]>Index:
2.6.x-xfs/fs/xfs/quota/xfs_dquot_item.c
===================================================================
---2.6.x-xfs.orig/fs/xfs/quota/xfs_dquot_item.c2008-09-24
14:45:29.270573668 +1000
+++2.6.x-xfs/fs/xfs/quota/xfs_dquot_item.c2008-09-24
16:36:06.748350869 +1000(quoted diff) +0
-1@@ -88,25 +88,22 @@ xfs_qm_dquot_logitem_format( /* * Increment the pin count of the given dquot. - * This value is protected by pinlock spinlock in the xQM structure. */STATIC void
xfs_qm_dquot_logitem_pin(
xfs_dq_logitem_t*logitem)
{(quoted diff) +2
-5- xfs_dquot_t *dqp; + xfs_dquot_t *dqp = logitem->qli_dquot; - dqp = logitem->qli_dquot; ASSERT(XFS_DQ_IS_LOCKED(dqp)); - spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); - dqp->q_pincount++; - spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); + atomic_inc(dqp->q_pincount);}
/*
* Decrement the pin count of the given dquot, and wake up
* anyone inxfs_dqwait_unpin()if the count goes to 0. The
- * dquot must have been previously pinned with a call toxfs_dqpin().
+ * dquot must have been previously pinned with a call to
+ *xfs_qm_dquot_logitem_pin().
*/
/* ARGSUSED */
STATIC void(quoted diff) +7
-19@@ -114,16 +111,11 @@ xfs_qm_dquot_logitem_unpin( xfs_dq_logitem_t *logitem, int stale) { - xfs_dquot_t *dqp; + xfs_dquot_t *dqp = logitem->qli_dquot; - dqp = logitem->qli_dquot; - ASSERT(dqp->q_pincount > 0); - spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); - dqp->q_pincount--; - if (dqp->q_pincount == 0) { - sv_broadcast(&dqp->q_pinwait); - } - spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); + ASSERT(atomic_read(&dqp->q_pincount) > 0); + if (atomic_dec_and_test(&dqp->q_pincount)) + wake_up(&dqp->q_pinwait); } /* ARGSUSED */@@ -193,21 +185,14 @@ xfs_qm_dqunpin_wait( xfs_dquot_t *dqp) { ASSERT(XFS_DQ_IS_LOCKED(dqp)); - if (dqp->q_pincount == 0) { + if (atomic_read(&dqp->q_pincount) == 0) return; - } /* * Give the log a push so we don't wait here too long. */ xfs_log_force(dqp->q_mount, (xfs_lsn_t)0, XFS_LOG_FORCE); - spin_lock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); - if (dqp->q_pincount == 0) { - spin_unlock(&(XFS_DQ_TO_QINF(dqp)->qi_pinlock)); - return; - } - sv_wait(&(dqp->q_pinwait), PINOD, - &(XFS_DQ_TO_QINF(dqp)->qi_pinlock), s); + wait_event(dqp->q_pinwait, (atomic_read(&dqp->q_pincount) == 0)); } /*@@ -310,7 +295,7 @@ xfs_qm_dquot_logitem_trylock( uint retval; dqp = qip->qli_dquot; - if (dqp->q_pincount > 0) + if (atomic_read(&dqp->q_pincount) > 0) return (XFS_ITEM_PINNED); if (! xfs_qm_dqlock_nowait(dqp))Index:
2.6.x-xfs/fs/xfs/quota/xfs_dquot.h
===================================================================
---2.6.x-xfs.orig/fs/xfs/quota/xfs_dquot.h2008-09-24
14:45:29.270573668 +1000
+++2.6.x-xfs/fs/xfs/quota/xfs_dquot.h2008-09-24 16:24:58.623139695
+1000(quoted diff) +2
-2@@ -83,8 +83,8 @@ typedef struct xfs_dquot { xfs_qcnt_t q_res_rtbcount;/* total realtime blks used+reserved */ mutex_t q_qlock; /* quota lock */ struct completion q_flush; /* flush completion queue */ - uint q_pincount; /* pin count for this dquot */ - sv_t q_pinwait; /* sync var for pinning */ + atomic_t q_pincount; /* dquot pin count */ + wait_queue_head_t q_pinwait; /* dquot pinning wait queue */ #ifdef XFS_DQUOT_TRACE struct ktrace *q_trace; /* trace header structure */ #endifIndex:
2.6.x-xfs/fs/xfs/quota/xfs_dquot.c
===================================================================
---2.6.x-xfs.orig/fs/xfs/quota/xfs_dquot.c2008-09-24
14:45:29.270573668 +1000
+++2.6.x-xfs/fs/xfs/quota/xfs_dquot.c2008-09-24 16:24:58.623139695
+1000(quoted diff) +3
-3@@ -101,7 +101,7 @@ xfs_qm_dqinit( if (brandnewdquot) { dqp->dq_flnext = dqp->dq_flprev = dqp; mutex_init(&dqp->q_qlock); - sv_init(&dqp->q_pinwait, SV_DEFAULT, "pdq"); + init_waitqueue_head(&dqp->q_pinwait); /* * Because we want to use a counting completion, complete@@ -131,7 +131,7 @@ xfs_qm_dqinit( dqp->q_res_bcount = 0; dqp->q_res_icount = 0; dqp->q_res_rtbcount = 0; - dqp->q_pincount = 0; + atomic_set(&dqp->q_pincount, 0); dqp->q_hash = NULL; ASSERT(dqp->dq_flnext == dqp->dq_flprev);@@ -1489,7 +1489,7 @@ xfs_qm_dqpurge( "xfs_qm_dqpurge: dquot %p flush failed", dqp); xfs_dqflock(dqp); } - ASSERT(dqp->q_pincount == 0); + ASSERT(atomic_read(&dqp->q_pincount) == 0); ASSERT(XFS_FORCED_SHUTDOWN(mp) || !(dqp->q_logitem.qli_item.li_flags & XFS_LI_IN_AIL));Index:
2.6.x-xfs/fs/xfs/xfsidbg.c
===================================================================fs/xfs/xfsidbg.c 2008-09-24 16:24:58.627139176 +1000 renamed from fs/xfs/xfsidbg.c 2008-09-24 14:45:29.270573668 +1000 +2
-3@@ -6577,7 +6577,7 @@ xfsidbg_xqm_dquot(xfs_dquot_t *dqp) (unsigned long long)dqp->q_res_rtbcount); kdb_printf("qlock 0x%p &q_flush 0x%p (%d) pincount 0x%x\n", &dqp->q_qlock, &dqp->q_flush, - dqp->q_flush.done, dqp->q_pincount); + dqp->q_flush.done, atomic_read(&dqp->q_pincount)); #ifdef XFS_DQUOT_TRACE qprintf("dqtrace 0x%p\n", dqp->q_trace); #endif@@ -6761,10 +6761,9 @@ xfsidbg_xqm_qinfo(xfs_mount_t *mp) return; } - kdb_printf("uqip 0x%p, gqip 0x%p, &pinlock 0x%p &dqlist 0x%p\n", + kdb_printf("uqip 0x%p, gqip 0x%p, &dqlist 0x%p\n", mp->m_quotainfo->qi_uquotaip, mp->m_quotainfo->qi_gquotaip, - &mp->m_quotainfo->qi_pinlock, &mp->m_quotainfo->qi_dqlist); kdb_printf("btmlimit 0x%x, itmlimit 0x%x, RTbtmlim 0x%x\n",Index:
2.6.x-xfs/fs/xfs/quota/xfs_qm.h
===================================================================
---2.6.x-xfs.orig/fs/xfs/quota/xfs_qm.h2008-09-24
14:45:29.270573668 +1000
+++2.6.x-xfs/fs/xfs/quota/xfs_qm.h2008-09-24 16:24:58.627139176 +1000fs/xfs/xfsidbg.c 2008-09-24 16:24:58.627139176 +1000 renamed from fs/xfs/xfsidbg.c 2008-09-24 14:45:29.270573668 +1000 +0
-1@@ -106,7 +106,6 @@ typedef struct xfs_qm { typedef struct xfs_quotainfo { xfs_inode_t *qi_uquotaip; /* user quota inode */ xfs_inode_t *qi_gquotaip; /* group quota inode */ - spinlock_t qi_pinlock; /* dquot pinning lock */ xfs_dqlist_t qi_dqlist; /* all dquots in filesys */ int qi_dqreclaims; /* a change here indicates a removal in the dqlist */Index:
2.6.x-xfs/fs/xfs/quota/xfs_qm.c
===================================================================
---2.6.x-xfs.orig/fs/xfs/quota/xfs_qm.c2008-09-24
14:45:29.270573668 +1000
+++2.6.x-xfs/fs/xfs/quota/xfs_qm.c2008-09-24 16:24:58.631138657 +1000fs/xfs/xfsidbg.c 2008-09-24 16:24:58.627139176 +1000 renamed from fs/xfs/xfsidbg.c 2008-09-24 14:45:29.270573668 +1000 +0
-2@@ -1137,7 +1137,6 @@ xfs_qm_init_quotainfo( return error; } - spin_lock_init(&qinf->qi_pinlock); xfs_qm_list_init(&qinf->qi_dqlist, "mpdqlist", 0); qinf->qi_dqreclaims = 0;@@ -1234,7 +1233,6 @@ xfs_qm_destroy_quotainfo( */ xfs_qm_rele_quotafs_ref(mp); - spinlock_destroy(&qi->qi_pinlock); xfs_qm_list_destroy(&qi->qi_dqlist); if (qi->qi_uquotaip) {
Quoted 1 line · Lachlan McIlroy
Looks good Pete.
No, it is not yet good. Pete cannot explain the underlying problem and we need to understand if this is fixing the problem or just changing the timing so it doesn't show up....
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 7 lines · Dave Chinner
Quoted 1 line · Lachlan McIlroy
Looks good Pete.
No, it is not yet good. Pete cannot explain the underlying problem and we need to understand if this is fixing the problem or just changing the timing so it doesn't show up....
Pete clearly demonstrated to me that the sv_wait is being prematurely woken up when it should not be. There may be an underlying problem or this could just be another peculiarity of the Linux kernel. Either way Pete will continue to look into this and whatever the explanation turns out to be, we deperately need a way to prevent our customers from running into this deadlock. Even if there is another problem lurking this code change is fine and if nothing else will serve as a performance improvement.
Quoted 8 lines · Lachlan McIlroy
Pete clearly demonstrated to me that the
sv_waitis being prematurely woken up when it should not be. There may be an underlying problem or this could just be another peculiarity of the Linux kernel. Either way Pete will continue to look into this and whatever the explanation turns out to be, we deperately need a way to prevent our customers from running into this deadlock. Even if there is another problem lurking this code change is fine and if nothing else will serve as a performance improvement.
Folks, please at least invest half the time you spend flaming each other into looking at underlying causes ;-)
Quoted 4 lines · Dave Chinner
No, it is not yet good. Pete cannot explain the underlying problem and we need to understand if this is fixing the problem or just
No this patch does not change the timing so it does not happen, it fixes a problem we don't properly understand. Using wait_event prevents the thread from waking up unless the test condition is actually true. The the bug here is we are being woken up with our test condition still being false.
And this patch catches that case and hence fixes the problem the question is why is this happening in the first place. And yes that is an unanswered question.
Quoted 1 line
Only the XFS code can cause it to be woken....
Do you know this for sure?
Quoted 10 lines
Wait queues are not affected by such events when they are configured to be uninterruptable. The
sv_t:#define
sv_wait(sv, pri, lock, s)\
_sv_wait(sv, lock, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT)Is as uninterruptible as it comes. Which means only an explicit wakeup will cause any waiters to wake up.
I added some tracing to sv_broadcast to trace anyone waking up the thread however the only time it was called was from the unpinning code well after the thread had already been woken up.
Quoted 4 lines
Assert fail the machine when a dquot with a non-zero pincount is woken in
xfs_qm_dqunpin_wait(). If the assert trips, then we need to work out how the pin count is getting elevated while we have a waiter....
Done that, and it trips however it does not help us as there is no insight into who woke us.
XFS does have references to wake_up in other spots for example in xfs_super.c and xfs_buf.c so I could add some tracing there, however the most ideal spot is in __wake_up_common() so as soon as I have a chance I'll add some tracing there so we can work out what's going on here.
Either way this patch resolves this issue and is a nice code cleanup.
Thanks,
Pete
[ Pete, please wrap your text at 68-72 columns. ]
Quoted 8 lines · Peter Leckie
Quoted 4 lines · Dave Chinner
No, it is not yet good. Pete cannot explain the underlying problem and we need to understand if this is fixing the problem or just
No this patch does not change the timing so it does not happen, it fixes a problem
we don't properly understand.
Therein lies my objection - you can't fix what you don't understand. What you are proposing is a work-around. The root cause has not yet been found....
A recent example, perhaps. We discovered that the way semaphores are implemented on x86_64 can result in a thread calling up() still using the semaphore when the waiting down() has already been woken and hence can free the semaphore while we are still using it. The first patch I saw fixed the symptom seen in the buffer cache by adding an extra reference to the xfs_buf_t across I/O so that the up() in I/O completion was guaranteed to complete before we dropped the reference and freed the xfs_buf_t.
However, looking at it more deeply lead us to the fundamental problem that semaphores are optimised in an non-obvious way that lead to this problem (i.e. that down() can complete before up()). The result of understanding this is that semaphores were not safe for use within XFS for flush locks, I/O completion semaphores, etc, so we had to replace them all with completions.
This is a similar situation - if the sv_t is broken, we need to replace all users, not just work around one symptom of the brokenness. This is expecially important as the remaining users of sv_t's are in the log for iclog synchronisation....
Quoted 2 lines
Quoted 1 line
Only the XFS code can cause it to be woken....
Do you know this for sure?
Yes! wake_up() can only wake tasks on that wait queue's task list. Each different wait queue has it's own task list....
Quoted 5 lines
I added some tracing to
sv_broadcastto trace anyone waking up the thread however
the only time it was called was from the unpinning code well after the thread had already
been woken up.
So did the unpin wait even sleep? i.e. did the unpin waiter receive a wakeup to get into the state it was in or did it just pass straight through the wait code?
Quoted 7 lines
Quoted 4 lines
Assert fail the machine when a dquot with a non-zero pincount is woken in
xfs_qm_dqunpin_wait(). If the assert trips, then we need to work out how the pin count is getting elevated while we have a waiter....Done that, and it trips however it does not help us as there is no insight into
who woke us.
Ok - so what was the pin count value before it went to sleep? i.e. Did it change at all across the sleep?
Quoted 1 line
Either way this patch resolves this issue and is a nice code cleanup.
Still, don't check it in until we understand whether sv_t's are completely broken or not...
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 3 lines
Still, don't check it in until we understand whether sv_t's are completely broken or not...
Well I added some tracing code to the __wake_up_common, however it never tripped which made me think "are we even being woken up from the wait queue", or is someone directly waking us up from the task struct. So I had a look and found the following.
xfsaild_wakeup(
xfs_mount_t *mp,
xfs_lsn_t threshold_lsn)
{
mp->m_ail.xa_target = threshold_lsn;
wake_up_process(mp->m_ail.xa_task);
}Which is indirectly called from xlog_grant_push_ail, which is called from various other places.
In fact this bug is not restricted to the aild the xfssyncd also hit this issue a number of times during todays testing where it was woken while waiting on sv_wait for the pincount to drop to zero.
It also is woken up from a number of functions in xfs_super.c including xfs_syncd_queue_work(), xfs_sync_worker(), xfs_fs_sync_super()
The change that introduced the wake_up on the aild was introduced from
modid: xfs-linux-melb:xfs-kern:30371a
Move AIL pushing into it's own thread
However xfssyncd has had a long history of the task being woken up from other code,
so it looks like it's simply not safe for either the aild or xfssyncd to sleep on a queue assuming that no one else will wake the processes up.
So I would say the fix I proposed is a good solution for this issue.
However there are other functions that use sv_wait and should also be fixed in a similar way so I'll look into the other callers and prepare a patch tomorrow.
Thanks,
Pete
Quoted 5 lines · Peter Leckie
So I would say the fix I proposed is a good solution for this issue.
However there are other functions that use
sv_waitand should also be fixed in a similar way so I'll
look into the other callers and prepare a patch tomorrow.
Note that most users of sv_wait do actually re-check the condition. It's just that wait_event enforces it in the API while sv_wait doesn't
make it as explicit. But another audit of this would be a good thing.
Quoted 13 lines · Peter Leckie
Quoted 3 lines
Still, don't check it in until we understand whether sv_t's are completely broken or not...
Well I added some tracing code to the
__wake_up_common, however it never tripped
which made me think "are we even being woken up from the wait queue", or is someone
directly waking us up from the task struct. So I had a look and found the following.xfsaild_wakeup(
xfs_mount_t*mp,
xfs_lsn_tthreshold_lsn)
Quoted 8 lines
{ mp->m_ail.xa_target = threshold_lsn; wake_up_process(mp->m_ail.xa_task); }Which is indirectly called from
xlog_grant_push_ail, which is called from various other
places.
Ok, so that one will only wake up the xfsaild, which does not flush pinned items - it will never end up in an unpin wait for any type of item, so we can rule that one out.
Quoted 5 lines
In fact this bug is not restricted to the aild the xfssyncd also hit this issue a number of times
during todays testing where it was woken while waiting onsv_waitfor the pincount to drop
to zero.
Ok, so there is the fundamental issue. This one is problematic because xfssyncd calls into xfs_sync() -> xfs_qm_sync(). It does so with the flag SYNC_BDFLUSH set, which means:
1013 /*
1014 * We won't block unless we are asked to.
1015 */
1016 nowait = (boolean_t)(flags & SYNC_BDFLUSH || (flags & SYNC_WAIT) == 0);
1017
We should not be blocking when flushing dquots. IOWs, we should not be waiting on pinned quots in xfs_qm_sync() when it calls xfs_dqflush(). i.e. it should behave exactly like the inode flush code.
i.e. the reason why we are seeing this is that xfs_dqflush is not obeying the non-blocking semantics of the sync that it is being asked to run. If we enter xfs_sync() from anywhere else, then we won't have task wakeups occurring to interrupt a pin wait on a synchronous sync....
Quoted 2 lines
It also is woken up from a number of functions in
xfs_super.cincludingxfs_syncd_queue_work(),xfs_sync_worker(),xfs_fs_sync_super()
Yeah, when different work needs doing.
Quoted 10 lines
The change that introduced the
wake_upon the aild was introduced frommodid: xfs-linux-melb:xfs-kern:30371a
Move AIL pushing into it's own threadHowever xfssyncd has had a long history of the task being woken up from other code,
so it looks like it's simply not safe for either the aild or xfssyncd to sleep on a queue assuming that
no one else will wake the processes up.
Given that both xfsaild and xfssyncd are supposed to be doing non-blocking flushes, neither of them should ever be waiting on a pinned item, therefore fixing that problem in xfs_qm_dqflush() should make this problem go away. It will also substantially reduce tehnumber of log forces being triggered by dquot writeback which will have positive impact on performance, too.
Quoted 1 line
So I would say the fix I proposed is a good solution for this issue.
but it doesn't fix the underlying problem that was causing the spurious wakeups, which is the fact that xfs_qm_dqflush() is not obeying non-blocking flush directions. The patch below should fix that. Can you please test it before you add your patch?
Quoted 3 lines
However there are other functions that use
sv_waitand should also be fixed in a similar way so I'll
look into the other callers and prepare a patch tomorrow.
The log force and write sv_t's are already in loops that would catch spurious wakeups, so I don't think there's a problem there....
Cheers,
Dave.
Signature · 21 lines
Dave Chinner [email protected] XFS: don't block in xfs_qm_dqflush() during async writeback Normally dquots are written back via delayed write mechanisms. They are flushed to their backing buffer by xfssyncd, which is then pushed out by either AIL or xfsbufd flushing. The flush from the xfssyncd is supposed to be non-blocking, but xfs_qm_dqflush() always waits for pinned duots, which means that it will block for the length of time it takes to do a synchronous log force. This causes unnecessary extra log I/O to be issued whenever we try to flush a busy dquot. Avoid the log forces and blocking xfssyncd by making xfs_qm_dqflush() pay attention to what type of sync it is doing when it sees a pinned dquot and not waiting when doing non-blocking flushes. --- fs/xfs/quota/xfs_dquot.c | 7 ++++++- 1 files changed, 6 insertions(+), 1 deletions(-)
fs/xfs/quota/xfs_dquot.c +6 -1
@@ -1229,8 +1229,13 @@ xfs_qm_dqflush( }/* -* Cant flush a pinned dquot. Wait for it. +* Cant flush a pinned dquot. If we are not supposed to block, +* don't wait for it. */ +if (!(flags &XFS_QMOPT_SYNC ) && dqp->q_pincount >0 ) { +xfs_dqfunlock (dqp); +return (0 ); + }xfs_qm_dqunpin_wait (dqp);/*
Quoted 5 lines · Dave Chinner
but it doesn't fix the underlying problem that was causing the spurious wakeups, which is the fact that
xfs_qm_dqflush()is not obeying non-blocking flush directions. The patch below should fix that. Can you please test it before you add your patch?
Yeah I already had this idea I just have not posted a patch because Lachlan though it might introduce a deadlock. If you think this is a good Idea I will update my patch to be non blocking.
Quoted 8 lines · Peter Leckie
Quoted 5 lines · Dave Chinner
but it doesn't fix the underlying problem that was causing the spurious wakeups, which is the fact that
xfs_qm_dqflush()is not obeying non-blocking flush directions. The patch below should fix that. Can you please test it before you add your patch?Yeah I already had this idea I just have not posted a patch because Lachlan though it might introduce a deadlock.
I suggested some changes a while back to make tail pushing non-blocking and Dave thought it might cause a deadlock.
http://oss.sgi.com/archives/xfs/2008-07/msg00472.html
I actually did hit a deadlock with this change but could not figure out why. It may have been the same issue Pete is trying to fix here.
Quoted 3 lines
If you think this is a good Idea I will update my patch to be non blocking.
Quoted 7 lines · Lachlan McIlroy
Quoted 6 lines · Peter Leckie
Quoted 5 lines · Dave Chinner
but it doesn't fix the underlying problem that was causing the spurious wakeups, which is the fact that
xfs_qm_dqflush()is not obeying non-blocking flush directions. The patch below should fix that. Can you please test it before you add your patch?
Quoted 7 lines
Quoted 3 lines
Yeah I already had this idea I just have not posted a patch because Lachlan though
it might introduce a deadlock.I suggested some changes a while back to make tail pushing non-blocking and Dave thought it might cause a deadlock.
That is a different case - the aborting of writeback due to a locked inode cluster buffer could be problematic for the AIL code because it already has special code to handle cluster buffer pushing in the case of DELWRI flushed inodes.
The case I described is what the xfsaild "watchdog timeout" really catches - before the aild the filesystem would simply lock up, and one way to trigger that was to have the AIL traversal restart too many times without making progress. The AIL cursor patch series I posted fixes the excessive restart problem, but doesn't prevent problem from occurring if the async push doesn't actually write the item back in IOP_PUSH(). Effectively we need to tweak the ail push-wait-push loops in the log grant code to avoid this problem.
FWIW, IOP_TRYLOCK() will return ITEM_PINNED for any object that is still pinned, and hence the AIL does not call IOP_PUSH() for such items. Instead it schedules a non-blocking log force for the end of the traversal to get things moving for the next push to flush it out. Hence the AIL pushing should never, ever be trying to push a pinned inode or dquot to disk, and hence the proposed change will not affect AIL pushing at all....
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 69 lines · Dave Chinner
Quoted 9 lines · Peter Leckie
Quoted 2 lines
Still, don't check it in until we understand whether sv_t's are completely broken or not...
Well I added some tracing code to the
__wake_up_common, however it never tripped which made me think "are we even being woken up from the wait queue", or is someone directly waking us up from the task struct. So I had a look and found the following.xfsaild_wakeup(
xfs_mount_t*mp,
xfs_lsn_tthreshold_lsn)Quoted 7 lines
{ mp->m_ail.xa_target = threshold_lsn; wake_up_process(mp->m_ail.xa_task); }Which is indirectly called from
xlog_grant_push_ail, which is called from various other places.Ok, so that one will only wake up the xfsaild, which does not flush pinned items - it will never end up in an unpin wait for any type of item, so we can rule that one out.
Quoted 3 lines
In fact this bug is not restricted to the aild the xfssyncd also hit this issue a number of times during todays testing where it was woken while waiting on
sv_waitfor the pincount to drop to zero.Ok, so there is the fundamental issue. This one is problematic because xfssyncd calls into
xfs_sync()->xfs_qm_sync(). It does so with the flagSYNC_BDFLUSHset, which means:1013 /*
1014 * We won't block unless we are asked to.
1015 */
1016 nowait = (boolean_t)(flags &SYNC_BDFLUSH|| (flags &SYNC_WAIT) == 0);
1017We should not be blocking when flushing dquots. IOWs, we should not be waiting on pinned quots in
xfs_qm_sync()when it callsxfs_dqflush(). i.e. it should behave exactly like the inode flush code.i.e. the reason why we are seeing this is that
xfs_dqflushis not obeying the non-blocking semantics of the sync that it is being asked to run. If we enterxfs_sync()from anywhere else, then we won't have task wakeups occurring to interrupt a pin wait on a synchronous sync....Quoted 2 lines
It also is woken up from a number of functions in
xfs_super.cincludingxfs_syncd_queue_work(),xfs_sync_worker(),xfs_fs_sync_super()Yeah, when different work needs doing.
Quoted 8 lines
The change that introduced the
wake_upon the aild was introduced frommodid: xfs-linux-melb:xfs-kern:30371a
Move AIL pushing into it's own threadHowever xfssyncd has had a long history of the task being woken up from other code,
so it looks like it's simply not safe for either the aild or xfssyncd to sleep on a queue assuming that no one else will wake the processes up.Given that both xfsaild and xfssyncd are supposed to be doing non-blocking flushes, neither of them should ever be waiting on a pinned item, therefore fixing that problem in
xfs_qm_dqflush()should make this problem go away. It will also substantially reduce tehnumber of log forces being triggered by dquot writeback which will have positive impact on performance, too.
Quoted 5 lines
Quoted 1 line
So I would say the fix I proposed is a good solution for this issue.
but it doesn't fix the underlying problem that was causing the spurious wakeups, which is the fact that
xfs_qm_dqflush()is not obeying non-blocking flush directions.
The underlying problem has nothing to do with xfs_qm_dqflush() - the spurious wakeups are caused by calls to wake_up_process() that arbitrarily wake up a process that is in a state where it shouldn't be woken up. If we don't fix the spurious wakeups then we could easily re-introduce this problem again. If xfs_qm_dqflush() should be non-blocking then that's a separate change and it sounds like a good change too.
Quoted 12 lines
The patch below should fix
that. Can you please test it before you add your patch?Quoted 2 lines
However there are other functions that use
sv_waitand should also be fixed in a similar way so I'll look into the other callers and prepare a patch tomorrow.The log force and write sv_t's are already in loops that would catch spurious wakeups, so I don't think there's a problem there....
Cheers,
Dave.
Quoted 6 lines · Lachlan McIlroy
The underlying problem has nothing to do with
xfs_qm_dqflush()- the spurious wakeups are caused by calls towake_up_process()that arbitrarily wake up a process that is in a state where it shouldn't be woken up. If we don't fix the spurious wakeups then we could easily re-introduce this problem again. Ifxfs_qm_dqflush()should be non-blocking then that's a separate change and it sounds like a good change too.
Ok so what do we want to do. It almost sounds like there are 3 issues I need to solve, first clean up the code, second make xfs_qm_dqflush() non blocking, and 3ed fix up the spurious wakeups.
Should I propose 3 patches to fix each of these issues?
Pete
Quoted 7 lines · Peter Leckie
Quoted 6 lines · Lachlan McIlroy
The underlying problem has nothing to do with
xfs_qm_dqflush()- the spurious wakeups are caused by calls towake_up_process()that arbitrarily wake up a process that is in a state where it shouldn't be woken up. If we don't fix the spurious wakeups then we could easily re-introduce this problem again. Ifxfs_qm_dqflush()should be non-blocking then that's a separate change and it sounds like a good change too.
Quoted 5 lines
Ok so what do we want to do. It almost sounds like there are 3 issues I need to solve, first clean up the code, second make
xfs_qm_dqflush()non blocking, and 3ed fix up the spurious wakeups.Should I propose 3 patches to fix each of these issues?
yes - can we take the quota fix "as-is" for now. Those of us shipping NAS servers with quotas enabled need this fix.
The other two issues need more investigation and look like they may have more fundamental implications - they should be separate bugs.
Thanks
Signature · 4 lines
Mark Goodwin [email protected] Engineering Manager for XFS and PCP Phone: +61-3-99631937 SGI Australian Software Group Cell: +61-4-18969583 -------------------------------------------------------------
Quoted 3 lines
yes - can we take the quota fix "as-is" for now. Those of us shipping NAS servers with quotas enabled need this fix.
Ok is everyone happy for me to push the original patch now and I'll propose 2 more patches latter? The other 2 patches should not touch code involved with this patch.
Thanks,
Pete
Quoted 8 lines · Peter Leckie
Quoted 7 lines · Lachlan McIlroy
The underlying problem has nothing to do with
xfs_qm_dqflush()- the spurious wakeups are caused by calls towake_up_process()that arbitrarily
wake up a process that is in a state where it shouldn't be woken up. If we don't fix the spurious wakeups then we could easily re-introduce this problem again. Ifxfs_qm_dqflush()should be non-blocking then that's a separate change and it sounds like a good change too.
Quoted 6 lines
Ok so what do we want to do. It almost sounds like there are 3 issues I need to solve,
first clean up the code, second makexfs_qm_dqflush()non blocking, and 3ed fix up the spurious wakeups.Should I propose 3 patches to fix each of these issues?
Well, your patch for 1 is in, Dave has one for 2, and I don't think three is an issue - at least for xfssyncd.
Quoted 17 lines · Lachlan McIlroy
Quoted 16 lines · Dave Chinner
Quoted 5 lines · Peter Leckie
However xfssyncd has had a long history of the task being woken up from other code,
so it looks like it's simply not safe for either the aild or xfssyncd to sleep on a queue assuming that
no one else will wake the processes up.Given that both xfsaild and xfssyncd are supposed to be doing non-blocking flushes, neither of them should ever be waiting on a pinned item, therefore fixing that problem in
xfs_qm_dqflush()should make this problem go away. It will also substantially reduce tehnumber of log forces being triggered by dquot writeback which will have positive impact on performance, too.Quoted 1 line
So I would say the fix I proposed is a good solution for this issue.
Quoted 7 lines
Quoted 3 lines
but it doesn't fix the underlying problem that was causing the spurious wakeups, which is the fact that
xfs_qm_dqflush()is not obeying non-blocking flush directions.The underlying problem has nothing to do with
xfs_qm_dqflush()- the spurious wakeups are caused by calls towake_up_process()that arbitrarily wake up a process that is in a state where it shouldn't be woken up.
Spurious wakeups are causing problems in a place where we should not even be sleeping. If you don't sleep there, you can't get spurious wakeups....
Quoted 2 lines
If we don't fix the spurious wakeups then we could easily re-introduce this problem again.
Right, but keep in mind that the patch doesn't prevent spurious wakeups - it merely causes the thread to wakeup and go back to sleep when a spurious wakeup occurs. The patch I posted avoids the spurious wakeup problem completely, which is what we should be aiming to do given it avoids the overhead of 2 context switches and speeds up the rate at which we can flush unpinned dquots.
That being said, I agree that the original patch is still desirable, though not from a bug-fix perspective. It's a cleanup and optimisation patch, with the nice side effect of preventing future occurrences of the spurious wakeup problem....
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 21 lines · Dave Chinner
Quoted 12 lines · Lachlan McIlroy
Quoted 11 lines · Dave Chinner
Quoted 3 lines · Peter Leckie
However xfssyncd has had a long history of the task being woken up from other code,
so it looks like it's simply not safe for either the aild or xfssyncd to sleep on a queue assuming that no one else will wake the processes up.Given that both xfsaild and xfssyncd are supposed to be doing non-blocking flushes, neither of them should ever be waiting on a pinned item, therefore fixing that problem in
xfs_qm_dqflush()should make this problem go away. It will also substantially reduce tehnumber of log forces being triggered by dquot writeback which will have positive impact on performance, too.Quoted 7 lines
Quoted 4 lines
Quoted 1 line
So I would say the fix I proposed is a good solution for this issue.
but it doesn't fix the underlying problem that was causing the spurious wakeups, which is the fact that
xfs_qm_dqflush()is not obeying non-blocking flush directions.The underlying problem has nothing to do with
xfs_qm_dqflush()- the spurious wakeups are caused by calls towake_up_process()that arbitrarily wake up a process that is in a state where it shouldn't be woken up.
Quoted 8 lines
Spurious wakeups are causing problems in a place where we should not even be sleeping. If you don't sleep there, you can't get spurious wakeups....
Quoted 2 lines
If we don't fix the spurious wakeups then we could easily re-introduce this problem again.
Right, but keep in mind that the patch doesn't prevent spurious wakeups - it merely causes the thread to wakeup and go back to sleep
Yes that's right and it's why I suggested replacing the uses of wake_up_process with wake_up and a wait queue where both the xfsaild and xfssyncd threads can have a wait queue specific to them. This way we only wake them up if they are sleeping on that wait queue and not somewhere else waiting for a different event. I'm pretty sure that will be a safe change to make.
Quoted 13 lines
when a spurious wakeup occurs. The patch I posted avoids the spurious wakeup problem completely, which is what we should be aiming to do given it avoids the overhead of 2 context switches and speeds up the rate at which we can flush unpinned dquots.
That being said, I agree that the original patch is still desirable, though not from a bug-fix perspective. It's a cleanup and optimisation patch, with the nice side effect of preventing future occurrences of the spurious wakeup problem....
Cheers,
Dave.
Quoted 8 lines · Lachlan McIlroy
Quoted 2 lines · Dave Chinner
Right, but keep in mind that the patch doesn't prevent spurious wakeups - it merely causes the thread to wakeup and go back to sleep
Yes that's right and it's why I suggested replacing the uses of
wake_up_processwithwake_upand a wait queue where both the xfsaild and xfssyncd threads can have a wait queue specific to them. This way we only wake them up if they are sleeping on that wait queue and not somewhere else waiting for a different event. I'm pretty sure that will be a safe change to make.
Yes, that sounds like a good idea.
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Quoted 9 lines · Lachlan McIlroy
Quoted 3 lines
but it doesn't fix the underlying problem that was causing the spurious wakeups, which is the fact that
xfs_qm_dqflush()is not obeying non-blocking flush directions.The underlying problem has nothing to do with
xfs_qm_dqflush()- the spurious wakeups are caused by calls towake_up_process()that arbitrarily wake up a process that is in a state where it shouldn't be woken up. If we don't fix the spurious wakeups then we could easily re-introduce this problem again. Ifxfs_qm_dqflush()should be non-blocking then that's a separate change and it sounds like a good change too.
That sounds like there is one single underlying problem, but there isn't. The first problem was that we didn't re-check the condition after sv_wait. We must always do this, if only for robustness reasons. The second problem we have here is that xfssyncd blocks when flushing quotas.
fs/xfs/quota/xfs_dquot.c xfs_qm_dqflush( · line 1239
Quoted 10 lines
fs/xfs/quota/xfs_dquot.c +6
-1/* -* Cant flush a pinned dquot. Wait for it. +* Cant flush a pinned dquot. If we are not supposed to block, +* don't wait for it. */ +if (!(flags &XFS_QMOPT_SYNC ) && dqp->q_pincount >0 ) { +xfs_dqfunlock (dqp); +return (0 ); + }xfs_qm_dqunpin_wait (dqp);
Looks good, but please remove the braces around the 0. (And yes, I know that the statement just above it does it too..)
Quoted 5 lines · Christoph Hellwig
Quoted 5 lines
(quoted diff) +2
-1/* - * Cant flush a pinned dquot. Wait for it. + * Cant flush a pinned dquot. If we are not supposed to block, + * don't wait for it. */
Quoted 8 lines
Review
Quoted 5 lines
(quoted diff) +4
-0+ if (!(flags & XFS_QMOPT_SYNC) && dqp->q_pincount > 0) { + xfs_dqfunlock(dqp); + return (0); + } xfs_qm_dqunpin_wait(dqp);Looks good, but please remove the braces around the 0. (And yes, I know that the statement just above it does it too..)
No prize for guessing where I copied the code from, then ;)
New version that combines the dirty check with the pin count check below....
Cheers,
Dave.
Signature · 23 lines
Dave Chinner [email protected] XFS: don't block in xfs_qm_dqflush() during async writeback Normally dquots are written back via delayed write mechanisms. They are flushed to their backing buffer by xfssyncd, which is then pushed out by either AIL or xfsbufd flushing. The flush from the xfssyncd is supposed to be non-blocking, but xfs_qm_dqflush() always waits for pinned duots, which means that it will block for the length of time it takes to do a synchronous log force. This causes unnecessary extra log I/O to be issued whenever we try to flush a busy dquot. Avoid the log forces and blocking xfssyncd by making xfs_qm_dqflush() pay attention to what type of sync it is doing when it sees a pinned dquot and not waiting when doing non-blocking flushes. Signed-off-by: Dave Chinner <[email protected]> --- fs/xfs/quota/xfs_dquot.c | 12 +++++------- 1 files changed, 5 insertions(+), 7 deletions(-)
fs/xfs/quota/xfs_dquot.c +5 -7
@@ -1221,16 +1221,14 @@ xfs_qm_dqflush(xfs_dqtrace_entry (dqp,"DQFLUSH" );/* -* If not dirty, nada. +* If not dirty, or it's pinned and we are not supposed to +* block, nada. */ -if (!XFS_DQ_IS_DIRTY (dqp)) { +if (!XFS_DQ_IS_DIRTY (dqp) || + (!(flags &XFS_QMOPT_SYNC ) && dqp->q_pincount >0 )) {xfs_dqfunlock (dqp); -return (0 ); +return 0 ; } - -/* -* Cant flush a pinned dquot. Wait for it. -*/ xfs_qm_dqunpin_wait (dqp);/*
Quoted 33 lines · Peter Leckie
Quoted 3 lines
Still, don't check it in until we understand whether sv_t's are completely broken or not...
Well I added some tracing code to the
__wake_up_common, however it never tripped which made me think "are we even being woken up from the wait queue", or is someone directly waking us up from the task struct. So I had a look and found the following.xfsaild_wakeup(
xfs_mount_t*mp,
xfs_lsn_tthreshold_lsn){ mp->m_ail.xa_target = threshold_lsn; wake_up_process(mp->m_ail.xa_task); }Which is indirectly called from
xlog_grant_push_ail, which is called from various other places.In fact this bug is not restricted to the aild the xfssyncd also hit this issue a number of times during todays testing where it was woken while waiting on
sv_waitfor the pincount to drop to zero.It also is woken up from a number of functions in
xfs_super.cincludingxfs_syncd_queue_work(),xfs_sync_worker(),xfs_fs_sync_super()The change that introduced the
wake_upon the aild was introduced frommodid: xfs-linux-melb:xfs-kern:30371a
Move AIL pushing into it's own thread
Quoted 8 lines
However xfssyncd has had a long history of the task being woken up from other code,
so it looks like it's simply not safe for either the aild or xfssyncd to sleep on a queue assuming that no one else will wake the processes up.So I would say the fix I proposed is a good solution for this issue.
However there are other functions that use
sv_waitand should also be fixed in a similar way so I'll look into the other callers and prepare a patch tomorrow.
Good work Pete. We should also consider replacing all calls to wake_up_process() with wake_up() and a wait queue so we don't go waking up threads when we shouldn't be.
Quoted 3 lines · Lachlan McIlroy
Good work Pete. We should also consider replacing all calls to
wake_up_process()withwake_up()and a wait queue so we don't go waking up threads when we shouldn't be.
No. The daemons should not block anyway in these places, and using a waitqueue just causes additional locking overhead.
Quoted 9 lines · Christoph Hellwig
Quoted 3 lines · Lachlan McIlroy
Good work Pete. We should also consider replacing all calls to
wake_up_process()withwake_up()and a wait queue so we don't go waking up threads when we shouldn't be.No. The daemons should not block anyway in these places, and using a waitqueue just causes additional locking overhead.
The daemons shouldn't block anymore in the code we are going to fix but what about somewhere else? Maybe in a memory allocation, semaphore, mutex, etc... ? Can you guarantee that there is no other code that does not correctly handle waking up prematurely?
Just as it is prudent to be defensive and add a loop around the sv_wait() we should also be prudent by not potentially causing this same problem in some other buggy code somewhere else. Using wait queues may add additional locking overhead but if we are waking up threads that shouldn't be woken up then we're wasting cycles on unnecessary context switches anyway.
Our customers wont notice if they lose a couple of cycles here or there but they will notice deadlocks, corruption or panics. And I would feel at ease knowing this problem wont happen again.
Quoted 4 lines · Dave Chinner
This is a similar situation - if the
sv_tis broken, we need to replace all users, not just work around one symptom of the brokenness. This is expecially important as the remaining users of sv_t's are in the log for iclog synchronisation....
The sv_t is not broken per se but a quite dangerous primitive as people can easily miss re-checking the condition after a wakeup.
After a quick check 5 out of 10 callers of sv_wait re-check their condition, and most do in a quite broad loop. It might make sense to just that lille bit of code and use prepare_wait / finish_wait explicitly.
Quoted 6 lines · Dave Chinner
Quoted 1 line · Lachlan McIlroy
Looks good Pete.
No, it is not yet good. Pete cannot explain the underlying problem and we need to understand if this is fixing the problem or just changing the timing so it doesn't show up....
The patch does not only cause timing but also makes sure xfs_qm_dqunpin_wait sleeps again when woken up but the condition it was waiting on is not met. That's the reason why we have wait_event in Linux instead of the more traditional sv-style conditional variables.
Now the spurious wakeup from scheduler argument doesn't make any sense, so this spurious wakeup we're protecting from must come from XFS itself. The way this could happen is when a task trying to pin the dquot gets qi_pinlock before the one waiting for q_pincount to reach zero.
So the patch does looks good to me, but the current explanation needs some updating.
Quoted 13 lines · Christoph Hellwig
Quoted 6 lines · Dave Chinner
Quoted 1 line · Lachlan McIlroy
Looks good Pete.
No, it is not yet good. Pete cannot explain the underlying problem and we need to understand if this is fixing the problem or just changing the timing so it doesn't show up....
The patch does not only cause timing but also makes sure
xfs_qm_dqunpin_waitsleeps again when woken up but the condition it was waiting on is not met. That's the reason why we havewait_eventin Linux instead of the more traditional sv-style conditional variables.
Quoted 4 lines
Now the spurious wakeup from scheduler argument doesn't make any sense, so this spurious wakeup we're protecting from must come from XFS itself. The way this could happen is when a task trying to pin the dquot gets
qi_pinlockbefore the one waiting forq_pincountto reach zero.
Can't happen. To pin the dquot you have to hold the dquot lock. That dquot lock is held by the one waiting for the q_pincount to reach zero. i.e. pin and unpin_wait are mutually exclusive.
Also, qi_pinlock is a spinlock, so it should not be triggering any spurious scheduler events that wake up threads sleeping on some unrelated wait queue....
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]