mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-25 03:37:45 +03:00
Add support for parallel pool exports
Changed spa_export_common() such that it no longer holds the spa_namespace_lock for the entire duration and instead sets spa_export_thread to indicate an import is in progress on the spa. This allows for an export to a diffent pool to proceed in parallel while an export is still processing potentially long operations like spa_unload_log_sm_flush_all(). Calls like spa_lookup() and spa_vdev_enter() that rely on the spa_namespace_lock to serialize them against a concurrent export, now wait for any in-progress export thread to complete before proceeding. The 'zpool import -a' sub-command also provides multi-threaded support, using a thread pool to submit the exports in parallel. Sponsored-By: Klara Inc. Sponsored-by: Wasabi Technology, Inc. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: George Wilson <gwilson@delphix.com> Signed-off-by: Don Brady <don.brady@klarasystems.com> Closes #16153
This commit is contained in:
committed by
Brian Behlendorf
parent
abec7dcd30
commit
975a13259b
+40
-10
@@ -27,7 +27,7 @@
|
||||
* Copyright (c) 2017 Datto Inc.
|
||||
* Copyright (c) 2017, Intel Corporation.
|
||||
* Copyright (c) 2019, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
|
||||
* Copyright (c) 2023, Klara Inc.
|
||||
* Copyright (c) 2023, 2024, Klara Inc.
|
||||
*/
|
||||
|
||||
#include <sys/zfs_context.h>
|
||||
@@ -82,8 +82,8 @@
|
||||
* - Check if spa_refcount is zero
|
||||
* - Rename a spa_t
|
||||
* - add/remove/attach/detach devices
|
||||
* - Held for the duration of create/destroy/export
|
||||
* - Held at the start and end of import
|
||||
* - Held for the duration of create/destroy
|
||||
* - Held at the start and end of import and export
|
||||
*
|
||||
* It does not need to handle recursion. A create or destroy may
|
||||
* reference objects (files or zvols) in other pools, but by
|
||||
@@ -636,8 +636,14 @@ retry:
|
||||
if (spa == NULL)
|
||||
return (NULL);
|
||||
|
||||
if (spa->spa_load_thread != NULL &&
|
||||
spa->spa_load_thread != curthread) {
|
||||
/*
|
||||
* Avoid racing with import/export, which don't hold the namespace
|
||||
* lock for their entire duration.
|
||||
*/
|
||||
if ((spa->spa_load_thread != NULL &&
|
||||
spa->spa_load_thread != curthread) ||
|
||||
(spa->spa_export_thread != NULL &&
|
||||
spa->spa_export_thread != curthread)) {
|
||||
cv_wait(&spa_namespace_cv, &spa_namespace_lock);
|
||||
goto retry;
|
||||
}
|
||||
@@ -950,14 +956,15 @@ spa_open_ref(spa_t *spa, const void *tag)
|
||||
|
||||
/*
|
||||
* Remove a reference to the given spa_t. Must have at least one reference, or
|
||||
* have the namespace lock held.
|
||||
* have the namespace lock held or be part of a pool import/export.
|
||||
*/
|
||||
void
|
||||
spa_close(spa_t *spa, const void *tag)
|
||||
{
|
||||
ASSERT(zfs_refcount_count(&spa->spa_refcount) > spa->spa_minref ||
|
||||
MUTEX_HELD(&spa_namespace_lock) ||
|
||||
spa->spa_load_thread == curthread);
|
||||
spa->spa_load_thread == curthread ||
|
||||
spa->spa_export_thread == curthread);
|
||||
(void) zfs_refcount_remove(&spa->spa_refcount, tag);
|
||||
}
|
||||
|
||||
@@ -977,13 +984,15 @@ spa_async_close(spa_t *spa, const void *tag)
|
||||
|
||||
/*
|
||||
* Check to see if the spa refcount is zero. Must be called with
|
||||
* spa_namespace_lock held. We really compare against spa_minref, which is the
|
||||
* number of references acquired when opening a pool
|
||||
* spa_namespace_lock held or be the spa export thread. We really
|
||||
* compare against spa_minref, which is the number of references
|
||||
* acquired when opening a pool
|
||||
*/
|
||||
boolean_t
|
||||
spa_refcount_zero(spa_t *spa)
|
||||
{
|
||||
ASSERT(MUTEX_HELD(&spa_namespace_lock));
|
||||
ASSERT(MUTEX_HELD(&spa_namespace_lock) ||
|
||||
spa->spa_export_thread == curthread);
|
||||
|
||||
return (zfs_refcount_count(&spa->spa_refcount) == spa->spa_minref);
|
||||
}
|
||||
@@ -1231,6 +1240,21 @@ spa_vdev_enter(spa_t *spa)
|
||||
mutex_enter(&spa->spa_vdev_top_lock);
|
||||
mutex_enter(&spa_namespace_lock);
|
||||
|
||||
/*
|
||||
* We have a reference on the spa and a spa export could be
|
||||
* starting but no longer holding the spa_namespace_lock. So
|
||||
* check if there is an export and if so wait. It will fail
|
||||
* fast (EBUSY) since we are still holding a spa reference.
|
||||
*
|
||||
* Note that we can be woken by a different spa transitioning
|
||||
* through an import/export, so we must wait for our condition
|
||||
* to change before proceeding.
|
||||
*/
|
||||
while (spa->spa_export_thread != NULL &&
|
||||
spa->spa_export_thread != curthread) {
|
||||
cv_wait(&spa_namespace_cv, &spa_namespace_lock);
|
||||
}
|
||||
|
||||
vdev_autotrim_stop_all(spa);
|
||||
|
||||
return (spa_vdev_config_enter(spa));
|
||||
@@ -1248,6 +1272,12 @@ spa_vdev_detach_enter(spa_t *spa, uint64_t guid)
|
||||
mutex_enter(&spa->spa_vdev_top_lock);
|
||||
mutex_enter(&spa_namespace_lock);
|
||||
|
||||
/* See comment in spa_vdev_enter() */
|
||||
while (spa->spa_export_thread != NULL &&
|
||||
spa->spa_export_thread != curthread) {
|
||||
cv_wait(&spa_namespace_cv, &spa_namespace_lock);
|
||||
}
|
||||
|
||||
vdev_autotrim_stop_all(spa);
|
||||
|
||||
if (guid != 0) {
|
||||
|
||||
Reference in New Issue
Block a user