diff --git a/kmod/src/alloc.c b/kmod/src/alloc.c index 0ceaf3b8..e33a3e04 100644 --- a/kmod/src/alloc.c +++ b/kmod/src/alloc.c @@ -24,6 +24,7 @@ #include "trans.h" #include "alloc.h" #include "counters.h" +#include "msg.h" #include "scoutfs_trace.h" /* @@ -496,10 +497,11 @@ static int dirty_alloc_blocks(struct super_block *sb, struct scoutfs_block *fr_bl = NULL; struct scoutfs_block *bl; bool link_orig = false; + __le32 orig_first_nr; u64 av_peek; - u64 av_old; + u64 av_old = 0; u64 fr_peek; - u64 fr_old; + u64 fr_old = 0; int ret; if (alloc->dirty_avail_bl != NULL) @@ -509,6 +511,7 @@ static int dirty_alloc_blocks(struct super_block *sb, /* undo dirty freed if we get an error after */ orig_freed = alloc->freed.ref; + orig_first_nr = alloc->freed.first_nr; if (alloc->dirty_avail_bl != NULL) { ret = 0; @@ -562,6 +565,17 @@ static int dirty_alloc_blocks(struct super_block *sb, /* sort dirty avail to encourage contiguous sorted meta blocks */ list_block_sort(av_bl->data); + lblk = fr_bl->data; + if (WARN_ON_ONCE(alloc->freed.ref.blkno != lblk->hdr.blkno)) { + scoutfs_err(sb, "dirty_alloc freed ref %llu hdr %llu av_old %llu fr_old %llu av_peek %llu fr_peek %llu link_orig %d", + le64_to_cpu(alloc->freed.ref.blkno), + le64_to_cpu(lblk->hdr.blkno), + av_old, fr_old, av_peek, fr_peek, link_orig); + ret = -EIO; + goto out; + } + lblk = NULL; + if (av_old) list_block_add(&alloc->freed, fr_bl->data, av_old); if (fr_old) @@ -578,6 +592,7 @@ static int dirty_alloc_blocks(struct super_block *sb, if (fr_bl) scoutfs_block_writer_forget(sb, wri, fr_bl); alloc->freed.ref = orig_freed; + alloc->freed.first_nr = orig_first_nr; } mutex_unlock(&alloc->mutex); diff --git a/kmod/src/block.c b/kmod/src/block.c index 463e1b01..64712b47 100644 --- a/kmod/src/block.c +++ b/kmod/src/block.c @@ -624,11 +624,15 @@ static struct block_private *block_read(struct super_block *sb, u64 blkno) if (!test_bit(BLOCK_BIT_UPTODATE, &bp->bits) && test_and_clear_bit(BLOCK_BIT_NEW, &bp->bits)) { ret = block_submit_bio(sb, bp, REQ_OP_READ); - if (ret < 0) + if (ret < 0) { + set_bit(BLOCK_BIT_ERROR, &bp->bits); goto out; + } } - wait_event(binf->waitq, uptodate_or_error(bp)); + while (!wait_event_timeout(binf->waitq, uptodate_or_error(bp), 120 * HZ)) + WARN(1, "block read blkno %llu waiting for bio completion\n", + bp->bl.blkno); if (test_bit(BLOCK_BIT_ERROR, &bp->bits)) ret = -EIO; else @@ -836,6 +840,8 @@ int scoutfs_block_dirty_ref(struct super_block *sb, struct scoutfs_alloc *alloc, bp = BLOCK_PRIVATE(bl); if (block_is_dirty(bp)) { + if (ref_blkno) + *ref_blkno = 0; ret = 0; goto out; } diff --git a/kmod/src/client.c b/kmod/src/client.c index cffd2bfa..246616c3 100644 --- a/kmod/src/client.c +++ b/kmod/src/client.c @@ -646,8 +646,12 @@ void scoutfs_client_destroy(struct super_block *sb) client_farewell_response, NULL, NULL); if (ret == 0) { - wait_for_completion(&client->farewell_comp); - ret = client->farewell_error; + if (!wait_for_completion_timeout(&client->farewell_comp, + 120 * HZ)) { + ret = -ETIMEDOUT; + } else { + ret = client->farewell_error; + } } if (ret) { scoutfs_inc_counter(sb, client_farewell_error); diff --git a/kmod/src/lock.c b/kmod/src/lock.c index 63213874..e653cdc4 100644 --- a/kmod/src/lock.c +++ b/kmod/src/lock.c @@ -71,6 +71,8 @@ * relative to that lock state we resend. */ +#define CLIENT_LOCK_WAIT_TIMEOUT (60 * HZ) + /* * allocated per-super, freed on unmount. */ @@ -157,6 +159,33 @@ static void invalidate_inode(struct super_block *sb, u64 ino) } } +/* + * Remove all coverage items from the lock to tell users that their + * cache is stale. This is lock-internal bookkeeping that is safe to + * call during shutdown and unmount. The unconditional unlock/relock + * of cov_list_lock avoids sparse warnings from unbalanced locking in + * the trylock failure path. + */ +static void lock_clear_coverage(struct super_block *sb, + struct scoutfs_lock *lock) +{ + struct scoutfs_lock_coverage *cov; + + spin_lock(&lock->cov_list_lock); + while ((cov = list_first_entry_or_null(&lock->cov_list, + struct scoutfs_lock_coverage, head))) { + if (spin_trylock(&cov->cov_lock)) { + list_del_init(&cov->head); + cov->lock = NULL; + spin_unlock(&cov->cov_lock); + scoutfs_inc_counter(sb, lock_invalidate_coverage); + } + spin_unlock(&lock->cov_list_lock); + spin_lock(&lock->cov_list_lock); + } + spin_unlock(&lock->cov_list_lock); +} + /* * Invalidate caches associated with this lock. Either we're * invalidating a write to a read or we're invalidating to null. We @@ -166,7 +195,6 @@ static void invalidate_inode(struct super_block *sb, u64 ino) static int lock_invalidate(struct super_block *sb, struct scoutfs_lock *lock, enum scoutfs_lock_mode prev, enum scoutfs_lock_mode mode) { - struct scoutfs_lock_coverage *cov; u64 ino, last; int ret = 0; @@ -190,24 +218,7 @@ static int lock_invalidate(struct super_block *sb, struct scoutfs_lock *lock, /* have to invalidate if we're not in the only usable case */ if (!(prev == SCOUTFS_LOCK_WRITE && mode == SCOUTFS_LOCK_READ)) { - /* - * Remove cov items to tell users that their cache is - * stale. The unlock pattern comes from avoiding bad - * sparse warnings when taking else in a failed trylock. - */ - spin_lock(&lock->cov_list_lock); - while ((cov = list_first_entry_or_null(&lock->cov_list, - struct scoutfs_lock_coverage, head))) { - if (spin_trylock(&cov->cov_lock)) { - list_del_init(&cov->head); - cov->lock = NULL; - spin_unlock(&cov->cov_lock); - scoutfs_inc_counter(sb, lock_invalidate_coverage); - } - spin_unlock(&lock->cov_list_lock); - spin_lock(&lock->cov_list_lock); - } - spin_unlock(&lock->cov_list_lock); + lock_clear_coverage(sb, lock); /* invalidate inodes after removing coverage so drop/evict aren't covered */ if (lock->start.sk_zone == SCOUTFS_FS_ZONE) { @@ -714,10 +725,12 @@ static void lock_invalidate_worker(struct work_struct *work) ireq = list_first_entry(&lock->inv_list, struct inv_req, head); nl = &ireq->nl; - /* only lock protocol, inv can't call subsystems after shutdown */ - if (!linfo->shutdown) { + /* only lock protocol, inv can't call subsystems after shutdown or unmount */ + if (!linfo->shutdown && !scoutfs_unmounting(sb)) { ret = lock_invalidate(sb, lock, nl->old_mode, nl->new_mode); BUG_ON(ret < 0 && ret != -ENOLINK); + } else { + lock_clear_coverage(sb, lock); } /* respond with the key and modes from the request, server might have died */ @@ -953,6 +966,9 @@ static bool lock_wait_cond(struct super_block *sb, struct scoutfs_lock *lock, !lock->request_pending; spin_unlock(&linfo->lock); + if (!wake) + wake = scoutfs_unmounting(sb); + if (!wake) scoutfs_inc_counter(sb, lock_wait); @@ -997,8 +1013,10 @@ static int lock_key_range(struct super_block *sb, enum scoutfs_lock_mode mode, i return -EINVAL; /* maybe catch _setup() and _shutdown order mistakes */ - if (WARN_ON_ONCE(!linfo || linfo->shutdown)) + if (!linfo || linfo->shutdown) { + WARN_ON_ONCE(!scoutfs_unmounting(sb)); return -ENOLCK; + } /* have to lock before entering transactions */ if (WARN_ON_ONCE(scoutfs_trans_held())) @@ -1024,6 +1042,11 @@ static int lock_key_range(struct super_block *sb, enum scoutfs_lock_mode mode, i break; } + if (scoutfs_unmounting(sb)) { + ret = -ESHUTDOWN; + break; + } + /* the fast path where we can use the granted mode */ if (lock_modes_match(lock->mode, mode)) { lock_inc_count(lock->users, mode); @@ -1067,8 +1090,9 @@ static int lock_key_range(struct super_block *sb, enum scoutfs_lock_mode mode, i if (flags & SCOUTFS_LKF_INTERRUPTIBLE) { ret = wait_event_interruptible(lock->waitq, lock_wait_cond(sb, lock, mode)); - } else { - wait_event(lock->waitq, lock_wait_cond(sb, lock, mode)); + } else if (!wait_event_timeout(lock->waitq, + lock_wait_cond(sb, lock, mode), + CLIENT_LOCK_WAIT_TIMEOUT)) { ret = 0; } @@ -1650,6 +1674,7 @@ void scoutfs_lock_destroy(struct super_block *sb) list_del_init(&lock->inv_head); lock->invalidate_pending = 0; } + lock_clear_coverage(sb, lock); lock_remove(linfo, lock); lock_free(linfo, lock); } diff --git a/kmod/src/net.c b/kmod/src/net.c index 8e7cdb4c..11ef1dab 100644 --- a/kmod/src/net.c +++ b/kmod/src/net.c @@ -1990,8 +1990,9 @@ static int sync_response(struct super_block *sb, * buffer. Errors returned can come from the remote request processing * or local failure to send. * - * The wait for the response is interruptible and can return - * -ERESTARTSYS if it is interrupted. + * The wait for the response uses a 60 second timeout loop that + * checks for unmount, returning -ESHUTDOWN if the mount is + * being torn down. * * -EOVERFLOW is returned if the response message's data_length doesn't * match the caller's resp_len buffer. @@ -2002,6 +2003,7 @@ int scoutfs_net_sync_request(struct super_block *sb, void *resp, size_t resp_len) { struct sync_request_completion sreq; + struct message_send *msend; int ret; u64 id; @@ -2014,8 +2016,21 @@ int scoutfs_net_sync_request(struct super_block *sb, sync_response, &sreq, &id); if (ret == 0) { - wait_for_completion(&sreq.comp); - ret = sreq.error; + while (!wait_for_completion_timeout(&sreq.comp, 60 * HZ)) { + if (scoutfs_unmounting(sb)) { + ret = -ESHUTDOWN; + break; + } + } + if (ret == -ESHUTDOWN) { + spin_lock(&conn->lock); + msend = find_request(conn, cmd, id); + if (msend) + queue_dead_free(conn, msend); + spin_unlock(&conn->lock); + } else { + ret = sreq.error; + } } return ret; diff --git a/kmod/src/server.c b/kmod/src/server.c index e4791980..d1c808c5 100644 --- a/kmod/src/server.c +++ b/kmod/src/server.c @@ -638,7 +638,7 @@ static void scoutfs_server_commit_func(struct work_struct *work) ret = scoutfs_alloc_empty_list(sb, &server->alloc, &server->wri, server->meta_freed, server->other_freed); - if (ret) { + if (ret && ret != -ENOLINK) { scoutfs_err(sb, "server error emptying freed: %d", ret); goto out; } diff --git a/kmod/src/trans.c b/kmod/src/trans.c index d131bfa1..d4ff3645 100644 --- a/kmod/src/trans.c +++ b/kmod/src/trans.c @@ -195,7 +195,8 @@ static int retry_forever(struct super_block *sb, int (*func)(struct super_block retrying = true; } - if (scoutfs_forcing_unmount(sb)) { + if (scoutfs_forcing_unmount(sb) || + scoutfs_unmounting(sb)) { ret = -ENOLINK; break; }