Skip to content

Commit

Permalink
5095 panic when adding a duplicate dbuf to dn_dbufs
Browse files Browse the repository at this point in the history
Reviewed by: Adam Leventhal <adam.leventhal@delphix.com>
Reviewed by: George Wilson <george.wilson@delphix.com>
Reviewed by: Mattew Ahrens <mahrens@delphix.com>
Reviewed by: Dan Kimmel <dan.kimmel@delphix.com>
Reviewed by: Dan McDonald <danmcd@omniti.com>
Reviewed by: Josef Sipek <jeffpc@josefsipek.net>
Approved by: Robert Mustacchi <rm@joyent.com>
  • Loading branch information
Alex Reece authored and Christopher Siden committed Aug 19, 2014
1 parent 60a61f7 commit 86bb58a
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 23 deletions.
4 changes: 1 addition & 3 deletions usr/src/uts/common/fs/zfs/dbuf.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ dbuf_cons(void *vdb, void *unused, int kmflag)
cv_init(&db->db_changed, NULL, CV_DEFAULT, NULL);
refcount_create(&db->db_holds);

db->db_creation = gethrtime();

return (0);
}

Expand Down Expand Up @@ -819,7 +817,7 @@ dbuf_free_range(dnode_t *dn, uint64_t start_blkid, uint64_t end_blkid,

db_search.db_level = 0;
db_search.db_blkid = start_blkid;
db_search.db_creation = 0;
db_search.db_state = DB_SEARCH;

mutex_enter(&dn->dn_dbufs_mtx);
if (start_blkid >= dn->dn_unlisted_l0_blkid) {
Expand Down
34 changes: 18 additions & 16 deletions usr/src/uts/common/fs/zfs/dnode.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,33 +67,35 @@ dbuf_compare(const void *x1, const void *x2)

if (d1->db_level < d2->db_level) {
return (-1);
} else if (d1->db_level > d2->db_level) {
}
if (d1->db_level > d2->db_level) {
return (1);
}

if (d1->db_blkid < d2->db_blkid) {
return (-1);
} else if (d1->db_blkid > d2->db_blkid) {
}
if (d1->db_blkid > d2->db_blkid) {
return (1);
}

/*
* If a dbuf is being evicted while dn_dbufs_mutex is not held, we set
* the db_state to DB_EVICTING but do not remove it from dn_dbufs. If
* another thread creates a dbuf of the same blkid before the dbuf is
* removed from dn_dbufs, we can reach a state where there are two
* dbufs of the same blkid and level in db_dbufs. To maintain the avl
* invariant that there cannot be duplicate items, we distinguish
* between these two dbufs based on the time they were created.
*/
if (d1->db_creation < d2->db_creation) {
if (d1->db_state < d2->db_state) {
return (-1);
} else if (d1->db_creation > d2->db_creation) {
}
if (d1->db_state > d2->db_state) {
return (1);
} else {
ASSERT3P(d1, ==, d2);
return (0);
}

ASSERT3S(d1->db_state, !=, DB_SEARCH);
ASSERT3S(d2->db_state, !=, DB_SEARCH);

if ((uintptr_t)d1 < (uintptr_t)d2) {
return (-1);
}
if ((uintptr_t)d1 > (uintptr_t)d2) {
return (1);
}
return (0);
}

/* ARGSUSED */
Expand Down
8 changes: 5 additions & 3 deletions usr/src/uts/common/fs/zfs/sys/dbuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ extern "C" {
* | |
* | |
* +--------> NOFILL -------+
*
* DB_SEARCH is an invalid state for a dbuf. It is used by dbuf_free_range
* to find all dbufs in a range of a dnode and must be less than any other
* dbuf_states_t (see comment on dn_dbufs in dnode.h).
*/
typedef enum dbuf_states {
DB_SEARCH = -1,
DB_UNCACHED,
DB_FILL,
DB_NOFILL,
Expand Down Expand Up @@ -213,9 +218,6 @@ typedef struct dmu_buf_impl {
/* pointer to most recent dirty record for this buffer */
dbuf_dirty_record_t *db_last_dirty;

/* Creation time of dbuf (see comment in dbuf_compare). */
hrtime_t db_creation;

/*
* Our link on the owner dnodes's dn_dbufs list.
* Protected by its dn_dbufs_mtx.
Expand Down
13 changes: 12 additions & 1 deletion usr/src/uts/common/fs/zfs/sys/dnode.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,18 @@ typedef struct dnode {
refcount_t dn_holds;

kmutex_t dn_dbufs_mtx;
avl_tree_t dn_dbufs; /* descendent dbufs */
/*
* Descendent dbufs, ordered by dbuf_compare. Note that dn_dbufs
* can contain multiple dbufs of the same (level, blkid) when a
* dbuf is marked DB_EVICTING without being removed from
* dn_dbufs. To maintain the avl invariant that there cannot be
* duplicate entries, we order the dbufs by an arbitrary value -
* their address in memory. This means that dn_dbufs cannot be used to
* directly look up a dbuf. Instead, callers must use avl_walk, have
* a reference to the dbuf, or look up a non-existant node with
* db_state = DB_SEARCH (see dbuf_free_range for an example).
*/
avl_tree_t dn_dbufs;

/* protected by dn_struct_rwlock */
struct dmu_buf_impl *dn_bonus; /* bonus buffer dbuf */
Expand Down

0 comments on commit 86bb58a

Please sign in to comment.