Illumos #764: panic in zfs:dbuf_sync_list
Hypothesis about what's going on here. At some time in the past, something, i.e. dnode_reallocate() calls one of: dbuf_rm_spill(dn, tx); These will do: dbuf_rm_spill(dnode_t *dn, dmu_tx_t *tx) dbuf_free_range(dn, DMU_SPILL_BLKID, DMU_SPILL_BLKID, tx) dbuf_undirty(db, tx) Currently dbuf_undirty can leave a spill block in dn_dirty_records[], (it having been put there previously by dbuf_dirty) and free it. Sometime later, dbuf_sync_list trips over this reference to free'd (and typically reused) memory. Also, dbuf_undirty can call dnode_clear_range with a bogus block ID. It needs to test for DMU_SPILL_BLKID, similar to how dnode_clear_range is called in dbuf_dirty(). References to Illumos issue and patch: - https://www.illumos.org/issues/764 - https://github.com/illumos/illumos-gate/commit/3f2366c2bb Reviewed by: George Wilson <gwilson@zfsmail.com> Reviewed by: Mark.Maybe@oracle.com Reviewed by: Albert Lee <trisk@nexenta.com Approved by: Garrett D'Amore <garrett@nexenta.com> Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov> Issue #340
This commit is contained in:
parent
7b8518cb8d
commit
ef3c1dea70
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright 2011 Nexenta Systems, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <sys/zfs_context.h>
|
||||
|
@ -1347,13 +1348,17 @@ dbuf_undirty(dmu_buf_impl_t *db, dmu_tx_t *tx)
|
|||
* it, since one of the current holders may be in the
|
||||
* middle of an update. Note that users of dbuf_undirty()
|
||||
* should not place a hold on the dbuf before the call.
|
||||
* Also note: we can get here with a spill block, so
|
||||
* test for that similar to how dbuf_dirty does.
|
||||
*/
|
||||
if (refcount_count(&db->db_holds) > db->db_dirtycnt) {
|
||||
mutex_exit(&db->db_mtx);
|
||||
/* Make sure we don't toss this buffer at sync phase */
|
||||
mutex_enter(&dn->dn_mtx);
|
||||
dnode_clear_range(dn, db->db_blkid, 1, tx);
|
||||
mutex_exit(&dn->dn_mtx);
|
||||
if (db->db_blkid != DMU_SPILL_BLKID) {
|
||||
mutex_enter(&dn->dn_mtx);
|
||||
dnode_clear_range(dn, db->db_blkid, 1, tx);
|
||||
mutex_exit(&dn->dn_mtx);
|
||||
}
|
||||
DB_DNODE_EXIT(db);
|
||||
return (0);
|
||||
}
|
||||
|
@ -1366,11 +1371,18 @@ dbuf_undirty(dmu_buf_impl_t *db, dmu_tx_t *tx)
|
|||
|
||||
*drp = dr->dr_next;
|
||||
|
||||
/*
|
||||
* Note that there are three places in dbuf_dirty()
|
||||
* where this dirty record may be put on a list.
|
||||
* Make sure to do a list_remove corresponding to
|
||||
* every one of those list_insert calls.
|
||||
*/
|
||||
if (dr->dr_parent) {
|
||||
mutex_enter(&dr->dr_parent->dt.di.dr_mtx);
|
||||
list_remove(&dr->dr_parent->dt.di.dr_children, dr);
|
||||
mutex_exit(&dr->dr_parent->dt.di.dr_mtx);
|
||||
} else if (db->db_level+1 == dn->dn_nlevels) {
|
||||
} else if (db->db_blkid == DMU_SPILL_BLKID ||
|
||||
db->db_level+1 == dn->dn_nlevels) {
|
||||
ASSERT(db->db_blkptr == NULL || db->db_parent == dn->dn_dbuf);
|
||||
mutex_enter(&dn->dn_mtx);
|
||||
list_remove(&dn->dn_dirty_records[txg & TXG_MASK], dr);
|
||||
|
|
Loading…
Reference in New Issue