/* * Attempt to expire an automounted snapshot, unmounts are attempted every * 'zfs_expire_snapshot' seconds until they succeed. The work request is * responsible for rescheduling itself and freeing the zfs_expire_snapshot_t. */ static void zfsctl_expire_snapshot(void *data) { zfs_snapentry_t *sep = (zfs_snapentry_t *)data; zfs_sb_t *zsb = ITOZSB(sep->se_inode); int error; error = zfsctl_unmount_snapshot(zsb, sep->se_name, MNT_EXPIRE); if (error == EBUSY) sep->se_taskqid = taskq_dispatch_delay(zfs_expire_taskq, zfsctl_expire_snapshot, sep, TQ_SLEEP, ddi_get_lbolt() + zfs_expire_snapshot * HZ); }
int zfsctl_mount_snapshot(struct path *path, int flags) { struct dentry *dentry = path->dentry; struct inode *ip = dentry->d_inode; zfs_sb_t *zsb = ITOZSB(ip); char *full_name, *full_path; zfs_snapentry_t *sep; zfs_snapentry_t search; char *argv[] = { "/bin/sh", "-c", NULL, NULL }; char *envp[] = { NULL }; int error; ZFS_ENTER(zsb); full_name = kmem_zalloc(MAXNAMELEN, KM_SLEEP); full_path = kmem_zalloc(PATH_MAX, KM_SLEEP); error = zfsctl_snapshot_zname(ip, dname(dentry), MAXNAMELEN, full_name); if (error) goto error; error = zfsctl_snapshot_zpath(path, PATH_MAX, full_path); if (error) goto error; /* * Attempt to mount the snapshot from user space. Normally this * would be done using the vfs_kern_mount() function, however that * function is marked GPL-only and cannot be used. On error we * careful to log the real error to the console and return EISDIR * to safely abort the automount. This should be very rare. */ argv[2] = kmem_asprintf(SET_MOUNT_CMD, full_name, full_path); error = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC); strfree(argv[2]); if (error) { printk("ZFS: Unable to automount %s at %s: %d\n", full_name, full_path, error); error = EISDIR; goto error; } mutex_enter(&zsb->z_ctldir_lock); /* * Ensure a previous entry does not exist, if it does safely remove * it any cancel the outstanding expiration. This can occur when a * snapshot is manually unmounted and then an automount is triggered. */ search.se_name = full_name; sep = avl_find(&zsb->z_ctldir_snaps, &search, NULL); if (sep) { avl_remove(&zsb->z_ctldir_snaps, sep); taskq_cancel_id(zfs_expire_taskq, sep->se_taskqid); zfsctl_sep_free(sep); } sep = zfsctl_sep_alloc(); sep->se_name = full_name; sep->se_path = full_path; sep->se_inode = ip; avl_add(&zsb->z_ctldir_snaps, sep); sep->se_taskqid = taskq_dispatch_delay(zfs_expire_taskq, zfsctl_expire_snapshot, sep, TQ_SLEEP, ddi_get_lbolt() + zfs_expire_snapshot * HZ); mutex_exit(&zsb->z_ctldir_lock); error: if (error) { kmem_free(full_name, MAXNAMELEN); kmem_free(full_path, PATH_MAX); } ZFS_EXIT(zsb); return (error); }
static int splat_taskq_test10(struct file *file, void *arg) { taskq_t *tq; splat_taskq_arg_t **tqas; atomic_t count; int i, j, rc = 0; int minalloc = 1; int maxalloc = 10; int nr_tasks = 100; int canceled = 0; int completed = 0; int blocked = 0; clock_t start, cancel; tqas = vmalloc(sizeof(*tqas) * nr_tasks); if (tqas == NULL) return -ENOMEM; memset(tqas, 0, sizeof(*tqas) * nr_tasks); splat_vprint(file, SPLAT_TASKQ_TEST10_NAME, "Taskq '%s' creating (%s dispatch) (%d/%d/%d)\n", SPLAT_TASKQ_TEST10_NAME, "delay", minalloc, maxalloc, nr_tasks); if ((tq = taskq_create(SPLAT_TASKQ_TEST10_NAME, 3, maxclsyspri, minalloc, maxalloc, TASKQ_PREPOPULATE)) == NULL) { splat_vprint(file, SPLAT_TASKQ_TEST10_NAME, "Taskq '%s' create failed\n", SPLAT_TASKQ_TEST10_NAME); rc = -EINVAL; goto out_free; } atomic_set(&count, 0); for (i = 0; i < nr_tasks; i++) { splat_taskq_arg_t *tq_arg; uint32_t rnd; /* A random timeout in jiffies of at most 5 seconds */ get_random_bytes((void *)&rnd, 4); rnd = rnd % (5 * HZ); tq_arg = kmem_alloc(sizeof(splat_taskq_arg_t), KM_SLEEP); tq_arg->file = file; tq_arg->name = SPLAT_TASKQ_TEST10_NAME; tq_arg->count = &count; tqas[i] = tq_arg; /* * Dispatch every 1/3 one immediately to mix it up, the cancel * code is inherently racy and we want to try and provoke any * subtle concurrently issues. */ if ((i % 3) == 0) { tq_arg->expire = ddi_get_lbolt(); tq_arg->id = taskq_dispatch(tq, splat_taskq_test10_func, tq_arg, TQ_SLEEP); } else { tq_arg->expire = ddi_get_lbolt() + rnd; tq_arg->id = taskq_dispatch_delay(tq, splat_taskq_test10_func, tq_arg, TQ_SLEEP, ddi_get_lbolt() + rnd); } if (tq_arg->id == 0) { splat_vprint(file, SPLAT_TASKQ_TEST10_NAME, "Taskq '%s' dispatch failed\n", SPLAT_TASKQ_TEST10_NAME); kmem_free(tq_arg, sizeof(splat_taskq_arg_t)); taskq_wait(tq); rc = -EINVAL; goto out; } else { splat_vprint(file, SPLAT_TASKQ_TEST10_NAME, "Taskq '%s' dispatch %lu in %lu jiffies\n", SPLAT_TASKQ_TEST10_NAME, (unsigned long)tq_arg->id, !(i % 3) ? 0 : tq_arg->expire - ddi_get_lbolt()); } } /* * Start randomly canceling tasks for the duration of the test. We * happen to know the valid task id's will be in the range 1..nr_tasks * because the taskq is private and was just created. However, we * have no idea of a particular task has already executed or not. */ splat_vprint(file, SPLAT_TASKQ_TEST10_NAME, "Taskq '%s' randomly " "canceling task ids\n", SPLAT_TASKQ_TEST10_NAME); start = ddi_get_lbolt(); i = 0; while (ddi_time_before(ddi_get_lbolt(), start + 5 * HZ)) { taskqid_t id; uint32_t rnd; i++; cancel = ddi_get_lbolt(); get_random_bytes((void *)&rnd, 4); id = 1 + (rnd % nr_tasks); rc = taskq_cancel_id(tq, id); /* * Keep track of the results of the random cancels. */ if (rc == 0) { canceled++; } else if (rc == ENOENT) { completed++; } else if (rc == EBUSY) { blocked++; } else { rc = -EINVAL; break; } /* * Verify we never get blocked to long in taskq_cancel_id(). * The worst case is 10ms if we happen to cancel the task * which is currently executing. We allow a factor of 2x. */ if (ddi_get_lbolt() - cancel > HZ / 50) { splat_vprint(file, SPLAT_TASKQ_TEST10_NAME, "Taskq '%s' cancel for %lu took %lu\n", SPLAT_TASKQ_TEST10_NAME, (unsigned long)id, ddi_get_lbolt() - cancel); rc = -ETIMEDOUT; break; } get_random_bytes((void *)&rnd, 4); msleep(1 + (rnd % 100)); rc = 0; } taskq_wait(tq); /* * Cross check the results of taskq_cancel_id() with the number of * times the dispatched function actually ran successfully. */ if ((rc == 0) && (nr_tasks - canceled != atomic_read(&count))) rc = -EDOM; splat_vprint(file, SPLAT_TASKQ_TEST10_NAME, "Taskq '%s' %d attempts, " "%d canceled, %d completed, %d blocked, %d/%d tasks run\n", SPLAT_TASKQ_TEST10_NAME, i, canceled, completed, blocked, atomic_read(&count), nr_tasks); splat_vprint(file, SPLAT_TASKQ_TEST10_NAME, "Taskq '%s' destroying %d\n", SPLAT_TASKQ_TEST10_NAME, rc); out: taskq_destroy(tq); out_free: for (j = 0; j < nr_tasks && tqas[j] != NULL; j++) kmem_free(tqas[j], sizeof(splat_taskq_arg_t)); vfree(tqas); return rc; }
if (taskq_cancel_id(zfs_expire_taskq, se->se_taskqid) == 0) { se->se_taskqid = -1; zfsctl_snapshot_rele(se); } } /* * Dispatch the unmount task for delayed handling with a hold protecting it. */ static void zfsctl_snapshot_unmount_delay_impl(zfs_snapentry_t *se, int delay) { ASSERT3S(se->se_taskqid, ==, -1); se->se_taskqid = taskq_dispatch_delay(zfs_expire_taskq, snapentry_expire, se, TQ_SLEEP, ddi_get_lbolt() + delay * HZ); zfsctl_snapshot_hold(se); } /* * Schedule an automatic unmount of objset id to occur in delay seconds from * now. Any previous delayed unmount will be cancelled in favor of the * updated deadline. A reference is taken by zfsctl_snapshot_find_by_name() * and held until the outstanding task is handled or cancelled. */ int zfsctl_snapshot_unmount_delay(uint64_t objsetid, int delay) { zfs_snapentry_t *se; int error = ENOENT;
static int splat_taskq_test9(struct file *file, void *arg) { taskq_t *tq; atomic_t count; int i, rc = 0; int minalloc = 1; int maxalloc = 10; int nr_tasks = 100; splat_vprint(file, SPLAT_TASKQ_TEST9_NAME, "Taskq '%s' creating (%s dispatch) (%d/%d/%d)\n", SPLAT_TASKQ_TEST9_NAME, "delay", minalloc, maxalloc, nr_tasks); if ((tq = taskq_create(SPLAT_TASKQ_TEST9_NAME, 3, maxclsyspri, minalloc, maxalloc, TASKQ_PREPOPULATE)) == NULL) { splat_vprint(file, SPLAT_TASKQ_TEST9_NAME, "Taskq '%s' create failed\n", SPLAT_TASKQ_TEST9_NAME); return -EINVAL; } atomic_set(&count, 0); for (i = 1; i <= nr_tasks; i++) { splat_taskq_arg_t *tq_arg; taskqid_t id; uint32_t rnd; /* A random timeout in jiffies of at most 5 seconds */ get_random_bytes((void *)&rnd, 4); rnd = rnd % (5 * HZ); tq_arg = kmem_alloc(sizeof(splat_taskq_arg_t), KM_SLEEP); tq_arg->file = file; tq_arg->name = SPLAT_TASKQ_TEST9_NAME; tq_arg->expire = ddi_get_lbolt() + rnd; tq_arg->count = &count; splat_vprint(file, SPLAT_TASKQ_TEST9_NAME, "Taskq '%s' delay dispatch %u jiffies\n", SPLAT_TASKQ_TEST9_NAME, rnd); id = taskq_dispatch_delay(tq, splat_taskq_test9_func, tq_arg, TQ_SLEEP, ddi_get_lbolt() + rnd); if (id == 0) { splat_vprint(file, SPLAT_TASKQ_TEST9_NAME, "Taskq '%s' delay dispatch failed\n", SPLAT_TASKQ_TEST9_NAME); kmem_free(tq_arg, sizeof(splat_taskq_arg_t)); taskq_wait(tq); rc = -EINVAL; goto out; } } splat_vprint(file, SPLAT_TASKQ_TEST9_NAME, "Taskq '%s' waiting for " "%d delay dispatches\n", SPLAT_TASKQ_TEST9_NAME, nr_tasks); taskq_wait(tq); if (atomic_read(&count) != nr_tasks) rc = -ERANGE; splat_vprint(file, SPLAT_TASKQ_TEST9_NAME, "Taskq '%s' %d/%d delay " "dispatches finished on time\n", SPLAT_TASKQ_TEST9_NAME, atomic_read(&count), nr_tasks); splat_vprint(file, SPLAT_TASKQ_TEST9_NAME, "Taskq '%s' destroying\n", SPLAT_TASKQ_TEST9_NAME); out: taskq_destroy(tq); return rc; }