Skip to content

Commit

Permalink
Snap -r with existing snaps can cause panic
Browse files Browse the repository at this point in the history
Certain arrangment of existing snaps with same
name as specified in new snap -r command can
create a situation during sync phase with entries
in dp_dirty_dirs but no dirty dbufs in the MOS.

Also when zfs tests are destroying datasets and then
the pool there is a race condition between
dmu_objset_evict and dbuf_evict_one, resulting in
a hang.

Signed-off-by: Paul Zuchowski <[email protected]>
Fixes openzfs#8380
  • Loading branch information
PaulZ-98 committed Jun 14, 2019
1 parent 4c0883f commit 577f4fe
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 11 deletions.
2 changes: 2 additions & 0 deletions include/sys/dbuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ void dbuf_rele_and_unlock(dmu_buf_impl_t *db, void *tag, boolean_t evicting);

dmu_buf_impl_t *dbuf_find(struct objset *os, uint64_t object, uint8_t level,
uint64_t blkid);
dmu_buf_impl_t *dbuf_find_evicting(struct objset *os, uint64_t object,
uint8_t level, uint64_t blkid);

int dbuf_read(dmu_buf_impl_t *db, zio_t *zio, uint32_t flags);
void dmu_buf_will_not_fill(dmu_buf_t *db, dmu_tx_t *tx);
Expand Down
2 changes: 2 additions & 0 deletions include/sys/dsl_dataset.h
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,8 @@ void dsl_dataset_clone_swap_sync_impl(dsl_dataset_t *clone,
dsl_dataset_t *origin_head, dmu_tx_t *tx);
int dsl_dataset_snapshot_check_impl(dsl_dataset_t *ds, const char *snapname,
dmu_tx_t *tx, boolean_t recv, uint64_t cnt, cred_t *cr);
int dsl_dataset_snapshot_reserve_space(dsl_dataset_t *ds, dmu_tx_t *tx);

void dsl_dataset_snapshot_sync_impl(dsl_dataset_t *ds, const char *snapname,
dmu_tx_t *tx);

Expand Down
23 changes: 20 additions & 3 deletions module/zfs/dbuf.c
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,9 @@ dbuf_hash(void *os, uint64_t obj, uint8_t lvl, uint64_t blkid)
(dbuf)->db_level == (level) && \
(dbuf)->db_blkid == (blkid))

dmu_buf_impl_t *
dbuf_find(objset_t *os, uint64_t obj, uint8_t level, uint64_t blkid)
static dmu_buf_impl_t *
dbuf_find_impl(objset_t *os, uint64_t obj, uint8_t level, uint64_t blkid,
boolean_t find_evicting)
{
dbuf_hash_table_t *h = &dbuf_hash_table;
uint64_t hv;
Expand All @@ -341,9 +342,12 @@ dbuf_find(objset_t *os, uint64_t obj, uint8_t level, uint64_t blkid)

mutex_enter(DBUF_HASH_MUTEX(h, idx));
for (db = h->hash_table[idx]; db != NULL; db = db->db_hash_next) {
if (db->db_state != DB_EVICTING && find_evicting)
continue;
if (DBUF_EQUAL(db, os, obj, level, blkid)) {
mutex_enter(&db->db_mtx);
if (db->db_state != DB_EVICTING) {
if ((db->db_state == DB_EVICTING && find_evicting) ||
(db->db_state != DB_EVICTING && !find_evicting)) {
mutex_exit(DBUF_HASH_MUTEX(h, idx));
return (db);
}
Expand All @@ -354,6 +358,19 @@ dbuf_find(objset_t *os, uint64_t obj, uint8_t level, uint64_t blkid)
return (NULL);
}

dmu_buf_impl_t *
dbuf_find_evicting(objset_t *os, uint64_t obj, uint8_t level,
uint64_t blkid)
{
return (dbuf_find_impl(os, obj, level, blkid, B_TRUE));
}

dmu_buf_impl_t *
dbuf_find(objset_t *os, uint64_t obj, uint8_t level, uint64_t blkid)
{
return (dbuf_find_impl(os, obj, level, blkid, B_FALSE));
}

static dmu_buf_impl_t *
dbuf_find_bonus(objset_t *os, uint64_t object)
{
Expand Down
9 changes: 9 additions & 0 deletions module/zfs/dmu_recv.c
Original file line number Diff line number Diff line change
Expand Up @@ -2681,6 +2681,11 @@ dmu_recv_end_check(void *arg, dmu_tx_t *tx)
}
error = dsl_dataset_snapshot_check_impl(origin_head,
drc->drc_tosnap, tx, B_TRUE, 1, drc->drc_cred);
if (error != 0) {
dsl_dataset_rele(origin_head, FTAG);
return (error);
}
error = dsl_dataset_snapshot_reserve_space(origin_head, tx);
dsl_dataset_rele(origin_head, FTAG);
if (error != 0)
return (error);
Expand All @@ -2689,6 +2694,10 @@ dmu_recv_end_check(void *arg, dmu_tx_t *tx)
} else {
error = dsl_dataset_snapshot_check_impl(drc->drc_ds,
drc->drc_tosnap, tx, B_TRUE, 1, drc->drc_cred);
if (error != 0)
return (error);

error = dsl_dataset_snapshot_reserve_space(drc->drc_ds, tx);
}
return (error);
}
Expand Down
19 changes: 19 additions & 0 deletions module/zfs/dnode_sync.c
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ dnode_evict_dbufs(dnode_t *dn)
{
dmu_buf_impl_t *db_marker;
dmu_buf_impl_t *db, *db_next;
dmu_buf_impl_t *evicting_db;

db_marker = kmem_alloc(sizeof (dmu_buf_impl_t), KM_SLEEP);

Expand All @@ -442,6 +443,24 @@ dnode_evict_dbufs(dnode_t *dn)
mutex_enter(&db->db_mtx);
if (db->db_state != DB_EVICTING &&
zfs_refcount_is_zero(&db->db_holds)) {
/*
* It is rare but if dn_dbufs has a dbuf with
* this same bookmark but having db_state of
* DB_EVICTING, we won't be able to insert
* the db_marker. Skip this dbuf if that is
* the case.
*/
evicting_db = dbuf_find_evicting(dn->dn_objset,
dn->dn_object, db->db_level, db->db_blkid);

if (evicting_db != NULL) {
mutex_exit(&evicting_db->db_mtx);
db->db_pending_evict = TRUE;
mutex_exit(&db->db_mtx);
db_next = AVL_NEXT(&dn->dn_dbufs, db);
continue;
}

db_marker->db_level = db->db_level;
db_marker->db_blkid = db->db_blkid;
db_marker->db_state = DB_SEARCH;
Expand Down
57 changes: 51 additions & 6 deletions module/zfs/dsl_dataset.c
Original file line number Diff line number Diff line change
Expand Up @@ -1339,7 +1339,7 @@ dsl_dataset_dirty(dsl_dataset_t *ds, dmu_tx_t *tx)
}
}

static int
int
dsl_dataset_snapshot_reserve_space(dsl_dataset_t *ds, dmu_tx_t *tx)
{
uint64_t asize;
Expand Down Expand Up @@ -1419,18 +1419,20 @@ dsl_dataset_snapshot_check_impl(dsl_dataset_t *ds, const char *snapname,
return (error);
}

error = dsl_dataset_snapshot_reserve_space(ds, tx);
if (error != 0)
return (error);

return (0);
}

struct checknode {
list_node_t link;
dsl_dataset_t *ds;
};

int
dsl_dataset_snapshot_check(void *arg, dmu_tx_t *tx)
{
dsl_dataset_snapshot_arg_t *ddsa = arg;
dsl_pool_t *dp = dmu_tx_pool(tx);
struct checknode *chknode;
nvpair_t *pair;
int rv = 0;

Expand Down Expand Up @@ -1531,6 +1533,9 @@ dsl_dataset_snapshot_check(void *arg, dmu_tx_t *tx)
nvlist_free(cnt_track);
}

list_t datasets;
list_create(&datasets, sizeof (struct checknode),
offsetof(struct checknode, link));
for (pair = nvlist_next_nvpair(ddsa->ddsa_snaps, NULL);
pair != NULL; pair = nvlist_next_nvpair(ddsa->ddsa_snaps, pair)) {
int error = 0;
Expand All @@ -1554,7 +1559,6 @@ dsl_dataset_snapshot_check(void *arg, dmu_tx_t *tx)
/* passing 0/NULL skips dsl_fs_ss_limit_check */
error = dsl_dataset_snapshot_check_impl(ds,
atp + 1, tx, B_FALSE, 0, NULL);
dsl_dataset_rele(ds, FTAG);
}

if (error != 0) {
Expand All @@ -1564,6 +1568,42 @@ dsl_dataset_snapshot_check(void *arg, dmu_tx_t *tx)
}
rv = error;
}
chknode = kmem_alloc(sizeof (*chknode), KM_SLEEP);
chknode->ds = ds;
list_insert_tail(&datasets, chknode);
}
if (rv != 0) {
while ((chknode = list_remove_head(&datasets)) != NULL) {
dsl_dataset_rele(chknode->ds, FTAG);
kmem_free(chknode, sizeof (*chknode));
}
list_destroy(&datasets);
return (rv);
}

/* Now reserve space since we passed all the checks. */
while ((chknode = list_remove_head(&datasets)) != NULL) {
int error = 0;

/*
* This accumulates dsl_dir_t dd_space_to_write which may
* need to be undone if we get ENOSPC while reserving.
*/
error = dsl_dataset_snapshot_reserve_space(chknode->ds, tx);
dsl_dataset_rele(chknode->ds, FTAG);
kmem_free(chknode, sizeof (*chknode));
if (error != 0)
rv = error;
}
list_destroy(&datasets);
if (rv == 0)
return (rv);

ASSERT3U(rv, ==, ENOSPC);
dsl_dir_t *dd;
/* Undo the reserved space since no snapshot will be taken. */
while ((dd = txg_list_remove(&dp->dp_dirty_dirs, tx->tx_txg)) != NULL) {
dsl_dir_sync(dd, tx);
}

return (rv);
Expand Down Expand Up @@ -1863,6 +1903,11 @@ dsl_dataset_snapshot_tmp_check(void *arg, dmu_tx_t *tx)
dsl_dataset_rele(ds, FTAG);
return (error);
}
error = dsl_dataset_snapshot_reserve_space(ds, tx);
if (error != 0) {
dsl_dataset_rele(ds, FTAG);
return (error);
}

dsl_dataset_rele(ds, FTAG);
return (0);
Expand Down
3 changes: 2 additions & 1 deletion tests/runfiles/linux.run
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,8 @@ tests = ['clone_001_pos', 'rollback_001_pos', 'rollback_002_pos',
'snapshot_006_pos', 'snapshot_007_pos', 'snapshot_008_pos',
'snapshot_009_pos', 'snapshot_010_pos', 'snapshot_011_pos',
'snapshot_012_pos', 'snapshot_013_pos', 'snapshot_014_pos',
'snapshot_015_pos', 'snapshot_016_pos', 'snapshot_017_pos']
'snapshot_015_pos', 'snapshot_016_pos', 'snapshot_017_pos',
'snapshot_fail_existing']
tags = ['functional', 'snapshot']

[tests/functional/snapused]
Expand Down
3 changes: 2 additions & 1 deletion tests/zfs-tests/tests/functional/snapshot/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ dist_pkgdata_SCRIPTS = \
snapshot_014_pos.ksh \
snapshot_015_pos.ksh \
snapshot_016_pos.ksh \
snapshot_017_pos.ksh
snapshot_017_pos.ksh \
snapshot_fail_existing.ksh

dist_pkgdata_DATA = \
snapshot.cfg
Loading

0 comments on commit 577f4fe

Please sign in to comment.