Fix a false positive in a buf_ring assert

buf_ring contains an assert that checks whether an item being
enqueued already exists on the ring.  There is a subtle bug in
this assert.  An item can be returned by a peek() function and
freed, and then the consumer thread can be preempted before
calling advance().  If this happens the item appears to still be
on the queue, but another thread may allocate the item from the
free pool and wind up trying to enqueue it again, causing the
assert to trigger incorrectly.

Fix this by skipping the head of the consumer's portion of the
ring, as this index is what will be returned by peek().

Sponsored by:	Dell EMC Isilon
MFC After:	1 week
Differential Revision:	https://reviews.freebsd.org/D8685
Reviewed by:	hselasky
This commit is contained in:
Ryan Stone 2016-12-01 21:08:42 +00:00
parent 40a969525a
commit 86a6fcd4ff

View File

@ -67,11 +67,13 @@ buf_ring_enqueue(struct buf_ring *br, void *buf)
uint32_t prod_head, prod_next, cons_tail;
#ifdef DEBUG_BUFRING
int i;
for (i = br->br_cons_head; i != br->br_prod_head;
i = ((i + 1) & br->br_cons_mask))
if(br->br_ring[i] == buf)
panic("buf=%p already enqueue at %d prod=%d cons=%d",
buf, i, br->br_prod_tail, br->br_cons_tail);
if (br->br_cons_head != br->br_prod_head) {
for (i = (br->br_cons_head + 1) & br->br_cons_mask; i != br->br_prod_head;
i = ((i + 1) & br->br_cons_mask))
if(br->br_ring[i] == buf)
panic("buf=%p already enqueue at %d prod=%d cons=%d",
buf, i, br->br_prod_tail, br->br_cons_tail);
}
#endif
critical_enter();
do {