diff --git a/kmod/Makefile b/kmod/Makefile index ade36d3e..2963f205 100644 --- a/kmod/Makefile +++ b/kmod/Makefile @@ -22,7 +22,7 @@ SCOUTFS_FORMAT_HASH := \ SCOUTFS_ARGS := SCOUTFS_GIT_DESCRIBE=$(SCOUTFS_GIT_DESCRIBE) \ SCOUTFS_FORMAT_HASH=$(SCOUTFS_FORMAT_HASH) \ CONFIG_SCOUTFS_FS=m -C $(SK_KSRC) M=$(CURDIR)/src \ - EXTRA_CFLAGS="-Werror -DCONFIG_OCFS2_FS_STATS" + EXTRA_CFLAGS="-Werror" all: module diff --git a/kmod/src/Makefile b/kmod/src/Makefile index e44808c5..903d8d29 100644 --- a/kmod/src/Makefile +++ b/kmod/src/Makefile @@ -6,10 +6,10 @@ CFLAGS_super.o = -DSCOUTFS_GIT_DESCRIBE=\"$(SCOUTFS_GIT_DESCRIBE)\" \ CFLAGS_scoutfs_trace.o = -I$(src) # define_trace.h double include scoutfs-y += alloc.o bio.o btree.o client.o compact.o counters.o data.o dir.o \ - dlmglue.o export.o file.o kvec.o inode.o ioctl.o item.o key.o \ - lock.o manifest.o msg.o options.o per_task.o seg.o server.o \ - scoutfs_trace.o sock.o sort_priv.o stackglue.o super.o sysfs.o \ - trans.o triggers.o xattr.o + export.o file.o kvec.o inode.o ioctl.o item.o key.o lock.o \ + manifest.o msg.o options.o per_task.o seg.o server.o \ + scoutfs_trace.o sock.o sort_priv.o super.o sysfs.o trans.o \ + triggers.o xattr.o # # The raw types aren't available in userspace headers. Make sure all diff --git a/kmod/src/counters.h b/kmod/src/counters.h index f0c02f50..57cc8c6c 100644 --- a/kmod/src/counters.h +++ b/kmod/src/counters.h @@ -29,13 +29,6 @@ EXPAND_COUNTER(data_write_begin) \ EXPAND_COUNTER(data_write_end) \ EXPAND_COUNTER(data_writepage) \ - EXPAND_COUNTER(dlm_cancel_convert) \ - EXPAND_COUNTER(dlm_convert_request) \ - EXPAND_COUNTER(dlm_cw_downconvert) \ - EXPAND_COUNTER(dlm_ex_downconvert) \ - EXPAND_COUNTER(dlm_lock_request) \ - EXPAND_COUNTER(dlm_pr_downconvert) \ - EXPAND_COUNTER(dlm_unlock_request) \ EXPAND_COUNTER(item_alloc) \ EXPAND_COUNTER(item_create) \ EXPAND_COUNTER(item_delete) \ @@ -56,12 +49,23 @@ EXPAND_COUNTER(item_shrink_small_split) \ EXPAND_COUNTER(item_shrink_split_range) \ EXPAND_COUNTER(lock_alloc) \ - EXPAND_COUNTER(lock_blocked_wait) \ - EXPAND_COUNTER(lock_busy_wait) \ + EXPAND_COUNTER(lock_ast) \ + EXPAND_COUNTER(lock_ast_edeadlk) \ + EXPAND_COUNTER(lock_ast_error) \ + EXPAND_COUNTER(lock_bast) \ + EXPAND_COUNTER(lock_dlm_call) \ + EXPAND_COUNTER(lock_dlm_call_error) \ EXPAND_COUNTER(lock_free) \ - EXPAND_COUNTER(lock_incompat_wait) \ - EXPAND_COUNTER(lock_type_ino_downconvert) \ - EXPAND_COUNTER(lock_type_idx_downconvert) \ + EXPAND_COUNTER(lock_grace_enforced) \ + EXPAND_COUNTER(lock_grace_expired) \ + EXPAND_COUNTER(lock_grace_extended) \ + EXPAND_COUNTER(lock_invalidate_clean_item) \ + EXPAND_COUNTER(lock_lock) \ + EXPAND_COUNTER(lock_lock_error) \ + EXPAND_COUNTER(lock_nonblock_eagain) \ + EXPAND_COUNTER(lock_shrink) \ + EXPAND_COUNTER(lock_write_dirty_item) \ + EXPAND_COUNTER(lock_unlock) \ EXPAND_COUNTER(manifest_compact_migrate) \ EXPAND_COUNTER(manifest_hard_stale_error) \ EXPAND_COUNTER(seg_alloc) \ diff --git a/kmod/src/data.c b/kmod/src/data.c index 07491291..bf56c45b 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -1076,29 +1076,38 @@ out: return ret; } +/* + * This is almost never used. We can't block on a cluster lock while + * holding the page lock because lock invalidation gets the page lock + * while blocking locks. If we can't use an existing lock then we drop + * the page lock and try again. + */ static int scoutfs_readpage(struct file *file, struct page *page) { struct inode *inode = file->f_inode; struct super_block *sb = inode->i_sb; struct scoutfs_lock *inode_lock = NULL; - int unlock = 1; + int flags; int ret; - ret = scoutfs_lock_inode(sb, DLM_LOCK_PR, SCOUTFS_LKF_REFRESH_INODE | - SCOUTFS_LKF_TRYLOCK, inode, &inode_lock); - if (ret) { - if (ret == -EAGAIN) - ret = AOP_TRUNCATED_PAGE; - goto out; + flags = SCOUTFS_LKF_REFRESH_INODE | SCOUTFS_LKF_NONBLOCK; + ret = scoutfs_lock_inode(sb, DLM_LOCK_PR, flags, inode, &inode_lock); + if (ret < 0) { + unlock_page(page); + if (ret == -EAGAIN) { + flags &= ~SCOUTFS_LKF_NONBLOCK; + ret = scoutfs_lock_inode(sb, DLM_LOCK_PR, flags, inode, + &inode_lock); + if (ret == 0) { + scoutfs_unlock(sb, inode_lock, DLM_LOCK_PR); + ret = AOP_TRUNCATED_PAGE; + } + } + return ret; } ret = mpage_readpage(page, scoutfs_get_block); - unlock = 0; - scoutfs_unlock(sb, inode_lock, DLM_LOCK_PR); -out: - if (unlock) - unlock_page(page); return ret; } diff --git a/kmod/src/dlmglue.c b/kmod/src/dlmglue.c deleted file mode 100644 index d2724322..00000000 --- a/kmod/src/dlmglue.c +++ /dev/null @@ -1,2803 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8; -*- - * vim: noexpandtab sw=8 ts=8 sts=0: - * - * dlmglue.c - * - * Code which implements an OCFS2 specific interface to our DLM. - * - * Copyright (C) 2003, 2004 Oracle. All rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 021110-1307, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "counters.h" -#include "dlmglue.h" - -#include "scoutfs_trace.h" - -#ifdef TRACE_DLMGLUE -#define mlog(mask, fmt, args...) trace_printk(fmt , ##args) -#define mlog_errno(st) do { \ - int _st = (st); \ - if (_st != -ERESTARTSYS && _st != -EINTR && \ - _st != AOP_TRUNCATED_PAGE && _st != -ENOSPC) \ - mlog(ML_ERROR, "status = %lld\n", (long long)_st); \ -} while (0) -#else -#define mlog(mask, fmt, args...) -#define mlog_errno(st) -#endif -#define mlog_bug_on_msg(cond, fmt, args...) do { \ - if (cond) { \ - printk(KERN_ERR "bug expression: " #cond "\n"); \ - printk(KERN_ERR fmt, ##args); \ - BUG(); \ - } \ -} while (0) - -struct ocfs2_mask_waiter { - struct list_head mw_item; - int mw_status; - struct completion mw_complete; - unsigned long mw_mask; - unsigned long mw_goal; -#ifdef CONFIG_OCFS2_FS_STATS - ktime_t mw_lock_start; -#endif -}; - -struct ocfs2_unblock_ctl { - int requeue; - enum ocfs2_unblock_action unblock_action; -}; - -#if 0 && CONFIG_DEBUG_LOCK_ALLOC -/* Lockdep class keys */ -struct lock_class_key lockdep_keys[OCFS2_NUM_LOCK_TYPES]; -#endif - -static inline struct ocfs2_lock_res *ocfs2_lksb_to_lock_res(struct ocfs2_dlm_lksb *lksb) -{ - return container_of(lksb, struct ocfs2_lock_res, l_lksb); -} - -static inline struct ocfs2_super *ocfs2_get_lockres_osb(struct ocfs2_lock_res *lockres) -{ - if (lockres->l_ops->get_osb) - return lockres->l_ops->get_osb(lockres); - - return (struct ocfs2_super *)lockres->l_priv; -} - -static inline void lockres_name(struct ocfs2_lock_res *lockres, char *buf, - unsigned int len) -{ - if (lockres->l_ops->print) - lockres->l_ops->print(lockres, buf, len); - else - snprintf(buf, len, "%s", lockres->l_name); -} - -static inline void lockres_notify_event(struct ocfs2_lock_res *lockres, - enum ocfs2_lock_events event) -{ - if (lockres->l_ops->notify_event) - lockres->l_ops->notify_event(lockres, event); -} - -static inline int ocfs2_may_continue_on_blocked_lock(struct ocfs2_lock_res *lockres, - int wanted); -static void __ocfs2_cluster_unlock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, - int level, unsigned long caller_ip); -void ocfs2_cluster_unlock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, int level) -{ - __ocfs2_cluster_unlock(osb, lockres, level, _RET_IP_); -} - -static inline void ocfs2_generic_handle_downconvert_action(struct ocfs2_lock_res *lockres); -static inline void ocfs2_generic_handle_convert_action(struct ocfs2_lock_res *lockres); -static inline void ocfs2_generic_handle_attach_action(struct ocfs2_lock_res *lockres); -static int ocfs2_generic_handle_bast(struct ocfs2_lock_res *lockres, int level); -static void ocfs2_schedule_blocked_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres); -static inline void ocfs2_recover_from_dlm_error(struct ocfs2_lock_res *lockres, - int convert); -#define ocfs2_log_dlm_error(_func, _err, _lockres) do { \ - printk(KERN_ERR "DLM error %d while calling %s on resource %s\n", \ - _err, _func, (_lockres)->l_pretty_name); \ -} while (0) - -static int ocfs2_downconvert_thread(void *arg); -static void ocfs2_downconvert_on_unlock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres); -static unsigned int ocfs2_prepare_downconvert(struct ocfs2_lock_res *lockres, - int new_level); -static int ocfs2_downconvert_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, - int new_level, - int lvb, - unsigned int generation); -static int ocfs2_prepare_cancel_convert(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres); -static int ocfs2_cancel_convert(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres); - -static DEFINE_SPINLOCK(ocfs2_dlm_tracking_lock); - -static void ocfs2_add_lockres_tracking(struct ocfs2_lock_res *res, - struct ocfs2_dlm_debug *dlm_debug) -{ - mlog(0, "Add tracking for lockres %s\n", res->l_name); - - spin_lock(&ocfs2_dlm_tracking_lock); - BUG_ON(!list_empty(&res->l_debug_list)); - list_add(&res->l_debug_list, &dlm_debug->d_lockres_tracking); - spin_unlock(&ocfs2_dlm_tracking_lock); -} - -static void ocfs2_remove_lockres_tracking(struct ocfs2_lock_res *res) -{ - spin_lock(&ocfs2_dlm_tracking_lock); - if (!list_empty(&res->l_debug_list)) - list_del_init(&res->l_debug_list); - spin_unlock(&ocfs2_dlm_tracking_lock); -} - -#ifdef CONFIG_OCFS2_FS_STATS -static void ocfs2_init_lock_stats(struct ocfs2_lock_res *res) -{ - res->l_lock_refresh = 0; - memset(&res->l_lock_prmode, 0, sizeof(struct ocfs2_lock_stats)); - memset(&res->l_lock_exmode, 0, sizeof(struct ocfs2_lock_stats)); -} - -static void ocfs2_update_lock_stats(struct ocfs2_lock_res *res, int level, - struct ocfs2_mask_waiter *mw, int ret) -{ - u32 usec; - ktime_t kt; - struct ocfs2_lock_stats *stats; - - if (level == DLM_LOCK_PR) - stats = &res->l_lock_prmode; - else if (level == DLM_LOCK_EX) - stats = &res->l_lock_exmode; - else if (level == DLM_LOCK_CW) - stats = &res->l_lock_cwmode; - else - return; - - kt = ktime_sub(ktime_get(), mw->mw_lock_start); - usec = ktime_to_us(kt); - - stats->ls_gets++; - stats->ls_total += ktime_to_ns(kt); - /* overflow */ - if (unlikely(stats->ls_gets == 0)) { - stats->ls_gets++; - stats->ls_total = ktime_to_ns(kt); - } - - if (stats->ls_max < usec) - stats->ls_max = usec; - - if (ret) - stats->ls_fail++; -} - -static inline void ocfs2_track_lock_refresh(struct ocfs2_lock_res *lockres) -{ - lockres->l_lock_refresh++; -} - -static inline void ocfs2_init_start_time(struct ocfs2_mask_waiter *mw) -{ - mw->mw_lock_start = ktime_get(); -} -#else -static inline void ocfs2_init_lock_stats(struct ocfs2_lock_res *res) -{ -} -static inline void ocfs2_update_lock_stats(struct ocfs2_lock_res *res, - int level, struct ocfs2_mask_waiter *mw, int ret) -{ -} -static inline void ocfs2_track_lock_refresh(struct ocfs2_lock_res *lockres) -{ -} -static inline void ocfs2_init_start_time(struct ocfs2_mask_waiter *mw) -{ -} -#endif - -void ocfs2_lock_res_init_common(struct ocfs2_super *osb, - struct ocfs2_lock_res *res, - struct ocfs2_lock_res_ops *ops, - void *priv) -{ - res->l_ops = ops; - res->l_priv = priv; - - res->l_level = DLM_LOCK_IV; - res->l_requested = DLM_LOCK_IV; - res->l_blocking = DLM_LOCK_IV; - res->l_action = OCFS2_AST_INVALID; - res->l_unlock_action = OCFS2_UNLOCK_INVALID; - - res->l_flags = OCFS2_LOCK_INITIALIZED; - - lockres_name(res, res->l_pretty_name, OCFS2_LOCK_ID_PRETTY_LEN); - - ocfs2_add_lockres_tracking(res, osb->osb_dlm_debug); - - ocfs2_init_lock_stats(res); -#if 0 && CONFIG_DEBUG_LOCK_ALLOC - if (type != OCFS2_LOCK_TYPE_OPEN) - lockdep_init_map(&res->l_lockdep_map, ocfs2_lock_type_strings[type], - &lockdep_keys[type], 0); - else - res->l_lockdep_map.key = NULL; -#endif -} - -void ocfs2_lock_res_init_once(struct ocfs2_lock_res *res) -{ - /* This also clears out the lock status block */ - memset(res, 0, sizeof(struct ocfs2_lock_res)); - spin_lock_init(&res->l_lock); - init_waitqueue_head(&res->l_event); - INIT_LIST_HEAD(&res->l_blocked_list); - INIT_LIST_HEAD(&res->l_mask_waiters); - INIT_LIST_HEAD(&res->l_holders); - INIT_LIST_HEAD(&res->l_debug_list); -} - -void ocfs2_lock_res_free(struct ocfs2_lock_res *res) -{ - if (!(res->l_flags & OCFS2_LOCK_INITIALIZED)) - return; - - ocfs2_remove_lockres_tracking(res); - - mlog_bug_on_msg(!list_empty(&res->l_blocked_list), - "Lockres %s is on the blocked list\n", - res->l_name); - mlog_bug_on_msg(!list_empty(&res->l_mask_waiters), - "Lockres %s has mask waiters pending\n", - res->l_name); - mlog_bug_on_msg(spin_is_locked(&res->l_lock), - "Lockres %s is locked\n", - res->l_name); - mlog_bug_on_msg(res->l_ro_holders, - "Lockres %s has %u ro holders\n", - res->l_name, res->l_ro_holders); - mlog_bug_on_msg(res->l_ex_holders, - "Lockres %s has %u ex holders\n", - res->l_name, res->l_ex_holders); - - /* Need to clear out the lock status block for the dlm */ - memset(&res->l_lksb, 0, sizeof(res->l_lksb)); - - res->l_flags = 0UL; -} - -/* - * Keep a list of processes who have interest in a lockres. - * Note: this is now only uesed for check recursive cluster locking. - */ -static inline void ocfs2_add_holder(struct ocfs2_lock_res *lockres, - struct ocfs2_lock_holder *oh) -{ - INIT_LIST_HEAD(&oh->oh_list); - oh->oh_owner_pid = get_pid(task_pid(current)); - - spin_lock(&lockres->l_lock); - list_add_tail(&oh->oh_list, &lockres->l_holders); - spin_unlock(&lockres->l_lock); -} - -static inline void ocfs2_remove_holder(struct ocfs2_lock_res *lockres, - struct ocfs2_lock_holder *oh) -{ - spin_lock(&lockres->l_lock); - list_del(&oh->oh_list); - spin_unlock(&lockres->l_lock); - - put_pid(oh->oh_owner_pid); -} - -static inline int ocfs2_is_locked_by_me(struct ocfs2_lock_res *lockres) -{ - struct ocfs2_lock_holder *oh; - struct pid *pid; - - /* look in the list of holders for one with the current task as owner */ - spin_lock(&lockres->l_lock); - pid = task_pid(current); - list_for_each_entry(oh, &lockres->l_holders, oh_list) { - if (oh->oh_owner_pid == pid) { - spin_unlock(&lockres->l_lock); - return 1; - } - } - spin_unlock(&lockres->l_lock); - - return 0; -} - -static inline void ocfs2_inc_holders(struct ocfs2_lock_res *lockres, - int level) -{ - BUG_ON(!lockres); - - switch(level) { - case DLM_LOCK_EX: - lockres->l_ex_holders++; - break; - case DLM_LOCK_PR: - lockres->l_ro_holders++; - break; - case DLM_LOCK_CW: - lockres->l_cw_holders++; - break; - default: - BUG(); - } -} - -static inline void ocfs2_dec_holders(struct ocfs2_lock_res *lockres, - int level) -{ - BUG_ON(!lockres); - - switch(level) { - case DLM_LOCK_EX: - BUG_ON(!lockres->l_ex_holders); - lockres->l_ex_holders--; - break; - case DLM_LOCK_PR: - BUG_ON(!lockres->l_ro_holders); - lockres->l_ro_holders--; - break; - case DLM_LOCK_CW: - BUG_ON(!lockres->l_cw_holders); - lockres->l_cw_holders--; - break; - default: - BUG(); - } -} - -/* - * Compatibility matrix indexed by lock level - idea borrowed from - * fs/dlm/lock.c. Going across is the level our lock holds, going down - * is the level we're asked to convert to. The UN column and PD - * columns are unused and act as padding. - */ -static const int level_compat_matrix[8][8] = { - /* Lockres granted level */ - /* UN NL CR CW PR PW EX PD */ - {0, 0, 0, 0, 0, 0, 0, 0}, /* UN */ - {0, 1, 1, 1, 1, 1, 1, 0}, /* NL */ - {0, 0, 1, 1, 1, 1, 1, 0}, /* CR */ - {0, 0, 0, 1, 0, 1, 1, 0}, /* CW */ /* <-- Wanted levels */ - {0, 0, 0, 0, 1, 1, 1, 0}, /* PR */ - {0, 0, 0, 0, 0, 1, 1, 0}, /* PW */ - {0, 0, 0, 0, 0, 0, 1, 0}, /* EX */ - {0, 0, 0, 0, 0, 0, 0, 0} /* PD */ -}; - -static inline int __levels_compat(int lockres_level, int wanted) -{ - return level_compat_matrix[wanted + 1][lockres_level + 1]; -} - -int ocfs2_levels_compat(struct ocfs2_lock_res *lockres, int wanted) -{ - return __levels_compat(lockres->l_level, wanted); -} - -/* - * WARNING: We have to adjust this function when adding lock levels to - * dlmglue - * - * Given a lock blocking 'lockres' at 'level', what new level should - * we downconvert to. This function will never return a level which - * would result in an upconvert. - */ -static inline int ocfs2_downconvert_level(struct ocfs2_lock_res *lockres, - int level) -{ - int new_level = DLM_LOCK_EX; - - if (level == DLM_LOCK_EX) - new_level = DLM_LOCK_NL; - else if (level == DLM_LOCK_PR) { - if (lockres->l_level == DLM_LOCK_EX) - new_level = DLM_LOCK_PR; - else - new_level = DLM_LOCK_NL; - } else if (level == DLM_LOCK_CW) - new_level = DLM_LOCK_CW; - return new_level; -} - -#define H_EX 0x1 -#define H_PR 0x2 -#define H_CW 0x4 -#define H_ANY (H_EX|H_PR|H_CW) -static int lockres_has_holders(struct ocfs2_lock_res *lockres, int which) -{ - if (which & H_EX && lockres->l_ex_holders) - return 1; - if (which & H_PR && lockres->l_ro_holders) - return 1; - if (which & H_CW && lockres->l_cw_holders) - return 1; - return 0; -} - -static void lockres_set_flags(struct ocfs2_lock_res *lockres, - unsigned long newflags) -{ - struct ocfs2_mask_waiter *mw, *tmp; - - assert_spin_locked(&lockres->l_lock); - - lockres->l_flags = newflags; - - list_for_each_entry_safe(mw, tmp, &lockres->l_mask_waiters, mw_item) { - if ((lockres->l_flags & mw->mw_mask) != mw->mw_goal) - continue; - - list_del_init(&mw->mw_item); - mw->mw_status = 0; - complete(&mw->mw_complete); - } -} -static void lockres_or_flags(struct ocfs2_lock_res *lockres, unsigned long or) -{ - lockres_set_flags(lockres, lockres->l_flags | or); -} -static void lockres_clear_flags(struct ocfs2_lock_res *lockres, - unsigned long clear) -{ - lockres_set_flags(lockres, lockres->l_flags & ~clear); -} - -/* - * Make sure that a lock gets a strictly increasing number only once - * each time it needs to be refreshed. The gen needs to be larger than - * any previous gen the locked resources has seen so we maintain the gen - * in the super. The caller has serialized on the lock but lots of - * locks can all be racing on the super. - * - * This is used by callers to have a single read-only indicator that - * they need to refresh their resource while they have it locked. - */ -static void lockres_inc_refresh_gen(struct ocfs2_lock_res *lockres) -{ - struct ocfs2_super *osb = ocfs2_get_lockres_osb(lockres); - - lockres->l_refresh_gen = atomic64_inc_return(&osb->refresh_gen); -} - -static inline void ocfs2_generic_handle_downconvert_action(struct ocfs2_lock_res *lockres) -{ - int dc_level = ocfs2_downconvert_level(lockres, lockres->l_blocking); - - BUG_ON(!(lockres->l_flags & OCFS2_LOCK_BUSY)); - BUG_ON(!(lockres->l_flags & OCFS2_LOCK_ATTACHED)); - BUG_ON(!(lockres->l_flags & OCFS2_LOCK_BLOCKED)); - BUG_ON(lockres->l_blocking <= DLM_LOCK_NL); - - lockres->l_level = lockres->l_requested; - if (ocfs2_levels_compat(lockres, dc_level)) { - lockres->l_blocking = DLM_LOCK_NL; - lockres_clear_flags(lockres, OCFS2_LOCK_BLOCKED); - } - lockres_clear_flags(lockres, OCFS2_LOCK_BUSY); -} - -static inline void ocfs2_generic_handle_convert_action(struct ocfs2_lock_res *lockres) -{ - int old_level = lockres->l_level; - - BUG_ON(!(lockres->l_flags & OCFS2_LOCK_BUSY)); - BUG_ON(!(lockres->l_flags & OCFS2_LOCK_ATTACHED)); - - /* - * Converting from NL to any mode, or upconverting between - * incompatible modes will require a refresh. - */ - lockres->l_level = lockres->l_requested; - if (lockres->l_ops->flags & LOCK_TYPE_REQUIRES_REFRESH) { - if (old_level == DLM_LOCK_NL || - (old_level == DLM_LOCK_CW && - lockres->l_level != DLM_LOCK_NL)) { - lockres_or_flags(lockres, OCFS2_LOCK_NEEDS_REFRESH); - lockres_inc_refresh_gen(lockres); - } - } - - /* - * We set the OCFS2_LOCK_UPCONVERT_FINISHING flag before clearing - * the OCFS2_LOCK_BUSY flag to prevent the dc thread from - * downconverting the lock before the upconvert has fully completed. - * Do not prevent the dc thread from downconverting if NONBLOCK lock - * had already returned. - */ - if (!(lockres->l_flags & OCFS2_LOCK_NONBLOCK_FINISHED)) - lockres_or_flags(lockres, OCFS2_LOCK_UPCONVERT_FINISHING); - else - lockres_clear_flags(lockres, OCFS2_LOCK_NONBLOCK_FINISHED); - - lockres_clear_flags(lockres, OCFS2_LOCK_BUSY); -} - -static inline void ocfs2_generic_handle_attach_action(struct ocfs2_lock_res *lockres) -{ - BUG_ON((!(lockres->l_flags & OCFS2_LOCK_BUSY))); - BUG_ON(lockres->l_flags & OCFS2_LOCK_ATTACHED); - - if (lockres->l_requested > DLM_LOCK_NL && - !(lockres->l_flags & OCFS2_LOCK_LOCAL) && - lockres->l_ops->flags & LOCK_TYPE_REQUIRES_REFRESH) { - lockres_or_flags(lockres, OCFS2_LOCK_NEEDS_REFRESH); - lockres_inc_refresh_gen(lockres); - } - - lockres->l_level = lockres->l_requested; - lockres_or_flags(lockres, OCFS2_LOCK_ATTACHED); - lockres_clear_flags(lockres, OCFS2_LOCK_BUSY); -} - -static int ocfs2_generic_handle_bast(struct ocfs2_lock_res *lockres, - int level) -{ - int needs_downconvert = 0; - - assert_spin_locked(&lockres->l_lock); - - if (level > lockres->l_blocking) { - /* only schedule a downconvert if we haven't already scheduled - * one that goes low enough to satisfy the level we're - * blocking. this also catches the case where we get - * duplicate BASTs */ - if (ocfs2_downconvert_level(lockres, level) < - ocfs2_downconvert_level(lockres, lockres->l_blocking)) - needs_downconvert = 1; - - lockres->l_blocking = level; - } - - mlog(ML_BASTS, "lockres %s, block %d, level %d, l_block %d, dwn %d\n", - lockres->l_name, level, lockres->l_level, lockres->l_blocking, - needs_downconvert); - - if (needs_downconvert) - lockres_or_flags(lockres, OCFS2_LOCK_BLOCKED); - mlog(0, "needs_downconvert = %d\n", needs_downconvert); - return needs_downconvert; -} - -static void set_lock_blocking(struct ocfs2_lock_res *lockres, int level) -{ - struct ocfs2_super *osb = ocfs2_get_lockres_osb(lockres); - int needs_downconvert; - - needs_downconvert = ocfs2_generic_handle_bast(lockres, level); - if (needs_downconvert) - ocfs2_schedule_blocked_lock(osb, lockres); -} - -/* - * OCFS2_LOCK_PENDING and l_pending_gen. - * - * Why does OCFS2_LOCK_PENDING exist? To close a race between setting - * OCFS2_LOCK_BUSY and calling ocfs2_dlm_lock(). See ocfs2_unblock_lock() - * for more details on the race. - * - * OCFS2_LOCK_PENDING closes the race quite nicely. However, it introduces - * a race on itself. In o2dlm, we can get the ast before ocfs2_dlm_lock() - * returns. The ast clears OCFS2_LOCK_BUSY, and must therefore clear - * OCFS2_LOCK_PENDING at the same time. When ocfs2_dlm_lock() returns, - * the caller is going to try to clear PENDING again. If nothing else is - * happening, __lockres_clear_pending() sees PENDING is unset and does - * nothing. - * - * But what if another path (eg downconvert thread) has just started a - * new locking action? The other path has re-set PENDING. Our path - * cannot clear PENDING, because that will re-open the original race - * window. - * - * [Example] - * - * ocfs2_meta_lock() - * ocfs2_cluster_lock() - * set BUSY - * set PENDING - * drop l_lock - * ocfs2_dlm_lock() - * ocfs2_locking_ast() ocfs2_downconvert_thread() - * clear PENDING ocfs2_unblock_lock() - * take_l_lock - * !BUSY - * ocfs2_prepare_downconvert() - * set BUSY - * set PENDING - * drop l_lock - * take l_lock - * clear PENDING - * drop l_lock - * - * ocfs2_dlm_lock() - * - * So as you can see, we now have a window where l_lock is not held, - * PENDING is not set, and ocfs2_dlm_lock() has not been called. - * - * The core problem is that ocfs2_cluster_lock() has cleared the PENDING - * set by ocfs2_prepare_downconvert(). That wasn't nice. - * - * To solve this we introduce l_pending_gen. A call to - * lockres_clear_pending() will only do so when it is passed a generation - * number that matches the lockres. lockres_set_pending() will return the - * current generation number. When ocfs2_cluster_lock() goes to clear - * PENDING, it passes the generation it got from set_pending(). In our - * example above, the generation numbers will *not* match. Thus, - * ocfs2_cluster_lock() will not clear the PENDING set by - * ocfs2_prepare_downconvert(). - */ - -/* Unlocked version for ocfs2_locking_ast() */ -static void __lockres_clear_pending(struct ocfs2_lock_res *lockres, - unsigned int generation, - struct ocfs2_super *osb) -{ - assert_spin_locked(&lockres->l_lock); - - /* - * The ast and locking functions can race us here. The winner - * will clear pending, the loser will not. - */ - if (!(lockres->l_flags & OCFS2_LOCK_PENDING) || - (lockres->l_pending_gen != generation)) - return; - - lockres_clear_flags(lockres, OCFS2_LOCK_PENDING); - lockres->l_pending_gen++; - - /* - * The downconvert thread may have skipped us because we - * were PENDING. Wake it up. - */ - if (lockres->l_flags & OCFS2_LOCK_BLOCKED) - ocfs2_wake_downconvert_thread(osb); -} - -/* Locked version for callers of ocfs2_dlm_lock() */ -static void lockres_clear_pending(struct ocfs2_lock_res *lockres, - unsigned int generation, - struct ocfs2_super *osb) -{ - unsigned long flags; - - spin_lock_irqsave(&lockres->l_lock, flags); - __lockres_clear_pending(lockres, generation, osb); - spin_unlock_irqrestore(&lockres->l_lock, flags); -} - -static unsigned int lockres_set_pending(struct ocfs2_lock_res *lockres) -{ - assert_spin_locked(&lockres->l_lock); - BUG_ON(!(lockres->l_flags & OCFS2_LOCK_BUSY)); - - lockres_or_flags(lockres, OCFS2_LOCK_PENDING); - - return lockres->l_pending_gen; -} - -static void ocfs2_blocking_ast(struct ocfs2_dlm_lksb *lksb, int level) -{ - struct ocfs2_lock_res *lockres = ocfs2_lksb_to_lock_res(lksb); - struct ocfs2_super *osb = ocfs2_get_lockres_osb(lockres); - int needs_downconvert; - unsigned long flags; - - BUG_ON(level <= DLM_LOCK_NL); - - mlog(ML_BASTS, "BAST fired for lockres %s, blocking %d, level %d\n", - lockres->l_name, level, lockres->l_level); - - /* - * We can skip the bast for locks which don't enable caching - - * they'll be dropped at the earliest possible time anyway. - */ - if (lockres->l_flags & OCFS2_LOCK_NOCACHE) - return; - - spin_lock_irqsave(&lockres->l_lock, flags); - needs_downconvert = ocfs2_generic_handle_bast(lockres, level); - if (needs_downconvert) - ocfs2_schedule_blocked_lock(osb, lockres); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - wake_up(&lockres->l_event); - - ocfs2_wake_downconvert_thread(osb); -} - -static void ocfs2_locking_ast(struct ocfs2_dlm_lksb *lksb) -{ - struct ocfs2_lock_res *lockres = ocfs2_lksb_to_lock_res(lksb); - struct ocfs2_super *osb = ocfs2_get_lockres_osb(lockres); - unsigned long flags; - int status; - - spin_lock_irqsave(&lockres->l_lock, flags); - - status = ocfs2_dlm_lock_status(&lockres->l_lksb); - - if (status == -EAGAIN) { - lockres_clear_flags(lockres, OCFS2_LOCK_BUSY); - goto out; - } - - if (status) { - mlog(ML_ERROR, "lockres %s: lksb status value of %d!\n", - lockres->l_name, status); - spin_unlock_irqrestore(&lockres->l_lock, flags); - return; - } - - mlog(ML_BASTS, "AST fired for lockres %s, action %d, unlock %d, " - "level %d => %d\n", lockres->l_name, lockres->l_action, - lockres->l_unlock_action, lockres->l_level, lockres->l_requested); - - switch(lockres->l_action) { - case OCFS2_AST_ATTACH: - ocfs2_generic_handle_attach_action(lockres); - lockres_clear_flags(lockres, OCFS2_LOCK_LOCAL); - break; - case OCFS2_AST_CONVERT: - ocfs2_generic_handle_convert_action(lockres); - break; - case OCFS2_AST_DOWNCONVERT: - ocfs2_generic_handle_downconvert_action(lockres); - break; - default: - mlog(ML_ERROR, "lockres %s: AST fired with invalid action: %u, " - "flags 0x%lx, unlock: %u\n", - lockres->l_name, lockres->l_action, lockres->l_flags, - lockres->l_unlock_action); - BUG(); - } -out: - /* set it to something invalid so if we get called again we - * can catch it. */ - lockres->l_action = OCFS2_AST_INVALID; - - /* Did we try to cancel this lock? Clear that state */ - if (lockres->l_unlock_action == OCFS2_UNLOCK_CANCEL_CONVERT) - lockres->l_unlock_action = OCFS2_UNLOCK_INVALID; - - /* - * We may have beaten the locking functions here. We certainly - * know that dlm_lock() has been called :-) - * Because we can't have two lock calls in flight at once, we - * can use lockres->l_pending_gen. - */ - __lockres_clear_pending(lockres, lockres->l_pending_gen, osb); - - wake_up(&lockres->l_event); - spin_unlock_irqrestore(&lockres->l_lock, flags); -} - -static void ocfs2_unlock_ast(struct ocfs2_dlm_lksb *lksb, int error) -{ - struct ocfs2_lock_res *lockres = ocfs2_lksb_to_lock_res(lksb); - unsigned long flags; - - mlog(ML_BASTS, "UNLOCK AST fired for lockres %s, action = %d\n", - lockres->l_name, lockres->l_unlock_action); - - spin_lock_irqsave(&lockres->l_lock, flags); - if (error) { - mlog(ML_ERROR, "Dlm passes error %d for lock %s, " - "unlock_action %d\n", error, lockres->l_name, - lockres->l_unlock_action); - spin_unlock_irqrestore(&lockres->l_lock, flags); - return; - } - - switch(lockres->l_unlock_action) { - case OCFS2_UNLOCK_CANCEL_CONVERT: - mlog(0, "Cancel convert success for %s\n", lockres->l_name); - lockres->l_action = OCFS2_AST_INVALID; - /* Downconvert thread may have requeued this lock, we - * need to wake it. */ - if (lockres->l_flags & OCFS2_LOCK_BLOCKED) - ocfs2_wake_downconvert_thread(ocfs2_get_lockres_osb(lockres)); - break; - case OCFS2_UNLOCK_DROP_LOCK: - lockres->l_level = DLM_LOCK_IV; - break; - default: - BUG(); - } - - lockres_clear_flags(lockres, OCFS2_LOCK_BUSY); - lockres->l_unlock_action = OCFS2_UNLOCK_INVALID; - wake_up(&lockres->l_event); - spin_unlock_irqrestore(&lockres->l_lock, flags); -} - -/* - * This is the filesystem locking protocol. It provides the lock handling - * hooks for the underlying DLM. It has a maximum version number. - * The version number allows interoperability with systems running at - * the same major number and an equal or smaller minor number. - * - * Whenever the filesystem does new things with locks (adds or removes a - * lock, orders them differently, does different things underneath a lock), - * the version must be changed. The protocol is negotiated when joining - * the dlm domain. A node may join the domain if its major version is - * identical to all other nodes and its minor version is greater than - * or equal to all other nodes. When its minor version is greater than - * the other nodes, it will run at the minor version specified by the - * other nodes. - * - * If a locking change is made that will not be compatible with older - * versions, the major number must be increased and the minor version set - * to zero. If a change merely adds a behavior that can be disabled when - * speaking to older versions, the minor version must be increased. If a - * change adds a fully backwards compatible change (eg, LVB changes that - * are just ignored by older versions), the version does not need to be - * updated. - */ -static struct ocfs2_locking_protocol lproto = { -#if 0 - .lp_max_version = { - .pv_major = OCFS2_LOCKING_PROTOCOL_MAJOR, - .pv_minor = OCFS2_LOCKING_PROTOCOL_MINOR, - }, -#endif - .lp_lock_ast = ocfs2_locking_ast, - .lp_blocking_ast = ocfs2_blocking_ast, - .lp_unlock_ast = ocfs2_unlock_ast, -}; - -#if 0 -void ocfs2_set_locking_protocol(void) -{ - ocfs2_stack_glue_set_max_proto_version(&lproto.lp_max_version); -} -#endif - -static inline void ocfs2_recover_from_dlm_error(struct ocfs2_lock_res *lockres, - int convert) -{ - unsigned long flags; - - spin_lock_irqsave(&lockres->l_lock, flags); - lockres_clear_flags(lockres, OCFS2_LOCK_BUSY); - lockres_clear_flags(lockres, OCFS2_LOCK_UPCONVERT_FINISHING); - if (convert) - lockres->l_action = OCFS2_AST_INVALID; - else - lockres->l_unlock_action = OCFS2_UNLOCK_INVALID; - spin_unlock_irqrestore(&lockres->l_lock, flags); - - wake_up(&lockres->l_event); -} - -#if 0 -/* Note: If we detect another process working on the lock (i.e., - * OCFS2_LOCK_BUSY), we'll bail out returning 0. It's up to the caller - * to do the right thing in that case. - */ -static int ocfs2_lock_create(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, - int level, - u32 dlm_flags) -{ - int ret = 0; - unsigned long flags; - unsigned int gen; - - mlog(0, "lock %s, level = %d, flags = %u\n", lockres->l_name, level, - dlm_flags); - - spin_lock_irqsave(&lockres->l_lock, flags); - if ((lockres->l_flags & OCFS2_LOCK_ATTACHED) || - (lockres->l_flags & OCFS2_LOCK_BUSY)) { - spin_unlock_irqrestore(&lockres->l_lock, flags); - goto bail; - } - - lockres->l_action = OCFS2_AST_ATTACH; - lockres->l_requested = level; - lockres_or_flags(lockres, OCFS2_LOCK_BUSY); - gen = lockres_set_pending(lockres); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - ret = ocfs2_dlm_lock(osb->cconn, - level, - &lockres->l_lksb, - dlm_flags, - lockres->l_name, - OCFS2_LOCK_ID_MAX_LEN - 1); - lockres_clear_pending(lockres, gen, osb); - if (ret) { - ocfs2_log_dlm_error("ocfs2_dlm_lock", ret, lockres); - ocfs2_recover_from_dlm_error(lockres, 1); - } - - mlog(0, "lock %s, return from ocfs2_dlm_lock\n", lockres->l_name); - -bail: - return ret; -} -#endif - -static inline int ocfs2_check_wait_flag(struct ocfs2_lock_res *lockres, - int flag) -{ - unsigned long flags; - int ret; - - spin_lock_irqsave(&lockres->l_lock, flags); - ret = lockres->l_flags & flag; - spin_unlock_irqrestore(&lockres->l_lock, flags); - - return ret; -} - -static inline void ocfs2_wait_on_busy_lock(struct ocfs2_lock_res *lockres) - -{ - wait_event(lockres->l_event, - !ocfs2_check_wait_flag(lockres, OCFS2_LOCK_BUSY)); -} - -static inline void ocfs2_wait_on_refreshing_lock(struct ocfs2_lock_res *lockres) - -{ - wait_event(lockres->l_event, - !ocfs2_check_wait_flag(lockres, OCFS2_LOCK_REFRESHING)); -} - -/* predict what lock level we'll be dropping down to on behalf - * of another node, and return true if the currently wanted - * level will be compatible with it. */ -static inline int ocfs2_may_continue_on_blocked_lock(struct ocfs2_lock_res *lockres, - int wanted) -{ - BUG_ON(!(lockres->l_flags & OCFS2_LOCK_BLOCKED)); - - return wanted <= ocfs2_downconvert_level(lockres, lockres->l_blocking); -} - -static void ocfs2_init_mask_waiter(struct ocfs2_mask_waiter *mw) -{ - INIT_LIST_HEAD(&mw->mw_item); - init_completion(&mw->mw_complete); - ocfs2_init_start_time(mw); -} - -static int ocfs2_wait_for_mask(struct ocfs2_mask_waiter *mw) -{ - wait_for_completion(&mw->mw_complete); - /* Re-arm the completion in case we want to wait on it again */ - reinit_completion(&mw->mw_complete); - return mw->mw_status; -} - -static void lockres_add_mask_waiter(struct ocfs2_lock_res *lockres, - struct ocfs2_mask_waiter *mw, - unsigned long mask, - unsigned long goal) -{ - BUG_ON(!list_empty(&mw->mw_item)); - - assert_spin_locked(&lockres->l_lock); - - list_add_tail(&mw->mw_item, &lockres->l_mask_waiters); - mw->mw_mask = mask; - mw->mw_goal = goal; -} - -/* returns 0 if the mw that was removed was already satisfied, -EBUSY - * if the mask still hadn't reached its goal */ -static int __lockres_remove_mask_waiter(struct ocfs2_lock_res *lockres, - struct ocfs2_mask_waiter *mw) -{ - int ret = 0; - - assert_spin_locked(&lockres->l_lock); - if (!list_empty(&mw->mw_item)) { - if ((lockres->l_flags & mw->mw_mask) != mw->mw_goal) - ret = -EBUSY; - - list_del_init(&mw->mw_item); - init_completion(&mw->mw_complete); - } - - return ret; -} - -#if 0 -static int lockres_remove_mask_waiter(struct ocfs2_lock_res *lockres, - struct ocfs2_mask_waiter *mw) -{ - unsigned long flags; - int ret = 0; - - spin_lock_irqsave(&lockres->l_lock, flags); - ret = __lockres_remove_mask_waiter(lockres, mw); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - return ret; - -} - -static int ocfs2_wait_for_mask_interruptible(struct ocfs2_mask_waiter *mw, - struct ocfs2_lock_res *lockres) -{ - int ret; - - ret = wait_for_completion_interruptible(&mw->mw_complete); - if (ret) - lockres_remove_mask_waiter(lockres, mw); - else - ret = mw->mw_status; - /* Re-arm the completion in case we want to wait on it again */ - reinit_completion(&mw->mw_complete); - return ret; -} -#endif - -static inline int cw_incompat_convert(struct ocfs2_lock_res *lockres, - int level) -{ - /* Have CW, want PR/EX */ - if (lockres->l_level == DLM_LOCK_CW && - (level == DLM_LOCK_PR || level == DLM_LOCK_EX)) - return 1; - /* Have EX/PR, want CW */ - if (level == DLM_LOCK_CW && - (lockres->l_level == DLM_LOCK_PR || lockres->l_level == DLM_LOCK_EX)) - return 1; - return 0; -} - -static int __ocfs2_cluster_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, - int level, - u32 lkm_flags, - int arg_flags, - int l_subclass, - unsigned long caller_ip) -{ - struct ocfs2_mask_waiter mw; - int wait, catch_signals = !(osb->s_mount_opt & OCFS2_MOUNT_NOINTR); - int ret = 0; /* gcc doesn't realize wait = 1 guarantees ret is set */ - unsigned long flags; - unsigned int gen; - int noqueue_attempted = 0; - int dlm_locked = 0; - int kick_dc = 0; - - trace_ocfs2_cluster_lock(osb, lockres, level, lkm_flags, arg_flags); - - if (!(lockres->l_flags & OCFS2_LOCK_INITIALIZED)) { - mlog_errno(-EINVAL); - return -EINVAL; - } - - ocfs2_init_mask_waiter(&mw); - - if (lockres->l_ops->flags & LOCK_TYPE_USES_LVB) - lkm_flags |= DLM_LKF_VALBLK; - -again: - wait = 0; - - spin_lock_irqsave(&lockres->l_lock, flags); - - if (catch_signals && signal_pending(current)) { - ret = -ERESTARTSYS; - goto unlock; - } - - mlog_bug_on_msg(lockres->l_flags & OCFS2_LOCK_FREEING, - "Cluster lock called on freeing lockres %s! flags " - "0x%lx\n", lockres->l_name, lockres->l_flags); - - /* We only compare against the currently granted level - * here. If the lock is blocked waiting on a downconvert, - * we'll get caught below. */ - if (lockres->l_flags & OCFS2_LOCK_BUSY && - !ocfs2_levels_compat(lockres, level)) { - /* is someone sitting in dlm_lock? If so, wait on - * them. */ - lockres_add_mask_waiter(lockres, &mw, OCFS2_LOCK_BUSY, 0); - wait = 1; - scoutfs_inc_counter(osb->sb, lock_busy_wait); - goto unlock; - } - - if (lockres->l_flags & OCFS2_LOCK_UPCONVERT_FINISHING) { - /* - * We've upconverted. If the lock now has a level we can - * work with, we take it. If, however, the lock is not at the - * required level, we go thru the full cycle. One way this could - * happen is if a process requesting an upconvert to PR is - * closely followed by another requesting upconvert to an EX. - * If the process requesting EX lands here, we want it to - * continue attempting to upconvert and let the process - * requesting PR take the lock. - * If multiple processes request upconvert to PR, the first one - * here will take the lock. The others will have to go thru the - * OCFS2_LOCK_BLOCKED check to ensure that there is no pending - * downconvert request. - */ - if (ocfs2_levels_compat(lockres, level)) - goto update_holders; - } - - if (lockres->l_flags & OCFS2_LOCK_BLOCKED && - !ocfs2_may_continue_on_blocked_lock(lockres, level)) { - /* is the lock is currently blocked on behalf of - * another node */ - lockres_add_mask_waiter(lockres, &mw, OCFS2_LOCK_BLOCKED, 0); - wait = 1; - scoutfs_inc_counter(osb->sb, lock_blocked_wait); - goto unlock; - } - - /* - * Convert from PR/EX to CW and vice-versa. Those levels are - * not compatible with each other. As a result, we have to - * wait for holders on the lock to drain. The easiest way to - * do this is by forcing a downconvert. We can then allow the - * process to come back and reacquire the lock at the correct - * level. - */ - if (cw_incompat_convert(lockres, level)) { - /* ocfs2_unblock_lock will drop to NL, then we can upconvert. */ - set_lock_blocking(lockres, DLM_LOCK_EX); - lockres_add_mask_waiter(lockres, &mw, OCFS2_LOCK_BLOCKED, 0); - wait = 1; - scoutfs_inc_counter(osb->sb, lock_incompat_wait); - goto unlock; - } - - /* NL->Anything, PR->EX conditions are handled here */ - if (level > lockres->l_level) { - if (noqueue_attempted > 0) { - ret = -EAGAIN; - goto unlock; - } - if (lkm_flags & DLM_LKF_NOQUEUE) - noqueue_attempted = 1; - - if (lockres->l_action != OCFS2_AST_INVALID) - mlog(ML_ERROR, "lockres %s has action %u pending\n", - lockres->l_name, lockres->l_action); - - if (!(lockres->l_flags & OCFS2_LOCK_ATTACHED)) { - lockres->l_action = OCFS2_AST_ATTACH; - lkm_flags &= ~DLM_LKF_CONVERT; - } else { - lockres->l_action = OCFS2_AST_CONVERT; - lkm_flags |= DLM_LKF_CONVERT; - } - - lockres->l_requested = level; - lockres_or_flags(lockres, OCFS2_LOCK_BUSY); - gen = lockres_set_pending(lockres); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - if (lkm_flags & DLM_LKF_CONVERT) { - scoutfs_inc_counter(osb->sb, dlm_convert_request); - lockres_notify_event(lockres, EVENT_DLM_CONVERT); - } else { - scoutfs_inc_counter(osb->sb, dlm_lock_request); - lockres_notify_event(lockres, EVENT_DLM_LOCK); - } - - BUG_ON(level == DLM_LOCK_IV); - BUG_ON(level == DLM_LOCK_NL); - - mlog(ML_BASTS, "lockres %s, convert from %d to %d\n", - lockres->l_name, lockres->l_level, level); - - /* call dlm_lock to upgrade lock now */ - ret = ocfs2_dlm_lock(osb->cconn, - level, - &lockres->l_lksb, - lkm_flags, - lockres->l_name, - OCFS2_LOCK_ID_MAX_LEN - 1); - lockres_clear_pending(lockres, gen, osb); - if (ret) { - if (!(lkm_flags & DLM_LKF_NOQUEUE) || - (ret != -EAGAIN)) { - ocfs2_log_dlm_error("ocfs2_dlm_lock", - ret, lockres); - } - ocfs2_recover_from_dlm_error(lockres, 1); - goto out; - } - dlm_locked = 1; - - mlog(0, "lock %s, successful return from ocfs2_dlm_lock\n", - lockres->l_name); - - /* At this point we've gone inside the dlm and need to - * complete our work regardless. */ - catch_signals = 0; - - /* wait for busy to clear and carry on */ - goto again; - } - -update_holders: - /* Ok, if we get here then we're good to go. */ - ocfs2_inc_holders(lockres, level); - - ret = 0; -unlock: - lockres_clear_flags(lockres, OCFS2_LOCK_UPCONVERT_FINISHING); - - /* ocfs2_unblock_lock reques on seeing OCFS2_LOCK_UPCONVERT_FINISHING */ - kick_dc = (lockres->l_flags & OCFS2_LOCK_BLOCKED); - - spin_unlock_irqrestore(&lockres->l_lock, flags); - if (kick_dc) - ocfs2_wake_downconvert_thread(osb); -out: - /* - * This is helping work around a lock inversion between the page lock - * and dlm locks. One path holds the page lock while calling aops - * which block acquiring dlm locks. The voting thread holds dlm - * locks while acquiring page locks while down converting data locks. - * This block is helping an aop path notice the inversion and back - * off to unlock its page lock before trying the dlm lock again. - */ - if (wait && arg_flags & OCFS2_LOCK_NONBLOCK && - mw.mw_mask & (OCFS2_LOCK_BUSY|OCFS2_LOCK_BLOCKED)) { - wait = 0; - spin_lock_irqsave(&lockres->l_lock, flags); - if (__lockres_remove_mask_waiter(lockres, &mw)) { - if (dlm_locked) - lockres_or_flags(lockres, - OCFS2_LOCK_NONBLOCK_FINISHED); - spin_unlock_irqrestore(&lockres->l_lock, flags); - ret = -EAGAIN; - } else { - spin_unlock_irqrestore(&lockres->l_lock, flags); - goto again; - } - } - if (wait) { - ret = ocfs2_wait_for_mask(&mw); - if (ret == 0) - goto again; - mlog_errno(ret); - } - ocfs2_update_lock_stats(lockres, level, &mw, ret); - -#if 0 && CONFIG_DEBUG_LOCK_ALLOC - if (!ret && lockres->l_lockdep_map.key != NULL) { - if (level == DLM_LOCK_PR) - rwsem_acquire_read(&lockres->l_lockdep_map, l_subclass, - !!(arg_flags & OCFS2_META_LOCK_NOQUEUE), - caller_ip); - else - rwsem_acquire(&lockres->l_lockdep_map, l_subclass, - !!(arg_flags & OCFS2_META_LOCK_NOQUEUE), - caller_ip); - } -#endif - return ret; -} - -int ocfs2_cluster_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, - int level, - u32 lkm_flags, - int arg_flags) -{ - return __ocfs2_cluster_lock(osb, lockres, level, lkm_flags, arg_flags, - 0, _RET_IP_); -} - -static void __ocfs2_cluster_unlock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, - int level, - unsigned long caller_ip) -{ - unsigned long flags; - - trace_ocfs2_cluster_unlock(osb, lockres, level); - - spin_lock_irqsave(&lockres->l_lock, flags); - ocfs2_dec_holders(lockres, level); - ocfs2_downconvert_on_unlock(osb, lockres); - spin_unlock_irqrestore(&lockres->l_lock, flags); -#if 0 && CONFIG_DEBUG_LOCK_ALLOC - if (lockres->l_lockdep_map.key != NULL) - rwsem_release(&lockres->l_lockdep_map, 1, caller_ip); -#endif -} - -#if 0 -static int ocfs2_create_new_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, - int ex, - int local) -{ - int level = ex ? DLM_LOCK_EX : DLM_LOCK_PR; - unsigned long flags; - u32 lkm_flags = local ? DLM_LKF_LOCAL : 0; - - spin_lock_irqsave(&lockres->l_lock, flags); - BUG_ON(lockres->l_flags & OCFS2_LOCK_ATTACHED); - lockres_or_flags(lockres, OCFS2_LOCK_LOCAL); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - return ocfs2_lock_create(osb, lockres, level, lkm_flags); -} -#endif - -#if 0 -static int ocfs2_flock_handle_signal(struct ocfs2_lock_res *lockres, - int level) -{ - int ret; - struct ocfs2_super *osb = ocfs2_get_lockres_osb(lockres); - unsigned long flags; - struct ocfs2_mask_waiter mw; - - ocfs2_init_mask_waiter(&mw); - -retry_cancel: - spin_lock_irqsave(&lockres->l_lock, flags); - if (lockres->l_flags & OCFS2_LOCK_BUSY) { - ret = ocfs2_prepare_cancel_convert(osb, lockres); - if (ret) { - spin_unlock_irqrestore(&lockres->l_lock, flags); - ret = ocfs2_cancel_convert(osb, lockres); - if (ret < 0) { - mlog_errno(ret); - goto out; - } - goto retry_cancel; - } - lockres_add_mask_waiter(lockres, &mw, OCFS2_LOCK_BUSY, 0); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - ocfs2_wait_for_mask(&mw); - goto retry_cancel; - } - - ret = -ERESTARTSYS; - /* - * We may still have gotten the lock, in which case there's no - * point to restarting the syscall. - */ - if (lockres->l_level == level) - ret = 0; - - mlog(0, "Cancel returning %d. flags: 0x%lx, level: %d, act: %d\n", ret, - lockres->l_flags, lockres->l_level, lockres->l_action); - - spin_unlock_irqrestore(&lockres->l_lock, flags); - -out: - return ret; -} - -/* - * ocfs2_file_lock() and ocfs2_file_unlock() map to a single pair of - * flock() calls. The locking approach this requires is sufficiently - * different from all other cluster lock types that we implement a - * separate path to the "low-level" dlm calls. In particular: - * - * - No optimization of lock levels is done - we take at exactly - * what's been requested. - * - * - No lock caching is employed. We immediately downconvert to - * no-lock at unlock time. This also means flock locks never go on - * the blocking list). - * - * - Since userspace can trivially deadlock itself with flock, we make - * sure to allow cancellation of a misbehaving applications flock() - * request. - * - * - Access to any flock lockres doesn't require concurrency, so we - * can simplify the code by requiring the caller to guarantee - * serialization of dlmglue flock calls. - */ -int ocfs2_file_lock(struct file *file, int ex, int trylock) -{ - int ret, level = ex ? DLM_LOCK_EX : DLM_LOCK_PR; - unsigned int lkm_flags = trylock ? DLM_LKF_NOQUEUE : 0; - unsigned long flags; - struct ocfs2_file_private *fp = file->private_data; - struct ocfs2_lock_res *lockres = &fp->fp_flock; - struct ocfs2_super *osb = OCFS2_SB(file->f_mapping->host->i_sb); - struct ocfs2_mask_waiter mw; - - ocfs2_init_mask_waiter(&mw); - - if ((lockres->l_flags & OCFS2_LOCK_BUSY) || - (lockres->l_level > DLM_LOCK_NL)) { - mlog(ML_ERROR, - "File lock \"%s\" has busy or locked state: flags: 0x%lx, " - "level: %u\n", lockres->l_name, lockres->l_flags, - lockres->l_level); - return -EINVAL; - } - - spin_lock_irqsave(&lockres->l_lock, flags); - if (!(lockres->l_flags & OCFS2_LOCK_ATTACHED)) { - lockres_add_mask_waiter(lockres, &mw, OCFS2_LOCK_BUSY, 0); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - /* - * Get the lock at NLMODE to start - that way we - * can cancel the upconvert request if need be. - */ - ret = ocfs2_lock_create(osb, lockres, DLM_LOCK_NL, 0); - if (ret < 0) { - mlog_errno(ret); - goto out; - } - - ret = ocfs2_wait_for_mask(&mw); - if (ret) { - mlog_errno(ret); - goto out; - } - spin_lock_irqsave(&lockres->l_lock, flags); - } - - lockres->l_action = OCFS2_AST_CONVERT; - lkm_flags |= DLM_LKF_CONVERT; - lockres->l_requested = level; - lockres_or_flags(lockres, OCFS2_LOCK_BUSY); - - lockres_add_mask_waiter(lockres, &mw, OCFS2_LOCK_BUSY, 0); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - ret = ocfs2_dlm_lock(osb->cconn, level, &lockres->l_lksb, lkm_flags, - lockres->l_name, OCFS2_LOCK_ID_MAX_LEN - 1); - if (ret) { - if (!trylock || (ret != -EAGAIN)) { - ocfs2_log_dlm_error("ocfs2_dlm_lock", ret, lockres); - ret = -EINVAL; - } - - ocfs2_recover_from_dlm_error(lockres, 1); - lockres_remove_mask_waiter(lockres, &mw); - goto out; - } - - ret = ocfs2_wait_for_mask_interruptible(&mw, lockres); - if (ret == -ERESTARTSYS) { - /* - * Userspace can cause deadlock itself with - * flock(). Current behavior locally is to allow the - * deadlock, but abort the system call if a signal is - * received. We follow this example, otherwise a - * poorly written program could sit in kernel until - * reboot. - * - * Handling this is a bit more complicated for Ocfs2 - * though. We can't exit this function with an - * outstanding lock request, so a cancel convert is - * required. We intentionally overwrite 'ret' - if the - * cancel fails and the lock was granted, it's easier - * to just bubble success back up to the user. - */ - ret = ocfs2_flock_handle_signal(lockres, level); - } else if (!ret && (level > lockres->l_level)) { - /* Trylock failed asynchronously */ - BUG_ON(!trylock); - ret = -EAGAIN; - } - -out: - - mlog(0, "Lock: \"%s\" ex: %d, trylock: %d, returns: %d\n", - lockres->l_name, ex, trylock, ret); - return ret; -} - -void ocfs2_file_unlock(struct file *file) -{ - int ret; - unsigned int gen; - unsigned long flags; - struct ocfs2_file_private *fp = file->private_data; - struct ocfs2_lock_res *lockres = &fp->fp_flock; - struct ocfs2_super *osb = OCFS2_SB(file->f_mapping->host->i_sb); - struct ocfs2_mask_waiter mw; - - ocfs2_init_mask_waiter(&mw); - - if (!(lockres->l_flags & OCFS2_LOCK_ATTACHED)) - return; - - if (lockres->l_level == DLM_LOCK_NL) - return; - - mlog(0, "Unlock: \"%s\" flags: 0x%lx, level: %d, act: %d\n", - lockres->l_name, lockres->l_flags, lockres->l_level, - lockres->l_action); - - spin_lock_irqsave(&lockres->l_lock, flags); - /* - * Fake a blocking ast for the downconvert code. - */ - lockres_or_flags(lockres, OCFS2_LOCK_BLOCKED); - lockres->l_blocking = DLM_LOCK_EX; - - gen = ocfs2_prepare_downconvert(lockres, DLM_LOCK_NL); - lockres_add_mask_waiter(lockres, &mw, OCFS2_LOCK_BUSY, 0); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - ret = ocfs2_downconvert_lock(osb, lockres, DLM_LOCK_NL, 0, gen); - if (ret) { - mlog_errno(ret); - return; - } - - ret = ocfs2_wait_for_mask(&mw); - if (ret) - mlog_errno(ret); -} -#endif - -static void ocfs2_downconvert_on_unlock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres) -{ - int kick = 0; - - /* If we know that another node is waiting on our lock, kick - * the downconvert thread * pre-emptively when we reach a release - * condition. */ - if (lockres->l_flags & OCFS2_LOCK_BLOCKED) { - switch(lockres->l_blocking) { - case DLM_LOCK_EX: - if (!lockres_has_holders(lockres, H_ANY)) - kick = 1; - break; - case DLM_LOCK_PR: - if (!lockres_has_holders(lockres, H_EX|H_CW)) - kick = 1; - break; - case DLM_LOCK_CW: - if (!lockres_has_holders(lockres, H_EX|H_PR)) - kick = 1; - break; - default: - BUG(); - } - } - - if (kick) - ocfs2_wake_downconvert_thread(osb); -} - -#if 0 -/* Determine whether a lock resource needs to be refreshed, and - * arbitrate who gets to refresh it. - * - * 0 means no refresh needed. - * - * > 0 means you need to refresh this and you MUST call - * ocfs2_complete_lock_res_refresh afterwards. */ -static int ocfs2_should_refresh_lock_res(struct ocfs2_lock_res *lockres) -{ - unsigned long flags; - int status = 0; - -refresh_check: - spin_lock_irqsave(&lockres->l_lock, flags); - if (!(lockres->l_flags & OCFS2_LOCK_NEEDS_REFRESH)) { - spin_unlock_irqrestore(&lockres->l_lock, flags); - goto bail; - } - - if (lockres->l_flags & OCFS2_LOCK_REFRESHING) { - spin_unlock_irqrestore(&lockres->l_lock, flags); - - ocfs2_wait_on_refreshing_lock(lockres); - goto refresh_check; - } - - /* Ok, I'll be the one to refresh this lock. */ - lockres_or_flags(lockres, OCFS2_LOCK_REFRESHING); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - status = 1; -bail: - mlog(0, "status %d\n", status); - return status; -} -#endif - -/* If status is non zero, I'll mark it as not being in refresh - * anymroe, but i won't clear the needs refresh flag. */ -static inline void ocfs2_complete_lock_res_refresh(struct ocfs2_lock_res *lockres, - int status) -{ - unsigned long flags; - - spin_lock_irqsave(&lockres->l_lock, flags); - lockres_clear_flags(lockres, OCFS2_LOCK_REFRESHING); - if (!status) - lockres_clear_flags(lockres, OCFS2_LOCK_NEEDS_REFRESH); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - wake_up(&lockres->l_event); -} - -u64 ocfs2_lock_refresh_gen(struct ocfs2_lock_res *lockres) -{ - return lockres->l_refresh_gen; -} - -/* Reference counting of the dlm debug structure. We want this because - * open references on the debug inodes can live on after a mount, so - * we can't rely on the ocfs2_super to always exist. */ -static void ocfs2_dlm_debug_free(struct kref *kref) -{ - struct ocfs2_dlm_debug *dlm_debug; - - dlm_debug = container_of(kref, struct ocfs2_dlm_debug, d_refcnt); - - kfree(dlm_debug); -} - -void ocfs2_put_dlm_debug(struct ocfs2_dlm_debug *dlm_debug) -{ - if (dlm_debug) - kref_put(&dlm_debug->d_refcnt, ocfs2_dlm_debug_free); -} - -static void ocfs2_get_dlm_debug(struct ocfs2_dlm_debug *debug) -{ - kref_get(&debug->d_refcnt); -} - -struct ocfs2_dlm_debug *ocfs2_new_dlm_debug(void) -{ - struct ocfs2_dlm_debug *dlm_debug; - - dlm_debug = kmalloc(sizeof(struct ocfs2_dlm_debug), GFP_KERNEL); - if (!dlm_debug) { - mlog_errno(-ENOMEM); - goto out; - } - - kref_init(&dlm_debug->d_refcnt); - INIT_LIST_HEAD(&dlm_debug->d_lockres_tracking); - dlm_debug->d_locking_state = NULL; -out: - return dlm_debug; -} - -/* Access to this is arbitrated for us via seq_file->sem. */ -struct ocfs2_dlm_seq_priv { - struct ocfs2_dlm_debug *p_dlm_debug; - struct ocfs2_lock_res p_iter_res; - struct ocfs2_lock_res p_tmp_res; -}; - -static struct ocfs2_lock_res *ocfs2_dlm_next_res(struct ocfs2_lock_res *start, - struct ocfs2_dlm_seq_priv *priv) -{ - struct ocfs2_lock_res *iter, *ret = NULL; - struct ocfs2_dlm_debug *dlm_debug = priv->p_dlm_debug; - - assert_spin_locked(&ocfs2_dlm_tracking_lock); - - list_for_each_entry(iter, &start->l_debug_list, l_debug_list) { - /* discover the head of the list */ - if (&iter->l_debug_list == &dlm_debug->d_lockres_tracking) { - mlog(0, "End of list found, %p\n", ret); - break; - } - - /* We track our "dummy" iteration lockres' by a NULL - * l_ops field. */ - if (iter->l_ops != NULL) { - ret = iter; - break; - } - } - - return ret; -} - -static void *ocfs2_dlm_seq_start(struct seq_file *m, loff_t *pos) -{ - struct ocfs2_dlm_seq_priv *priv = m->private; - struct ocfs2_lock_res *iter; - - spin_lock(&ocfs2_dlm_tracking_lock); - iter = ocfs2_dlm_next_res(&priv->p_iter_res, priv); - if (iter) { - /* Since lockres' have the lifetime of their container - * (which can be inodes, ocfs2_supers, etc) we want to - * copy this out to a temporary lockres while still - * under the spinlock. Obviously after this we can't - * trust any pointers on the copy returned, but that's - * ok as the information we want isn't typically held - * in them. */ - priv->p_tmp_res = *iter; - iter = &priv->p_tmp_res; - } - spin_unlock(&ocfs2_dlm_tracking_lock); - - return iter; -} - -static void ocfs2_dlm_seq_stop(struct seq_file *m, void *v) -{ -} - -static void *ocfs2_dlm_seq_next(struct seq_file *m, void *v, loff_t *pos) -{ - struct ocfs2_dlm_seq_priv *priv = m->private; - struct ocfs2_lock_res *iter = v; - struct ocfs2_lock_res *dummy = &priv->p_iter_res; - - spin_lock(&ocfs2_dlm_tracking_lock); - iter = ocfs2_dlm_next_res(iter, priv); - list_del_init(&dummy->l_debug_list); - if (iter) { - list_add(&dummy->l_debug_list, &iter->l_debug_list); - priv->p_tmp_res = *iter; - iter = &priv->p_tmp_res; - } - spin_unlock(&ocfs2_dlm_tracking_lock); - - return iter; -} - -/* - * Version is used by debugfs.ocfs2 to determine the format being used - * - * New in version 2 - * - Lock stats printed - * New in version 3 - * - Max time in lock stats is in usecs (instead of nsecs) - */ -#define OCFS2_DLM_DEBUG_STR_VERSION 3 -static int ocfs2_dlm_seq_show(struct seq_file *m, void *v) -{ - int i; - char *lvb; - struct ocfs2_lock_res *lockres = v; - char lockname[256]; - - if (!lockres) - return -EINVAL; - - lockres_name(lockres, lockname, 256); - - seq_printf(m, "0x%x\t%s\t", OCFS2_DLM_DEBUG_STR_VERSION, lockname); - - seq_printf(m, "%d\t" - "0x%lx\t" - "0x%x\t" - "0x%x\t" - "%u\t" - "%u\t" - "%d\t" - "%d\t", - lockres->l_level, - lockres->l_flags, - lockres->l_action, - lockres->l_unlock_action, - lockres->l_ro_holders, - lockres->l_ex_holders, - lockres->l_requested, - lockres->l_blocking); - - /* Dump the raw LVB */ - lvb = ocfs2_dlm_lvb(&lockres->l_lksb); - for(i = 0; i < DLM_LVB_LEN; i++) - seq_printf(m, "0x%x\t", lvb[i]); - -#ifdef CONFIG_OCFS2_FS_STATS -# define lock_num_cwmode(_l) ((_l)->l_lock_cwmode.ls_gets) -# define lock_num_prmode(_l) ((_l)->l_lock_prmode.ls_gets) -# define lock_num_exmode(_l) ((_l)->l_lock_exmode.ls_gets) -# define lock_num_cwmode_failed(_l) ((_l)->l_lock_cwmode.ls_fail) -# define lock_num_prmode_failed(_l) ((_l)->l_lock_prmode.ls_fail) -# define lock_num_exmode_failed(_l) ((_l)->l_lock_exmode.ls_fail) -# define lock_total_cwmode(_l) ((_l)->l_lock_cwmode.ls_total) -# define lock_total_prmode(_l) ((_l)->l_lock_prmode.ls_total) -# define lock_total_exmode(_l) ((_l)->l_lock_exmode.ls_total) -# define lock_max_cwmode(_l) ((_l)->l_lock_cwmode.ls_max) -# define lock_max_prmode(_l) ((_l)->l_lock_prmode.ls_max) -# define lock_max_exmode(_l) ((_l)->l_lock_exmode.ls_max) -# define lock_refresh(_l) ((_l)->l_lock_refresh) -#else -# define lock_num_cwmode(_l) (0) -# define lock_num_prmode(_l) (0) -# define lock_num_exmode(_l) (0) -# define lock_num_cwmode_failed(_l) (0) -# define lock_num_prmode_failed(_l) (0) -# define lock_num_exmode_failed(_l) (0) -# define lock_total_cwmode(_l) (0ULL) -# define lock_total_prmode(_l) (0ULL) -# define lock_total_exmode(_l) (0ULL) -# define lock_max_cwmode(_l) (0) -# define lock_max_prmode(_l) (0) -# define lock_max_exmode(_l) (0) -# define lock_refresh(_l) (0) -#endif - /* The following seq_print was added in version 2 of this output */ - seq_printf(m, "%u\t" - "%u\t" - "%u\t" - "%u\t" - "%llu\t" - "%llu\t" - "%u\t" - "%u\t" - "%u\t", - lock_num_prmode(lockres), - lock_num_exmode(lockres), - lock_num_prmode_failed(lockres), - lock_num_exmode_failed(lockres), - lock_total_prmode(lockres), - lock_total_exmode(lockres), - lock_max_prmode(lockres), - lock_max_exmode(lockres), - lock_refresh(lockres)); - - seq_printf(m, "%u\t" - "%u\t" - "%u\t" - "%llu\t" - "%u\t", - lockres->l_cw_holders, - lock_num_cwmode(lockres), - lock_num_cwmode_failed(lockres), - lock_total_cwmode(lockres), - lock_max_cwmode(lockres)); - - /* End the line */ - seq_printf(m, "\n"); - return 0; -} - -static const struct seq_operations ocfs2_dlm_seq_ops = { - .start = ocfs2_dlm_seq_start, - .stop = ocfs2_dlm_seq_stop, - .next = ocfs2_dlm_seq_next, - .show = ocfs2_dlm_seq_show, -}; - -static int ocfs2_dlm_debug_release(struct inode *inode, struct file *file) -{ - struct seq_file *seq = file->private_data; - struct ocfs2_dlm_seq_priv *priv = seq->private; - struct ocfs2_lock_res *res = &priv->p_iter_res; - - ocfs2_remove_lockres_tracking(res); - ocfs2_put_dlm_debug(priv->p_dlm_debug); - return seq_release_private(inode, file); -} - -static int ocfs2_dlm_debug_open(struct inode *inode, struct file *file) -{ - struct ocfs2_dlm_seq_priv *priv; - struct ocfs2_super *osb; - - priv = __seq_open_private(file, &ocfs2_dlm_seq_ops, sizeof(*priv)); - if (!priv) { - mlog_errno(-ENOMEM); - return -ENOMEM; - } - - osb = inode->i_private; - ocfs2_get_dlm_debug(osb->osb_dlm_debug); - priv->p_dlm_debug = osb->osb_dlm_debug; - INIT_LIST_HEAD(&priv->p_iter_res.l_debug_list); - - ocfs2_add_lockres_tracking(&priv->p_iter_res, - priv->p_dlm_debug); - - return 0; -} - -static const struct file_operations ocfs2_dlm_debug_fops = { - .open = ocfs2_dlm_debug_open, - .release = ocfs2_dlm_debug_release, - .read = seq_read, - .llseek = seq_lseek, -}; - -static int ocfs2_dlm_init_debug(struct ocfs2_super *osb, - struct dentry *debug_root) -{ - int ret = 0; - struct ocfs2_dlm_debug *dlm_debug = osb->osb_dlm_debug; - - dlm_debug->d_locking_state = debugfs_create_file("locking_state", - S_IFREG|S_IRUSR, - debug_root, - osb, - &ocfs2_dlm_debug_fops); - if (!dlm_debug->d_locking_state) { - ret = -EINVAL; - mlog(ML_ERROR, - "Unable to create locking state debugfs file.\n"); - goto out; - } - - ocfs2_get_dlm_debug(dlm_debug); -out: - return ret; -} - -static void ocfs2_dlm_shutdown_debug(struct ocfs2_super *osb) -{ - struct ocfs2_dlm_debug *dlm_debug = osb->osb_dlm_debug; - - if (dlm_debug) { - debugfs_remove(dlm_debug->d_locking_state); - ocfs2_put_dlm_debug(dlm_debug); - } -} - -static void ocfs2_do_node_down(int node_num, void *data) -{ -} - -int ocfs2_dlm_init(struct ocfs2_super *osb, struct super_block *sb, - char *cluster_stack, char *cluster_name, char *ls_name, - struct dentry *debug_root) -{ - int status = 0; - struct ocfs2_cluster_connection *conn = NULL; - -#if 0 - if (ocfs2_mount_local(osb)) { - osb->node_num = 0; - goto local; - } -#endif - osb->sb = sb; - - status = ocfs2_dlm_init_debug(osb, debug_root); - if (status < 0) { - mlog_errno(status); - goto bail; - } - - /* launch downconvert thread */ - osb->dc_task = kthread_run(ocfs2_downconvert_thread, osb, "scoutdc-%s", - ls_name); - if (IS_ERR(osb->dc_task)) { - status = PTR_ERR(osb->dc_task); - osb->dc_task = NULL; - mlog_errno(status); - goto bail; - } - - /* for now, uuid == domain */ - status = ocfs2_cluster_connect(cluster_stack, - cluster_name, - strlen(cluster_name), - ls_name, - strlen(ls_name), - &lproto, ocfs2_do_node_down, osb, - &conn); - if (status) { - mlog_errno(status); - goto bail; - } - -#if 0 - status = ocfs2_cluster_this_node(conn, &osb->node_num); - if (status < 0) { - mlog_errno(status); - mlog(ML_ERROR, - "could not find this host's node number\n"); - ocfs2_cluster_disconnect(conn, 0); - goto bail; - } - -local: - ocfs2_super_lock_res_init(&osb->osb_super_lockres, osb); - ocfs2_rename_lock_res_init(&osb->osb_rename_lockres, osb); - ocfs2_nfs_sync_lock_res_init(&osb->osb_nfs_sync_lockres, osb); - ocfs2_orphan_scan_lock_res_init(&osb->osb_orphan_scan.os_lockres, osb); -#endif - osb->cconn = conn; -bail: - if (status < 0) { - ocfs2_dlm_shutdown_debug(osb); - if (osb->dc_task) - kthread_stop(osb->dc_task); - } - - return status; -} - -void ocfs2_dlm_shutdown(struct ocfs2_super *osb, - int hangup_pending) -{ -// ocfs2_drop_osb_locks(osb); - - /* - * Now that we have dropped all locks and ocfs2_dismount_volume() - * has disabled recovery, the DLM won't be talking to us. It's - * safe to tear things down before disconnecting the cluster. - */ - - if (osb->dc_task) { - kthread_stop(osb->dc_task); - osb->dc_task = NULL; - } - -#if 0 - ocfs2_lock_res_free(&osb->osb_super_lockres); - ocfs2_lock_res_free(&osb->osb_rename_lockres); - ocfs2_lock_res_free(&osb->osb_nfs_sync_lockres); - ocfs2_lock_res_free(&osb->osb_orphan_scan.os_lockres); -#endif - - ocfs2_cluster_disconnect(osb->cconn, hangup_pending); - osb->cconn = NULL; - - ocfs2_dlm_shutdown_debug(osb); -} - -static int ocfs2_drop_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres) -{ - int ret; - unsigned long flags; - u32 lkm_flags = 0; - - /* We didn't get anywhere near actually using this lockres. */ - if (!(lockres->l_flags & OCFS2_LOCK_INITIALIZED)) - goto out; - - if (lockres->l_ops->flags & LOCK_TYPE_USES_LVB) - lkm_flags |= DLM_LKF_VALBLK; - - spin_lock_irqsave(&lockres->l_lock, flags); - - mlog_bug_on_msg(!(lockres->l_flags & OCFS2_LOCK_FREEING), - "lockres %s, flags 0x%lx\n", - lockres->l_name, lockres->l_flags); - - while (lockres->l_flags & OCFS2_LOCK_BUSY) { - mlog(0, "waiting on busy lock \"%s\": flags = %lx, action = " - "%u, unlock_action = %u\n", - lockres->l_name, lockres->l_flags, lockres->l_action, - lockres->l_unlock_action); - - spin_unlock_irqrestore(&lockres->l_lock, flags); - - /* XXX: Today we just wait on any busy - * locks... Perhaps we need to cancel converts in the - * future? */ - ocfs2_wait_on_busy_lock(lockres); - - spin_lock_irqsave(&lockres->l_lock, flags); - } - - if (lockres->l_ops->flags & LOCK_TYPE_USES_LVB) { - if (lockres->l_flags & OCFS2_LOCK_ATTACHED && - lockres->l_level == DLM_LOCK_EX && - !(lockres->l_flags & OCFS2_LOCK_NEEDS_REFRESH)) - lockres->l_ops->set_lvb(lockres); - } - - if (lockres->l_flags & OCFS2_LOCK_BUSY) - mlog(ML_ERROR, "destroying busy lock: \"%s\"\n", - lockres->l_name); - if (lockres->l_flags & OCFS2_LOCK_BLOCKED) - mlog(0, "destroying blocked lock: \"%s\"\n", lockres->l_name); - - if (!(lockres->l_flags & OCFS2_LOCK_ATTACHED)) { - spin_unlock_irqrestore(&lockres->l_lock, flags); - goto out; - } - - lockres_clear_flags(lockres, OCFS2_LOCK_ATTACHED); - - /* make sure we never get here while waiting for an ast to - * fire. */ - BUG_ON(lockres->l_action != OCFS2_AST_INVALID); - - /* is this necessary? */ - lockres_or_flags(lockres, OCFS2_LOCK_BUSY); - lockres->l_unlock_action = OCFS2_UNLOCK_DROP_LOCK; - spin_unlock_irqrestore(&lockres->l_lock, flags); - - mlog(0, "lock %s\n", lockres->l_name); - - ret = ocfs2_dlm_unlock(osb->cconn, &lockres->l_lksb, lkm_flags); - if (ret) { - ocfs2_log_dlm_error("ocfs2_dlm_unlock", ret, lockres); - mlog(ML_ERROR, "lockres flags: %lu\n", lockres->l_flags); - ocfs2_dlm_dump_lksb(&lockres->l_lksb); - BUG(); - } - mlog(0, "lock %s, successful return from ocfs2_dlm_unlock\n", - lockres->l_name); - - scoutfs_inc_counter(osb->sb, dlm_unlock_request); - lockres_notify_event(lockres, EVENT_DLM_UNLOCK); - - ocfs2_wait_on_busy_lock(lockres); -out: - return 0; -} - -static void ocfs2_process_blocked_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres); - -/* Mark the lockres as being dropped. It will no longer be - * queued if blocking, but we still may have to wait on it - * being dequeued from the downconvert thread before we can consider - * it safe to drop. - * - * You can *not* attempt to call cluster_lock on this lockres anymore. */ -void ocfs2_mark_lockres_freeing(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres) -{ - int status; - struct ocfs2_mask_waiter mw; - unsigned long flags, flags2; - - ocfs2_init_mask_waiter(&mw); - - spin_lock_irqsave(&lockres->l_lock, flags); - lockres->l_flags |= OCFS2_LOCK_FREEING; - if (lockres->l_flags & OCFS2_LOCK_QUEUED && current == osb->dc_task) { - /* - * We know the downconvert is queued but not in progress - * because we are the downconvert thread and processing - * different lock. So we can just remove the lock from the - * queue. This is not only an optimization but also a way - * to avoid the following deadlock: - * ocfs2_dentry_post_unlock() - * ocfs2_dentry_lock_put() - * ocfs2_drop_dentry_lock() - * iput() - * ocfs2_evict_inode() - * ocfs2_clear_inode() - * ocfs2_mark_lockres_freeing() - * ... blocks waiting for OCFS2_LOCK_QUEUED - * since we are the downconvert thread which - * should clear the flag. - */ - spin_unlock_irqrestore(&lockres->l_lock, flags); - spin_lock_irqsave(&osb->dc_task_lock, flags2); - list_del_init(&lockres->l_blocked_list); - osb->blocked_lock_count--; - spin_unlock_irqrestore(&osb->dc_task_lock, flags2); - /* - * Warn if we recurse into another post_unlock call. Strictly - * speaking it isn't a problem but we need to be careful if - * that happens (stack overflow, deadlocks, ...) so warn if - * ocfs2 grows a path for which this can happen. - */ - WARN_ON_ONCE(lockres->l_ops->post_unlock); - /* Since the lock is freeing we don't do much in the fn below */ - ocfs2_process_blocked_lock(osb, lockres); - return; - } - while (lockres->l_flags & OCFS2_LOCK_QUEUED) { - lockres_add_mask_waiter(lockres, &mw, OCFS2_LOCK_QUEUED, 0); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - mlog(0, "Waiting on lockres %s\n", lockres->l_name); - - status = ocfs2_wait_for_mask(&mw); - if (status) - mlog_errno(status); - - spin_lock_irqsave(&lockres->l_lock, flags); - } - spin_unlock_irqrestore(&lockres->l_lock, flags); -} - -void ocfs2_simple_drop_lockres(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres) -{ - int ret; - - trace_ocfs2_simple_drop_lockres(osb, lockres); - - ocfs2_mark_lockres_freeing(osb, lockres); - - if (lockres->l_ops->drop_worker) - lockres->l_ops->drop_worker(lockres); - - ret = ocfs2_drop_lock(osb, lockres); - if (ret) - mlog_errno(ret); -} - -static unsigned int ocfs2_prepare_downconvert(struct ocfs2_lock_res *lockres, - int new_level) -{ - assert_spin_locked(&lockres->l_lock); - - BUG_ON(lockres->l_blocking <= DLM_LOCK_NL); - - if (lockres->l_level <= new_level) { - mlog(ML_ERROR, "lockres %s, lvl %d <= %d, blcklst %d, mask %d, " - "flags 0x%lx, hold %d %d, act %d %d, req %d, " - "block %d, pgen %d\n", lockres->l_name, lockres->l_level, - new_level, list_empty(&lockres->l_blocked_list), - list_empty(&lockres->l_mask_waiters), - lockres->l_flags, lockres->l_ro_holders, - lockres->l_ex_holders, lockres->l_action, - lockres->l_unlock_action, lockres->l_requested, - lockres->l_blocking, lockres->l_pending_gen); - BUG(); - } - - mlog(ML_BASTS, "lockres %s, level %d => %d, blocking %d\n", - lockres->l_name, lockres->l_level, new_level, lockres->l_blocking); - - lockres->l_action = OCFS2_AST_DOWNCONVERT; - lockres->l_requested = new_level; - lockres_or_flags(lockres, OCFS2_LOCK_BUSY); - return lockres_set_pending(lockres); -} - -static int ocfs2_downconvert_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, - int new_level, - int lvb, - unsigned int generation) -{ - int ret; - u32 dlm_flags = DLM_LKF_CONVERT; - - mlog(ML_BASTS, "lockres %s, level %d => %d\n", lockres->l_name, - lockres->l_level, new_level); - - /* - * On DLM_LKF_VALBLK, fsdlm behaves differently with o2cb. It always - * expects DLM_LKF_VALBLK being set if the LKB has LVB, so that - * we can recover correctly from node failure. Otherwise, we may get - * invalid LVB in LKB, but without DLM_SBF_VALNOTVALID being set. - */ - if (!ocfs2_is_o2cb_active() && - lockres->l_ops->flags & LOCK_TYPE_USES_LVB) - lvb = 1; - - if (lvb) - dlm_flags |= DLM_LKF_VALBLK; - - ret = ocfs2_dlm_lock(osb->cconn, - new_level, - &lockres->l_lksb, - dlm_flags, - lockres->l_name, - OCFS2_LOCK_ID_MAX_LEN - 1); - lockres_clear_pending(lockres, generation, osb); - if (ret) { - ocfs2_log_dlm_error("ocfs2_dlm_lock", ret, lockres); - ocfs2_recover_from_dlm_error(lockres, 1); - goto bail; - } - - ret = 0; -bail: - return ret; -} - -/* returns 1 when the caller should unlock and call ocfs2_dlm_unlock */ -static int ocfs2_prepare_cancel_convert(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres) -{ - assert_spin_locked(&lockres->l_lock); - - if (lockres->l_unlock_action == OCFS2_UNLOCK_CANCEL_CONVERT) { - /* If we're already trying to cancel a lock conversion - * then just drop the spinlock and allow the caller to - * requeue this lock. */ - mlog(ML_BASTS, "lockres %s, skip convert\n", lockres->l_name); - return 0; - } - - /* were we in a convert when we got the bast fire? */ - BUG_ON(lockres->l_action != OCFS2_AST_CONVERT && - lockres->l_action != OCFS2_AST_DOWNCONVERT); - /* set things up for the unlockast to know to just - * clear out the ast_action and unset busy, etc. */ - lockres->l_unlock_action = OCFS2_UNLOCK_CANCEL_CONVERT; - - mlog_bug_on_msg(!(lockres->l_flags & OCFS2_LOCK_BUSY), - "lock %s, invalid flags: 0x%lx\n", - lockres->l_name, lockres->l_flags); - - mlog(ML_BASTS, "lockres %s\n", lockres->l_name); - - return 1; -} - -static int ocfs2_cancel_convert(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres) -{ - int ret; - - ret = ocfs2_dlm_unlock(osb->cconn, &lockres->l_lksb, - DLM_LKF_CANCEL); - if (ret) { - ocfs2_log_dlm_error("ocfs2_dlm_unlock", ret, lockres); - ocfs2_recover_from_dlm_error(lockres, 0); - } - - mlog(ML_BASTS, "lockres %s\n", lockres->l_name); - - scoutfs_inc_counter(osb->sb, dlm_cancel_convert); - lockres_notify_event(lockres, EVENT_DLM_CONVERT); - - return ret; -} - -static int ocfs2_unblock_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, - struct ocfs2_unblock_ctl *ctl) -{ - unsigned long flags; - int blocking; - int new_level; - int level; - int ret = 0; - int set_lvb = 0; - unsigned int gen; - - - spin_lock_irqsave(&lockres->l_lock, flags); - -recheck: - trace_ocfs2_unblock_lock(osb, lockres); - - /* - * Is it still blocking? If not, we have no more work to do. - */ - if (!(lockres->l_flags & OCFS2_LOCK_BLOCKED)) { - BUG_ON(lockres->l_blocking != DLM_LOCK_NL); - spin_unlock_irqrestore(&lockres->l_lock, flags); - ret = 0; - goto leave; - } - - if (lockres->l_flags & OCFS2_LOCK_BUSY) { - /* XXX - * This is a *big* race. The OCFS2_LOCK_PENDING flag - * exists entirely for one reason - another thread has set - * OCFS2_LOCK_BUSY, but has *NOT* yet called dlm_lock(). - * - * If we do ocfs2_cancel_convert() before the other thread - * calls dlm_lock(), our cancel will do nothing. We will - * get no ast, and we will have no way of knowing the - * cancel failed. Meanwhile, the other thread will call - * into dlm_lock() and wait...forever. - * - * Why forever? Because another node has asked for the - * lock first; that's why we're here in unblock_lock(). - * - * The solution is OCFS2_LOCK_PENDING. When PENDING is - * set, we just requeue the unblock. Only when the other - * thread has called dlm_lock() and cleared PENDING will - * we then cancel their request. - * - * All callers of dlm_lock() must set OCFS2_DLM_PENDING - * at the same time they set OCFS2_DLM_BUSY. They must - * clear OCFS2_DLM_PENDING after dlm_lock() returns. - */ - if (lockres->l_flags & OCFS2_LOCK_PENDING) { - mlog(ML_BASTS, "lockres %s, ReQ: Pending\n", - lockres->l_name); - goto leave_requeue; - } - - ctl->requeue = 1; - ret = ocfs2_prepare_cancel_convert(osb, lockres); - spin_unlock_irqrestore(&lockres->l_lock, flags); - if (ret) { - ret = ocfs2_cancel_convert(osb, lockres); - if (ret < 0) - mlog_errno(ret); - } - goto leave; - } - - /* - * This prevents livelocks. OCFS2_LOCK_UPCONVERT_FINISHING flag is - * set when the ast is received for an upconvert just before the - * OCFS2_LOCK_BUSY flag is cleared. Now if the fs received a bast - * on the heels of the ast, we want to delay the downconvert just - * enough to allow the up requestor to do its task. Because this - * lock is in the blocked queue, the lock will be downconverted - * as soon as the requestor is done with the lock. - */ - if (lockres->l_flags & OCFS2_LOCK_UPCONVERT_FINISHING) - goto leave_requeue; - - /* - * How can we block and yet be at NL? We were trying to upconvert - * from NL and got canceled. The code comes back here, and now - * we notice and clear BLOCKING. - */ - if (lockres->l_level == DLM_LOCK_NL) { - BUG_ON(lockres_has_holders(lockres, H_ANY)); - mlog(ML_BASTS, "lockres %s, Aborting dc\n", lockres->l_name); - lockres->l_blocking = DLM_LOCK_NL; - lockres_clear_flags(lockres, OCFS2_LOCK_BLOCKED); - spin_unlock_irqrestore(&lockres->l_lock, flags); - goto leave; - } - - /* if we're blocking an exclusive and we have *any* holders, - * then requeue. */ - if (lockres->l_blocking == DLM_LOCK_EX && - lockres_has_holders(lockres, H_ANY)) { - mlog(ML_BASTS, "lockres %s, ReQ: EX/PR/CW Holders %u,%u\n", - lockres->l_name, lockres->l_ex_holders, - lockres->l_ro_holders, lockres->l_cw_holders); - goto leave_requeue; - } - - /* If it's a PR we're blocking, then only - * requeue if we've got any EX or CW holders */ - if (lockres->l_blocking == DLM_LOCK_PR && - lockres_has_holders(lockres, H_CW|H_EX)) { - mlog(ML_BASTS, "lockres %s, ReQ: EX/CW Holders %u,%u\n", - lockres->l_name, lockres->l_ex_holders, - lockres->l_cw_holders); - goto leave_requeue; - } - - /* - * Same logic as above, we're checking for any holders that - * are incompatible with CW. - */ - if (lockres->l_blocking == DLM_LOCK_CW - && lockres_has_holders(lockres, H_EX|H_PR)) { - mlog(ML_BASTS, "lockres %s, ReQ: EX/PR Holders %u,%u\n", - lockres->l_name, lockres->l_ex_holders, - lockres->l_ro_holders); - goto leave_requeue; - } - - /* - * Can we get a lock in this state if the holder counts are - * zero? The meta data unblock code used to check this. - */ - if ((lockres->l_ops->flags & LOCK_TYPE_REQUIRES_REFRESH) - && (lockres->l_flags & OCFS2_LOCK_REFRESHING)) { - mlog(ML_BASTS, "lockres %s, ReQ: Lock Refreshing\n", - lockres->l_name); - goto leave_requeue; - } - - new_level = ocfs2_downconvert_level(lockres, lockres->l_blocking); - - if (lockres->l_ops->check_downconvert - && !lockres->l_ops->check_downconvert(lockres, new_level)) { - mlog(ML_BASTS, "lockres %s, ReQ: Checkpointing\n", - lockres->l_name); - goto leave_requeue; - } - - - /* Some lockres types want to do a bit of work before - * downconverting a lock. Allow that here. The worker function - * may sleep, so we save off a copy of what we're blocking as - * it may change while we're not holding the spin lock. */ - blocking = lockres->l_blocking; - level = lockres->l_level; - - /* If we get here, then we know that there are no more - * incompatible holders (and anyone asking for an incompatible - * lock is blocked). We can now downconvert the lock */ - if (!lockres->l_ops->downconvert_worker) - goto downconvert; - - spin_unlock_irqrestore(&lockres->l_lock, flags); - - lockres_notify_event(lockres, EVENT_DLM_DOWNCONVERT_WORK); - - ctl->unblock_action = lockres->l_ops->downconvert_worker(lockres, blocking); - - if (ctl->unblock_action == UNBLOCK_STOP_POST) { - mlog(ML_BASTS, "lockres %s, UNBLOCK_STOP_POST\n", - lockres->l_name); - goto leave; - } - - spin_lock_irqsave(&lockres->l_lock, flags); - if ((blocking != lockres->l_blocking) || (level != lockres->l_level)) { - /* If this changed underneath us, then we can't drop - * it just yet. */ - mlog(ML_BASTS, "lockres %s, block=%d:%d, level=%d:%d, " - "Recheck\n", lockres->l_name, blocking, - lockres->l_blocking, level, lockres->l_level); - goto recheck; - } - -downconvert: - ctl->requeue = 0; - - if (lockres->l_ops->flags & LOCK_TYPE_USES_LVB) { - if (lockres->l_level == DLM_LOCK_EX) - set_lvb = 1; - - /* - * We only set the lvb if the lock has been fully - * refreshed - otherwise we risk setting stale - * data. Otherwise, there's no need to actually clear - * out the lvb here as it's value is still valid. - */ - if (set_lvb && !(lockres->l_flags & OCFS2_LOCK_NEEDS_REFRESH)) - lockres->l_ops->set_lvb(lockres); - } - - gen = ocfs2_prepare_downconvert(lockres, new_level); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - switch (level) { - case DLM_LOCK_EX: - scoutfs_inc_counter(osb->sb, dlm_ex_downconvert); - break; - case DLM_LOCK_PR: - scoutfs_inc_counter(osb->sb, dlm_pr_downconvert); - break; - case DLM_LOCK_CW: - scoutfs_inc_counter(osb->sb, dlm_cw_downconvert); - break; - } - - ret = ocfs2_downconvert_lock(osb, lockres, new_level, set_lvb, - gen); - -leave: - if (ret) - mlog_errno(ret); - return ret; - -leave_requeue: - spin_unlock_irqrestore(&lockres->l_lock, flags); - ctl->requeue = 1; - - return 0; -} - -static void ocfs2_process_blocked_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres) -{ - int status; - struct ocfs2_unblock_ctl ctl = {0, 0,}; - unsigned long flags; - - /* Our reference to the lockres in this function can be - * considered valid until we remove the OCFS2_LOCK_QUEUED - * flag. */ - - BUG_ON(!lockres); - BUG_ON(!lockres->l_ops); - - mlog(ML_BASTS, "lockres %s blocked\n", lockres->l_name); - - /* Detect whether a lock has been marked as going away while - * the downconvert thread was processing other things. A lock can - * still be marked with OCFS2_LOCK_FREEING after this check, - * but short circuiting here will still save us some - * performance. */ - spin_lock_irqsave(&lockres->l_lock, flags); - if (lockres->l_flags & OCFS2_LOCK_FREEING) - goto unqueue; - spin_unlock_irqrestore(&lockres->l_lock, flags); - - status = ocfs2_unblock_lock(osb, lockres, &ctl); - if (status < 0) - mlog_errno(status); - - spin_lock_irqsave(&lockres->l_lock, flags); -unqueue: - if (lockres->l_flags & OCFS2_LOCK_FREEING || !ctl.requeue) { - lockres_clear_flags(lockres, OCFS2_LOCK_QUEUED); - } else - ocfs2_schedule_blocked_lock(osb, lockres); - - mlog(ML_BASTS, "lockres %s, requeue = %s.\n", lockres->l_name, - ctl.requeue ? "yes" : "no"); - spin_unlock_irqrestore(&lockres->l_lock, flags); - - if (ctl.unblock_action != UNBLOCK_CONTINUE - && lockres->l_ops->post_unlock) - lockres->l_ops->post_unlock(osb, lockres); -} - -static void ocfs2_schedule_blocked_lock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres) -{ - unsigned long flags; - - assert_spin_locked(&lockres->l_lock); - - if (lockres->l_flags & OCFS2_LOCK_FREEING) { - /* Do not schedule a lock for downconvert when it's on - * the way to destruction - any nodes wanting access - * to the resource will get it soon. */ - mlog(ML_BASTS, "lockres %s won't be scheduled: flags 0x%lx\n", - lockres->l_name, lockres->l_flags); - return; - } - - lockres_or_flags(lockres, OCFS2_LOCK_QUEUED); - - spin_lock_irqsave(&osb->dc_task_lock, flags); - if (list_empty(&lockres->l_blocked_list)) { - list_add_tail(&lockres->l_blocked_list, - &osb->blocked_lock_list); - osb->blocked_lock_count++; - } - spin_unlock_irqrestore(&osb->dc_task_lock, flags); -} - -static void ocfs2_downconvert_thread_do_work(struct ocfs2_super *osb) -{ - unsigned long processed; - unsigned long flags; - struct ocfs2_lock_res *lockres; - - spin_lock_irqsave(&osb->dc_task_lock, flags); - /* grab this early so we know to try again if a state change and - * wake happens part-way through our work */ - osb->dc_work_sequence = osb->dc_wake_sequence; - - processed = osb->blocked_lock_count; - /* - * blocked lock processing in this loop might call iput which can - * remove items off osb->blocked_lock_list. Downconvert up to - * 'processed' number of locks, but stop short if we had some - * removed in ocfs2_mark_lockres_freeing when downconverting. - */ - while (processed && !list_empty(&osb->blocked_lock_list)) { - lockres = list_entry(osb->blocked_lock_list.next, - struct ocfs2_lock_res, l_blocked_list); - list_del_init(&lockres->l_blocked_list); - osb->blocked_lock_count--; - spin_unlock_irqrestore(&osb->dc_task_lock, flags); - - BUG_ON(!processed); - processed--; - - ocfs2_process_blocked_lock(osb, lockres); - - spin_lock_irqsave(&osb->dc_task_lock, flags); - } - spin_unlock_irqrestore(&osb->dc_task_lock, flags); -} - -static int ocfs2_downconvert_thread_lists_empty(struct ocfs2_super *osb) -{ - int empty = 0; - unsigned long flags; - - spin_lock_irqsave(&osb->dc_task_lock, flags); - if (list_empty(&osb->blocked_lock_list)) - empty = 1; - - spin_unlock_irqrestore(&osb->dc_task_lock, flags); - return empty; -} - -static int ocfs2_downconvert_thread_should_wake(struct ocfs2_super *osb) -{ - int should_wake = 0; - unsigned long flags; - - spin_lock_irqsave(&osb->dc_task_lock, flags); - if (osb->dc_work_sequence != osb->dc_wake_sequence) - should_wake = 1; - spin_unlock_irqrestore(&osb->dc_task_lock, flags); - - return should_wake; -} - -static int ocfs2_downconvert_thread(void *arg) -{ - int status = 0; - struct ocfs2_super *osb = arg; - - /* only quit once we've been asked to stop and there is no more - * work available */ - while (!(kthread_should_stop() && - ocfs2_downconvert_thread_lists_empty(osb))) { - - wait_event_interruptible(osb->dc_event, - ocfs2_downconvert_thread_should_wake(osb) || - kthread_should_stop()); - - mlog(0, "downconvert_thread: awoken\n"); - - ocfs2_downconvert_thread_do_work(osb); - } - - osb->dc_task = NULL; - return status; -} - -void ocfs2_wake_downconvert_thread(struct ocfs2_super *osb) -{ - unsigned long flags; - - spin_lock_irqsave(&osb->dc_task_lock, flags); - /* make sure the voting thread gets a swipe at whatever changes - * the caller may have made to the voting state */ - osb->dc_wake_sequence++; - spin_unlock_irqrestore(&osb->dc_task_lock, flags); - wake_up(&osb->dc_event); -} - -int ocfs2_init_super(struct ocfs2_super *osb, int flags) -{ - memset(osb, 0, sizeof(*osb)); - - osb->osb_dlm_debug = ocfs2_new_dlm_debug(); - if (!osb->osb_dlm_debug) - return -ENOMEM; - - spin_lock_init(&osb->dc_task_lock); - init_waitqueue_head(&osb->dc_event); - INIT_LIST_HEAD(&osb->blocked_lock_list); - osb->s_mount_opt = flags; - atomic64_set(&osb->refresh_gen, 0); - - return 0; -} diff --git a/kmod/src/dlmglue.h b/kmod/src/dlmglue.h deleted file mode 100644 index 7a51c528..00000000 --- a/kmod/src/dlmglue.h +++ /dev/null @@ -1,390 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8; -*- - * vim: noexpandtab sw=8 ts=8 sts=0: - * - * dlmglue.h - * - * description here - * - * Copyright (C) 2002, 2004 Oracle. All rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 021110-1307, USA. - */ - - -#ifndef DLMGLUE_H -#define DLMGLUE_H - -#include "stackglue.h" - -/* Max length of lockid name */ -#define OCFS2_LOCK_ID_MAX_LEN 32 -#define OCFS2_LOCK_ID_PRETTY_LEN 64 - -enum ocfs2_ast_action { - OCFS2_AST_INVALID = 0, - OCFS2_AST_ATTACH, - OCFS2_AST_CONVERT, - OCFS2_AST_DOWNCONVERT, -}; - -/* actions for an unlockast function to take. */ -enum ocfs2_unlock_action { - OCFS2_UNLOCK_INVALID = 0, - OCFS2_UNLOCK_CANCEL_CONVERT, - OCFS2_UNLOCK_DROP_LOCK, -}; - -/* ocfs2_lock_res->l_flags flags. */ -#define OCFS2_LOCK_ATTACHED (0x00000001) /* we have initialized - * the lvb */ -#define OCFS2_LOCK_BUSY (0x00000002) /* we are currently in - * dlm_lock */ -#define OCFS2_LOCK_BLOCKED (0x00000004) /* blocked waiting to - * downconvert*/ -#define OCFS2_LOCK_LOCAL (0x00000008) /* newly created inode */ -#define OCFS2_LOCK_NEEDS_REFRESH (0x00000010) -#define OCFS2_LOCK_REFRESHING (0x00000020) -#define OCFS2_LOCK_INITIALIZED (0x00000040) /* track initialization - * for shutdown paths */ -#define OCFS2_LOCK_FREEING (0x00000080) /* help dlmglue track - * when to skip queueing - * a lock because it's - * about to be - * dropped. */ -#define OCFS2_LOCK_QUEUED (0x00000100) /* queued for downconvert */ -#define OCFS2_LOCK_NOCACHE (0x00000200) /* don't use a holder count */ -#define OCFS2_LOCK_PENDING (0x00000400) /* This lockres is pending a - call to dlm_lock. Only - exists with BUSY set. */ -#define OCFS2_LOCK_UPCONVERT_FINISHING (0x00000800) /* blocks the dc thread - * from downconverting - * before the upconvert - * has completed */ - -#define OCFS2_LOCK_NONBLOCK_FINISHED (0x00001000) /* NONBLOCK cluster - * lock has already - * returned, do not block - * dc thread from - * downconverting */ - -struct ocfs2_lock_res_ops; - -typedef void (*ocfs2_lock_callback)(int status, unsigned long data); - -#ifdef CONFIG_OCFS2_FS_STATS -struct ocfs2_lock_stats { - u64 ls_total; /* Total wait in NSEC */ - u32 ls_gets; /* Num acquires */ - u32 ls_fail; /* Num failed acquires */ - - /* Storing max wait in usecs saves 24 bytes per inode */ - u32 ls_max; /* Max wait in USEC */ -}; -#endif - -struct ocfs2_lock_res { - void *l_priv; - struct ocfs2_lock_res_ops *l_ops; - - - struct list_head l_blocked_list; - struct list_head l_mask_waiters; - struct list_head l_holders; - - u64 l_refresh_gen; - unsigned long l_flags; - char l_name[OCFS2_LOCK_ID_MAX_LEN]; - char l_pretty_name[OCFS2_LOCK_ID_PRETTY_LEN]; - unsigned int l_ro_holders; - unsigned int l_cw_holders; - unsigned int l_ex_holders; - signed char l_level; - signed char l_requested; - signed char l_blocking; - - /* used from AST/BAST funcs. */ - /* Data packed - enum type ocfs2_ast_action */ - unsigned char l_action; - /* Data packed - enum type ocfs2_unlock_action */ - unsigned char l_unlock_action; - unsigned int l_pending_gen; - - spinlock_t l_lock; - - struct ocfs2_dlm_lksb l_lksb; - - wait_queue_head_t l_event; - - struct list_head l_debug_list; - -#ifdef CONFIG_OCFS2_FS_STATS - struct ocfs2_lock_stats l_lock_prmode; /* PR mode stats */ - u32 l_lock_refresh; /* Disk refreshes */ - struct ocfs2_lock_stats l_lock_exmode; /* EX mode stats */ - struct ocfs2_lock_stats l_lock_cwmode; /* CW mode stats */ -#endif -#ifdef CONFIG_DEBUG_LOCK_ALLOC - struct lockdep_map l_lockdep_map; -#endif -}; - -struct ocfs2_dlm_debug { - struct kref d_refcnt; - struct dentry *d_locking_state; - struct list_head d_lockres_tracking; -}; - -/* The cluster stack fields */ -#define OCFS2_STACK_LABEL_LEN 4 -#define OCFS2_CLUSTER_NAME_LEN 16 - -struct ocfs2_super -{ - struct ocfs2_cluster_connection *cconn; - struct ocfs2_dlm_debug *osb_dlm_debug; - - /* Downconvert thread */ - spinlock_t dc_task_lock; - struct task_struct *dc_task; - wait_queue_head_t dc_event; - unsigned long dc_wake_sequence; - unsigned long dc_work_sequence; - - /* - * Any thread can add locks to the list, but the downconvert - * thread is the only one allowed to remove locks. Any change - * to this rule requires updating - * ocfs2_downconvert_thread_do_work(). - */ - struct list_head blocked_lock_list; - unsigned long blocked_lock_count; - - /* refresh_gen needs to strictly increase as locks come and go */ - atomic64_t refresh_gen; - - unsigned long s_mount_opt; - - /* sb is for use with scoutfs counter macros only. Eventually - * we'll roll our own counters code in dlmglue. */ - struct super_block *sb; -}; -/* For s_mount_opt */ -#define OCFS2_MOUNT_NOINTR (1 << 2) - -/* - * Return value from ->downconvert_worker functions. - * - * These control the precise actions of ocfs2_unblock_lock() - * and ocfs2_process_blocked_lock() - * - */ -enum ocfs2_unblock_action { - UNBLOCK_CONTINUE = 0, /* Continue downconvert */ - UNBLOCK_CONTINUE_POST = 1, /* Continue downconvert, fire - * ->post_unlock callback */ - UNBLOCK_STOP_POST = 2, /* Do not downconvert, fire - * ->post_unlock() callback. */ -}; - -enum ocfs2_lock_events { - EVENT_DLM_LOCK = 0, - EVENT_DLM_UNLOCK, - EVENT_DLM_CONVERT, - EVENT_DLM_CANCEL_CONVERT, - EVENT_DLM_DOWNCONVERT_WORK, -}; - -/* - * OCFS2 Lock Resource Operations - * - * These fine tune the behavior of the generic dlmglue locking infrastructure. - * - * The most basic of lock types can point ->l_priv to their respective - * struct ocfs2_super and allow the default actions to manage things. - * - * Right now, each lock type also needs to implement an init function, - * and trivial lock/unlock wrappers. ocfs2_simple_drop_lockres() - * should be called when the lock is no longer needed (i.e., object - * destruction time). - */ -struct ocfs2_lock_res_ops { - /* - * Translate an ocfs2_lock_res * into an ocfs2_super *. Define - * this callback if ->l_priv is not an ocfs2_super pointer - */ - struct ocfs2_super * (*get_osb)(struct ocfs2_lock_res *); - - /* - * Optionally called in the downconvert thread after a - * successful downconvert. The lockres will not be referenced - * after this callback is called, so it is safe to free - * memory, etc. - * - * The exact semantics of when this is called are controlled - * by ->downconvert_worker() - */ - void (*post_unlock)(struct ocfs2_super *, struct ocfs2_lock_res *); - - /* - * Allow a lock type to add checks to determine whether it is - * safe to downconvert a lock. Return 0 to re-queue the - * downconvert at a later time, nonzero to continue. - * - * For most locks, the default checks that there are no - * incompatible holders are sufficient. - * - * Called with the lockres spinlock held. - */ - int (*check_downconvert)(struct ocfs2_lock_res *, int); - - /* - * Allows a lock type to populate the lock value block. This - * is called on downconvert, and when we drop a lock. - * - * Locks that want to use this should set LOCK_TYPE_USES_LVB - * in the flags field. - * - * Called with the lockres spinlock held. - */ - void (*set_lvb)(struct ocfs2_lock_res *); - - /* - * Called from the downconvert thread when it is determined - * that a lock will be downconverted. This is called without - * any locks held so the function can do work that might - * schedule (syncing out data, etc). - * - * This should return any one of the ocfs2_unblock_action - * values, depending on what it wants the thread to do. - */ - int (*downconvert_worker)(struct ocfs2_lock_res *, int); - - /* - * Called before we free a lock from the system. This allows - * the filesystem to sync and invalidate caches before that - * happens. The concept is identical to ->downconvert_worker - * except for two exceptions: - * - The FS must do the full downconvert work - as if it were - * blocking an EX. - * - We do not return an ocfs2_unblock_action - this worker is not - * allowed to delay dropping of the lock. - */ - void (*drop_worker)(struct ocfs2_lock_res *); - - /* - * Optional: pretty print the lockname into a buffer - */ - void (*print)(struct ocfs2_lock_res *, char *, unsigned int); - - /* - * Optional: Lightweight event callback, intended for quick - * operations like collecting stats, etc. - */ - void (*notify_event)(struct ocfs2_lock_res *, enum ocfs2_lock_events); - - /* - * LOCK_TYPE_* flags which describe the specific requirements - * of a lock type. Descriptions of each individual flag follow. - */ - int flags; -}; - -/* - * Some locks want to "refresh" potentially stale data when a - * meaningful (PRMODE or EXMODE) lock level is first obtained. If this - * flag is set, the OCFS2_LOCK_NEEDS_REFRESH flag will be set on the - * individual lockres l_flags member from the ast function. It is - * expected that the locking wrapper will clear the - * OCFS2_LOCK_NEEDS_REFRESH flag when done. - */ -#define LOCK_TYPE_REQUIRES_REFRESH 0x1 - -/* - * Indicate that a lock type makes use of the lock value block. The - * ->set_lvb lock type callback must be defined. - */ -#define LOCK_TYPE_USES_LVB 0x2 - -struct ocfs2_lock_holder { - struct list_head oh_list; - struct pid *oh_owner_pid; -}; - -/* ocfs2_inode_lock_full() 'arg_flags' flags */ -/* don't wait on recovery. */ -#define OCFS2_META_LOCK_RECOVERY (0x01) -/* Instruct the dlm not to queue ourselves on the other node. */ -#define OCFS2_META_LOCK_NOQUEUE (0x02) -/* don't block waiting for the downconvert thread, instead return -EAGAIN */ -#define OCFS2_LOCK_NONBLOCK (0x04) -/* just get back disk inode bh if we've got cluster lock. */ -#define OCFS2_META_LOCK_GETBH (0x08) - -/* Locking subclasses of inode cluster lock */ -enum { - OI_LS_NORMAL = 0, - OI_LS_PARENT, - OI_LS_RENAME1, - OI_LS_RENAME2, - OI_LS_REFLINK_TARGET, -}; - -int ocfs2_cluster_lock(struct ocfs2_super *osb, struct ocfs2_lock_res *lockres, - int level, u32 lkm_flags, int arg_flags); -void ocfs2_cluster_unlock(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres, int level); - -int ocfs2_init_super(struct ocfs2_super *osb, int flags); -int ocfs2_dlm_init(struct ocfs2_super *osb, struct super_block *sb, - char *cluster_stack, char *cluster_name, char *ls_name, - struct dentry *debug_root); -void ocfs2_dlm_shutdown(struct ocfs2_super *osb, int hangup_pending); -void ocfs2_lock_res_init_once(struct ocfs2_lock_res *res); -void ocfs2_lock_res_init_common(struct ocfs2_super *osb, - struct ocfs2_lock_res *res, - struct ocfs2_lock_res_ops *ops, - void *priv); -void ocfs2_lock_res_free(struct ocfs2_lock_res *res); - -u64 ocfs2_lock_refresh_gen(struct ocfs2_lock_res *lockres); - -void ocfs2_mark_lockres_freeing(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres); -void ocfs2_simple_drop_lockres(struct ocfs2_super *osb, - struct ocfs2_lock_res *lockres); - -/* for the downconvert thread */ -void ocfs2_wake_downconvert_thread(struct ocfs2_super *osb); - -struct ocfs2_dlm_debug *ocfs2_new_dlm_debug(void); -void ocfs2_put_dlm_debug(struct ocfs2_dlm_debug *dlm_debug); -int ocfs2_levels_compat(struct ocfs2_lock_res *lockres, int wanted); - -#if 0 -/* To set the locking protocol on module initialization */ -void ocfs2_set_locking_protocol(void); - -/* The _tracker pair is used to avoid cluster recursive locking */ -int ocfs2_inode_lock_tracker(struct inode *inode, - struct buffer_head **ret_bh, - int ex, - struct ocfs2_lock_holder *oh); -void ocfs2_inode_unlock_tracker(struct inode *inode, - int ex, - struct ocfs2_lock_holder *oh, - int had_lock); -#endif -#endif /* DLMGLUE_H */ diff --git a/kmod/src/inode.c b/kmod/src/inode.c index 31545f0c..7207466e 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -258,7 +258,7 @@ int scoutfs_inode_refresh(struct inode *inode, struct scoutfs_lock *lock, struct scoutfs_inode_key ikey; struct scoutfs_inode sinode; SCOUTFS_DECLARE_KVEC(val); - const u64 refresh_gen = scoutfs_lock_refresh_gen(lock); + const u64 refresh_gen = lock->refresh_gen; int ret; /* @@ -1272,7 +1272,7 @@ struct inode *scoutfs_new_inode(struct super_block *sb, struct inode *dir, ci->data_version = 0; ci->next_readdir_pos = SCOUTFS_DIRENT_FIRST_POS; ci->have_item = false; - atomic64_set(&ci->last_refreshed, scoutfs_lock_refresh_gen(lock)); + atomic64_set(&ci->last_refreshed, lock->refresh_gen); ci->flags = 0; ci->ino_alloc.ino = 0; ci->ino_alloc.nr = 0; diff --git a/kmod/src/item.c b/kmod/src/item.c index 7216709e..41140ee2 100644 --- a/kmod/src/item.c +++ b/kmod/src/item.c @@ -741,39 +741,23 @@ restart: /* * Return true if the lock protects the use of the key. Some locks not - * intended for item use don't have a key range and we wan't to safely - * detect that. We use the block 'rw' constants just because they're - * convenient. The level test is racey but it's a char.. how racy can - * it be? :). + * intended for item use don't have a key range and we want to safely + * detect that. The lock mode dereference is racy but the field always + * contains a single non-zero byte. */ static bool lock_coverage(struct scoutfs_lock *lock, - struct scoutfs_key_buf *key, int op_level) + struct scoutfs_key_buf *key, int op_mode) { - signed char level; + signed char mode; if (!lock || !lock->start || !lock->end) return false; - level = ACCESS_ONCE(lock->lockres.l_level); + mode = ACCESS_ONCE(lock->granted_mode); - switch (op_level) { - case DLM_LOCK_CW: - if (level != DLM_LOCK_CW) - return false; - break; - case DLM_LOCK_PR: - if (level < DLM_LOCK_PR) - return false; - break; - case DLM_LOCK_EX: - if (level != DLM_LOCK_EX) - return false; - break; - default: - return false; - } - - return scoutfs_key_compare_ranges(key, key, + return ((op_mode == mode) || + (op_mode == DLM_LOCK_PR && mode == DLM_LOCK_EX)) && + scoutfs_key_compare_ranges(key, key, lock->start, lock->end) == 0; } @@ -1781,6 +1765,8 @@ int scoutfs_item_dirty_seg(struct super_block *sb, struct scoutfs_segment *seg) * The caller wants us to write out any dirty items within the given * range. We look for any dirty items within the range and if we find * any we issue a sync which writes out all the dirty items. + * + * Returns a sync error or the number of dirty items written. */ int scoutfs_item_writeback(struct super_block *sb, struct scoutfs_key_buf *start, @@ -1791,6 +1777,7 @@ int scoutfs_item_writeback(struct super_block *sb, struct cached_item *item; unsigned long flags; bool sync = false; + int count = 0; int ret = 0; /* XXX think about racing with trans write */ @@ -1801,8 +1788,10 @@ int scoutfs_item_writeback(struct super_block *sb, item = next_item(&cac->items, start); if (item && !(item->dirty & ITEM_DIRTY)) item = next_dirty(item); - if (item && scoutfs_key_compare(item->key, end) <= 0) + if (item && scoutfs_key_compare(item->key, end) <= 0) { sync = true; + count = cac->nr_dirty_items; + } } spin_unlock_irqrestore(&cac->lock, flags); @@ -1812,12 +1801,14 @@ int scoutfs_item_writeback(struct super_block *sb, ret = scoutfs_trans_sync(sb, 1); } - return ret; + return ret ?: count; } /* * The caller wants us to drop any items within the range on the floor. * They should have ensured that items in this range won't be dirty. + * + * Returns errors or the count of the items invalidated. */ int scoutfs_item_invalidate(struct super_block *sb, struct scoutfs_key_buf *start, @@ -1830,6 +1821,7 @@ int scoutfs_item_invalidate(struct super_block *sb, struct cached_item *item; struct rb_node *node; unsigned long flags; + int count = 0; int ret; trace_scoutfs_item_invalidate_range(sb, start, end); @@ -1866,6 +1858,7 @@ int scoutfs_item_invalidate(struct super_block *sb, WARN_ON_ONCE(item->dirty & ITEM_DIRTY); erase_item(sb, cac, item); + count++; } remove_range(sb, &cac->ranges, rng); @@ -1874,7 +1867,7 @@ int scoutfs_item_invalidate(struct super_block *sb, ret = 0; out: - return ret; + return ret ?: count; } static struct cached_item *rb_next_item(struct cached_item *item) diff --git a/kmod/src/lock.c b/kmod/src/lock.c index 446a97bf..565a1c8c 100644 --- a/kmod/src/lock.c +++ b/kmod/src/lock.c @@ -27,147 +27,67 @@ #include "scoutfs_trace.h" #include "msg.h" #include "cmp.h" -#include "dlmglue.h" #include "inode.h" #include "trans.h" #include "counters.h" #include "endian_swap.h" #include "triggers.h" +/* + * scoutfs manages internode item cache consistency using the kernel's + * dlm service. We map ranges of item keys to dlm locks and use each + * lock's modes to govern what we can do with the items under the lock. + * + * The management of locks is based around state updates that queue work + * which then acts on the state. The work calls the dlm to change modes + * and gets completion notification in callbacks. + * + * We free locks that aren't actively protecting items instead of + * converting them to NL and leaving them around. It gives us fewer + * locks consuming resources and fewer locks to wade through to try and + * diagnose a problem. + * + * So far we've only needed a minimal trylock. We don't issue a NOQUEUE + * request to the dlm which can eventually return -EAGAIN if it finds + * contention. We return -EAGAIN ourselves if a user can't immediately + * match an existing granted lock. This is fine for the only rare user + * which can back out of its lock inversion and retry with a full + * blocking lock. This saves us from having to plumb per-waiter flags + * down to dlm requests. + */ + +#define GRACE_WORK_DELAY_JIFFIES msecs_to_jiffies(2) +#define GRACE_UNLOCK_DEADLINE_KT ms_to_ktime(2) + #define LN_FMT "%u.%u.%u.%llu.%llu" #define LN_ARG(name) \ (name)->scope, (name)->zone, (name)->type, le64_to_cpu((name)->first),\ le64_to_cpu((name)->second) -typedef struct ocfs2_super dlmglue_ctxt; - /* * allocated per-super, freed on unmount. */ struct lock_info { struct super_block *sb; - dlmglue_ctxt dlmglue; - bool dlmglue_online; - char ls_name[DLM_LOCKSPACE_LEN]; - spinlock_t lock; - unsigned int seq_cnt; + bool shutdown; struct rb_root lock_tree; struct rb_root lock_range_tree; struct shrinker shrinker; struct list_head lru_list; unsigned long long lru_nr; - struct workqueue_struct *lock_reclaim_wq; + struct workqueue_struct *workq; + dlm_lockspace_t *lockspace; struct dentry *debug_locks_dentry; struct idr debug_locks_idr; + atomic64_t next_refresh_gen; }; #define DECLARE_LOCK_INFO(sb, name) \ struct lock_info *name = SCOUTFS_SB(sb)->lock_info -static void scoutfs_lock_reclaim(struct work_struct *work); - -struct task_ref { - struct task_struct *task; - struct rb_node node; - int count; - int mode;/* for debugging */ -}; - -static struct task_ref *find_task_ref(struct scoutfs_lock *lock, - struct task_struct *task) -{ - struct rb_node *n; - struct task_ref *tmp; - - spin_lock(&lock->task_refs_lock); - n = lock->task_refs.rb_node; - while (n) { - tmp = rb_entry(n, struct task_ref, node); - - if (tmp->task < task) - n = n->rb_left; - else if (tmp->task > task) - n = n->rb_right; - else { - spin_unlock(&lock->task_refs_lock); - return tmp; - } - } - spin_unlock(&lock->task_refs_lock); - - return NULL; -} - -static struct task_ref *alloc_task_ref(struct task_struct *task, int mode) -{ - struct task_ref *ref = kzalloc(sizeof(*ref), GFP_NOFS); - if (ref) { - ref->task = task; - ref->count = 1; - ref->mode = mode; - RB_CLEAR_NODE(&ref->node); - } - return ref; -} - -static void insert_task_ref(struct scoutfs_lock *lock, struct task_ref *ref) -{ - struct task_ref *tmp; - struct rb_node *parent = NULL; - struct rb_node **p; - - spin_lock(&lock->task_refs_lock); - p = &lock->task_refs.rb_node; - while (*p) { - parent = *p; - - tmp = rb_entry(parent, struct task_ref, node); - - if (tmp->task < ref->task) - p = &(*p)->rb_left; - else if (tmp->task > ref->task) - p = &(*p)->rb_right; - else - BUG(); /* We should never find a duplicate */ - } - - rb_link_node(&ref->node, parent, p); - rb_insert_color(&ref->node, &lock->task_refs); - spin_unlock(&lock->task_refs_lock); -} - -static void get_task_ref(struct task_ref *ref) -{ - ref->count++; -} - -static struct task_ref *new_task_ref(struct scoutfs_lock *lock, - struct task_struct *task, int mode) -{ - struct task_ref *ref = alloc_task_ref(task, mode); - if (ref) - insert_task_ref(lock, ref); - - return ref; -} - -static int put_task_ref(struct scoutfs_lock *lock, struct task_ref *ref) -{ - if (!ref) - return 0; - - ref->count--; - if (ref->count == 0) { - spin_lock(&lock->task_refs_lock); - rb_erase(&ref->node, &lock->task_refs); - spin_unlock(&lock->task_refs_lock); - - kfree(ref); - return 0; - } - return 1; -} +static void scoutfs_lock_work(struct work_struct *work); +static void scoutfs_lock_grace_work(struct work_struct *work); /* * invalidate cached data associated with an inode whose lock is going @@ -210,29 +130,34 @@ static void invalidate_inode(struct super_block *sb, u64 ino) } /* - * Invalidate caches on this because another node wants a lock - * with the a lock with the given mode and range. We always have to - * write out dirty overlapping items. If they're writing then we need - * to also invalidate all cached overlapping structures. + * Invalidate caches associated with this lock. We're going from the + * previous mode to the next mode. */ -static int invalidate_caches(struct super_block *sb, int mode, - struct scoutfs_lock *lock) +static int lock_invalidate(struct super_block *sb, struct scoutfs_lock *lock, + int prev, int mode) { struct scoutfs_key_buf *start = lock->start; struct scoutfs_key_buf *end = lock->end; u64 ino, last; int ret; - trace_scoutfs_lock_invalidate(sb, lock); + /* any transition from a mode allowed to dirty items has to write */ + if (prev == DLM_LOCK_CW || prev == DLM_LOCK_EX) { + ret = scoutfs_item_writeback(sb, start, end); + if (ret < 0) + return ret; + if (ret > 0) { + scoutfs_add_counter(sb, lock_write_dirty_item, ret); + ret = 0; + } + } - ret = scoutfs_item_writeback(sb, start, end); - if (ret) - return ret; - - if (mode == DLM_LOCK_EX || - (mode == DLM_LOCK_PR && lock->lockres.l_level == DLM_LOCK_CW)) { - if (lock->lock_name.zone == SCOUTFS_FS_ZONE) { - ino = le64_to_cpu(lock->lock_name.first); + /* invalidate items that we could have but won't be able to use */ + if (prev == DLM_LOCK_CW || + (prev == DLM_LOCK_PR && mode != DLM_LOCK_EX) || + (prev == DLM_LOCK_EX && mode != DLM_LOCK_PR)) { + if (lock->name.zone == SCOUTFS_FS_ZONE) { + ino = le64_to_cpu(lock->name.first); last = ino + SCOUTFS_LOCK_INODE_GROUP_NR - 1; while (ino <= last) { invalidate_inode(sb, ino); @@ -241,191 +166,46 @@ static int invalidate_caches(struct super_block *sb, int mode, } ret = scoutfs_item_invalidate(sb, start, end); + if (ret > 0) { + scoutfs_add_counter(sb, lock_invalidate_clean_item, + ret); + ret = 0; + } } - /* - * Not really tracing the return value here, we're mostly - * interested in elapsed time between the top trace and this one. - */ - trace_scoutfs_lock_invalidate_ret(sb, lock); - return ret; } -static void free_scoutfs_lock(struct scoutfs_lock *lock) +static void lock_free(struct lock_info *linfo, struct scoutfs_lock *lock) { - struct lock_info *linfo; + struct super_block *sb = lock->sb; - if (lock) { - linfo = SCOUTFS_SB(lock->sb)->lock_info; + assert_spin_locked(&linfo->lock); - if (lock->debug_locks_id) { - spin_lock(&linfo->lock); - idr_remove(&linfo->debug_locks_idr, - lock->debug_locks_id); - spin_unlock(&linfo->lock); - } + trace_scoutfs_lock_free(sb, lock); + scoutfs_inc_counter(sb, lock_free); - scoutfs_inc_counter(lock->sb, lock_free); - ocfs2_lock_res_free(&lock->lockres); - scoutfs_key_free(lock->sb, lock->start); - scoutfs_key_free(lock->sb, lock->end); - BUG_ON(!RB_EMPTY_NODE(&lock->node)); - BUG_ON(!RB_EMPTY_NODE(&lock->range_node)); - kfree(lock); + BUG_ON(delayed_work_pending(&lock->grace_work)); + + if (lock->debug_locks_id) + idr_remove(&linfo->debug_locks_idr, lock->debug_locks_id); + if (!RB_EMPTY_NODE(&lock->node)) + rb_erase(&lock->node, &linfo->lock_tree); + if (!RB_EMPTY_NODE(&lock->range_node)) + rb_erase(&lock->range_node, &linfo->lock_range_tree); + if (!list_empty(&lock->lru_head)) { + list_del(&lock->lru_head); + linfo->lru_nr--; } + scoutfs_key_free(sb, lock->start); + scoutfs_key_free(sb, lock->end); + kfree(lock); } -static void put_scoutfs_lock(struct super_block *sb, struct scoutfs_lock *lock) -{ - DECLARE_LOCK_INFO(sb, linfo); - unsigned int refs; - - if (lock) { - spin_lock(&linfo->lock); - BUG_ON(!lock->refcnt); - refs = --lock->refcnt; - if (!refs) { - trace_scoutfs_lock_free(sb, lock); - rb_erase(&lock->node, &linfo->lock_tree); - RB_CLEAR_NODE(&lock->node); - if(!RB_EMPTY_NODE(&lock->range_node)) { - rb_erase(&lock->range_node, - &linfo->lock_range_tree); - RB_CLEAR_NODE(&lock->range_node); - } - list_del(&lock->lru_entry); - spin_unlock(&linfo->lock); - ocfs2_simple_drop_lockres(&linfo->dlmglue, - &lock->lockres); - free_scoutfs_lock(lock); - return; - } - spin_unlock(&linfo->lock); - } -} - -static void dec_lock_users(struct scoutfs_lock *lock) -{ - DECLARE_LOCK_INFO(lock->sb, linfo); - - spin_lock(&linfo->lock); - lock->users--; - if (list_empty(&lock->lru_entry) && lock->users == 0) { - list_add_tail(&lock->lru_entry, &linfo->lru_list); - linfo->lru_nr++; - } - spin_unlock(&linfo->lock); -} - -static struct ocfs2_super *get_ino_lock_osb(struct ocfs2_lock_res *lockres) -{ - struct scoutfs_lock *lock = lockres->l_priv; - struct super_block *sb = lock->sb; - DECLARE_LOCK_INFO(sb, linfo); - - return &linfo->dlmglue; -} - -static int ino_lock_downconvert(struct ocfs2_lock_res *lockres, int blocking) -{ - struct scoutfs_lock *lock = lockres->l_priv; - struct super_block *sb = lock->sb; - - invalidate_caches(sb, blocking, lock); - - return UNBLOCK_CONTINUE; -} - -static void ino_lock_drop(struct ocfs2_lock_res *lockres) -{ - struct scoutfs_lock *lock = lockres->l_priv; - struct super_block *sb = lock->sb; - struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); - - /* - * Locks get shut down near the end of our unmount process. By - * now everything that needs to be synced or invalidated, has - * been. - */ - if (!sbi->shutdown) - invalidate_caches(sb, DLM_LOCK_EX, lock); -} - -static void lock_name_string(struct ocfs2_lock_res *lockres, char *buf, - unsigned int len) -{ - struct scoutfs_lock *lock = lockres->l_priv; - - snprintf(buf, len, LN_FMT, LN_ARG(&lock->lock_name)); -} - -static void count_ino_lock_event(struct ocfs2_lock_res *lockres, - enum ocfs2_lock_events event) -{ - struct scoutfs_lock *lock = container_of(lockres, struct scoutfs_lock, - lockres); - struct super_block *sb = lock->sb; - - if (event == EVENT_DLM_DOWNCONVERT_WORK) - scoutfs_inc_counter(sb, lock_type_ino_downconvert); -} - -static void count_idx_lock_event(struct ocfs2_lock_res *lockres, - enum ocfs2_lock_events event) -{ - struct scoutfs_lock *lock = container_of(lockres, struct scoutfs_lock, - lockres); - struct super_block *sb = lock->sb; - - /* - * Treat all indicies together. Later we can decode the - * lockres name to get at specific indicies. - */ - if (event == EVENT_DLM_DOWNCONVERT_WORK) - scoutfs_inc_counter(sb, lock_type_idx_downconvert); -} - -static struct ocfs2_lock_res_ops scoufs_ino_lops = { - .get_osb = get_ino_lock_osb, - .downconvert_worker = ino_lock_downconvert, - .drop_worker = ino_lock_drop, - /* XXX: .check_downconvert that queries the item cache for dirty items */ - .print = lock_name_string, - .notify_event = count_ino_lock_event, - .flags = LOCK_TYPE_REQUIRES_REFRESH, -}; - -static struct ocfs2_lock_res_ops scoufs_ino_index_lops = { - .get_osb = get_ino_lock_osb, - .downconvert_worker = ino_lock_downconvert, - .drop_worker = ino_lock_drop, - .notify_event = count_idx_lock_event, - /* XXX: .check_downconvert that queries the item cache for dirty items */ - .print = lock_name_string, -}; - -static struct ocfs2_lock_res_ops scoutfs_global_lops = { - .get_osb = get_ino_lock_osb, - /* XXX: .check_downconvert that queries the item cache for dirty items */ - .print = lock_name_string, - .flags = 0, -}; - -static struct ocfs2_lock_res_ops scoutfs_node_id_lops = { - .get_osb = get_ino_lock_osb, - /* XXX: .check_downconvert that queries the item cache for dirty items */ - .downconvert_worker = ino_lock_downconvert, - .drop_worker = ino_lock_drop, - .print = lock_name_string, - .flags = 0, -}; - -static struct scoutfs_lock *alloc_scoutfs_lock(struct super_block *sb, - struct scoutfs_lock_name *lock_name, - struct ocfs2_lock_res_ops *type, - struct scoutfs_key_buf *start, - struct scoutfs_key_buf *end) +static struct scoutfs_lock *lock_alloc(struct super_block *sb, + struct scoutfs_lock_name *name, + struct scoutfs_key_buf *start, + struct scoutfs_key_buf *end) { DECLARE_LOCK_INFO(sb, linfo); @@ -439,6 +219,8 @@ static struct scoutfs_lock *alloc_scoutfs_lock(struct super_block *sb, if (lock == NULL) return NULL; + scoutfs_inc_counter(sb, lock_alloc); + idr_preload(GFP_NOFS); spin_lock(&linfo->lock); id = idr_alloc(&linfo->debug_locks_idr, lock, 1, INT_MAX, GFP_NOWAIT); @@ -447,40 +229,219 @@ static struct scoutfs_lock *alloc_scoutfs_lock(struct super_block *sb, spin_unlock(&linfo->lock); idr_preload_end(); if (id <= 0) { - free_scoutfs_lock(lock); + lock_free(linfo, lock); return NULL; } RB_CLEAR_NODE(&lock->node); RB_CLEAR_NODE(&lock->range_node); + INIT_LIST_HEAD(&lock->lru_head); if (start) { lock->start = scoutfs_key_dup(sb, start); lock->end = scoutfs_key_dup(sb, end); if (!lock->start || !lock->end) { - free_scoutfs_lock(lock); + lock_free(linfo, lock); return NULL; } } - spin_lock_init(&lock->task_refs_lock); - lock->task_refs = RB_ROOT; - RB_CLEAR_NODE(&lock->node); lock->sb = sb; - lock->lock_name = *lock_name; - INIT_LIST_HEAD(&lock->lru_entry); - ocfs2_lock_res_init_once(&lock->lockres); - BUG_ON(sizeof(struct scoutfs_lock_name) >= OCFS2_LOCK_ID_MAX_LEN); - /* kzalloc above ensures that l_name is NULL terminated */ - memcpy(&lock->lockres.l_name[0], &lock->lock_name, - sizeof(struct scoutfs_lock_name)); - ocfs2_lock_res_init_common(&linfo->dlmglue, &lock->lockres, type, lock); - INIT_WORK(&lock->reclaim_work, scoutfs_lock_reclaim); + lock->name = *name; init_waitqueue_head(&lock->waitq); + INIT_WORK(&lock->work, scoutfs_lock_work); + INIT_DELAYED_WORK(&lock->grace_work, scoutfs_lock_grace_work); + lock->granted_mode = DLM_LOCK_IV; + lock->bast_mode = DLM_LOCK_IV; + lock->work_prev_mode = DLM_LOCK_IV; + lock->work_mode = DLM_LOCK_IV; + + trace_scoutfs_lock_alloc(sb, lock); return lock; } +static void lock_inc_count(unsigned int *counts, int mode) +{ + BUG_ON(mode < 0 || mode >= SCOUTFS_LOCK_NR_MODES); + counts[mode]++; +} + +static void lock_dec_count(unsigned int *counts, int mode) +{ + BUG_ON(mode < 0 || mode >= SCOUTFS_LOCK_NR_MODES); + counts[mode]--; +} + +/* only PR and EX modes read items to populate the cache. */ +static bool lock_mode_can_read(int mode) +{ + return mode == DLM_LOCK_PR || mode == DLM_LOCK_EX; +} + +/* + * Returns true if a given user mode can be satisfied by a lock with the + * given granted mode. This is directional. A PR user is satisfied by + * an EX grant but not vice versa. + */ +static bool lock_modes_match(int granted, int user) +{ + return (granted == user) || + (granted == DLM_LOCK_EX && user == DLM_LOCK_PR); +} + +/* + * Returns true if all the actively used modes are satisfied by a lock + * of the given granted mode. + */ +static bool lock_counts_match(int granted, unsigned int *counts) +{ + int mode; + + for (mode = 0; mode < SCOUTFS_LOCK_NR_MODES; mode++) { + if (counts[mode] && !lock_modes_match(granted, mode)) + return false; + } + + return true; +} + +/* + * An idle lock has nothing going on and could be safely unlocked and freed. + */ +static bool lock_idle(struct scoutfs_lock *lock) +{ + int mode; + + if (lock->work_mode >= 0 || lock->grace_pending) + return false; + + for (mode = 0; mode < SCOUTFS_LOCK_NR_MODES; mode++) { + if (lock->waiters[mode] || lock->users[mode]) + return false; + } + + return true; +} + +/* + * Ensure forward progress on the lock after the caller has changed the lock. + * + * This is the core of the state transition engine that makes locking + * safe. Each transition has to consider the users of the lock, pending + * bast transitions, it's current mode, what mode it should be, and what + * mode to leave it in during the transition. + * + * This can free the lock if it's idle! Callers must not reference the + * lock after calling this. + */ +static void lock_process(struct lock_info *linfo, struct scoutfs_lock *lock) +{ + bool idle; + int mode; + + assert_spin_locked(&linfo->lock); + + /* nothing to do if we're shutting down, stops rearming */ + if (linfo->shutdown) + return; + + /* only idle locks are on the lru */ + idle = lock_idle(lock); + if (list_empty(&lock->lru_head) && idle) { + list_add_tail(&lock->lru_head, &linfo->lru_list); + linfo->lru_nr++; + + } else if (!list_empty(&lock->lru_head) && !idle) { + list_del_init(&lock->lru_head); + linfo->lru_nr--; + } + + /* errored locks are torn down */ + if (lock->error) { + wake_up(&lock->waitq); + goto out; + } + + /* + * Wake any waiters who might be able to use the lock now. + * Notice that this ignores the presence of basts! This lets us + * recursively acquire locks in one task without having to track + * per-task lock references. It comes at the cost of fairness. + * Spinning overlapping users can delay a bast down conversion + * indefinitely. + */ + for (mode = 0; mode < SCOUTFS_LOCK_NR_MODES; mode++) { + if (lock->waiters[mode] && + lock_modes_match(lock->granted_mode, mode)) { + wake_up(&lock->waitq); + break; + } + } + + /* + * Try to down convert a lock in response to a bast once users + * are done with it. We may have to wait for a grace period + * to expire after an unlock. + */ + if (lock->work_mode < 0 && + lock->bast_mode >= 0 && + lock_counts_match(lock->bast_mode, lock->users) && + !lock->grace_pending) { + + if (ktime_before(ktime_get(), lock->grace_deadline)) { + scoutfs_inc_counter(linfo->sb, lock_grace_enforced); + queue_delayed_work(linfo->workq, &lock->grace_work, + GRACE_WORK_DELAY_JIFFIES); + lock->grace_pending = true; + } else { + lock->work_prev_mode = lock->granted_mode; + lock->work_mode = lock->bast_mode; + lock->granted_mode = lock->bast_mode; + lock->bast_mode = DLM_LOCK_IV; + queue_work(linfo->workq, &lock->work); + } + } + + /* + * Convert on behalf of waiters who aren't satisfied by the + * current mode when it won't conflict with users or a pending + * bast conversion. The new mode may or may not match the + * current granted mode so we may or may not need to block users + * during the transition. + * + * Remember that the presence of waiters doesn't necessarily + * mean that they're blocked. Multiple lock attempts naturally + * line up to add themselves to the waiters count before each + * calls lock_wait() and is transitioned to a user. + */ + for (mode = 0; mode < SCOUTFS_LOCK_NR_MODES; mode++) { + if (lock->work_mode < 0 && + lock->waiters[mode] && + !lock_modes_match(lock->granted_mode, mode) && + lock_counts_match(mode, lock->users) && + (lock->bast_mode < 0 || + lock_modes_match(lock->bast_mode, mode))) { + + lock->work_prev_mode = lock->granted_mode; + lock->work_mode = mode; + if (!lock_modes_match(mode, lock->granted_mode)) + lock->granted_mode = DLM_LOCK_NL; + queue_work(linfo->workq, &lock->work); + break; + } + } + +out: + /* + * We can free the lock once it's idle and it's either never + * been initially locked or has been unlocked, both of which we + * indicate with IV. + */ + if (lock_idle(lock) && lock->granted_mode == DLM_LOCK_IV) + lock_free(linfo, lock); +} + static int cmp_lock_names(struct scoutfs_lock_name *a, struct scoutfs_lock_name *b) { @@ -491,7 +452,7 @@ static int cmp_lock_names(struct scoutfs_lock_name *a, scoutfs_cmp_u64s(le64_to_cpu(a->second), le64_to_cpu(b->second)); } -static int insert_range_node(struct super_block *sb, struct scoutfs_lock *ins) +static bool insert_range_node(struct super_block *sb, struct scoutfs_lock *ins) { DECLARE_LOCK_INFO(sb, linfo); struct rb_root *root = &linfo->lock_range_tree; @@ -500,9 +461,6 @@ static int insert_range_node(struct super_block *sb, struct scoutfs_lock *ins) struct scoutfs_lock *lock; int cmp; - if (!ins->start) - return 0; - while (*node) { parent = *node; lock = container_of(*node, struct scoutfs_lock, range_node); @@ -511,11 +469,11 @@ static int insert_range_node(struct super_block *sb, struct scoutfs_lock *ins) lock->start, lock->end); if (WARN_ON_ONCE(cmp == 0)) { scoutfs_warn_sk(sb, "inserting lock %p name "LN_FMT" start "SK_FMT" end "SK_FMT" overlaps with existing lock %p name "LN_FMT" start "SK_FMT" end "SK_FMT"\n", - ins, LN_ARG(&ins->lock_name), + ins, LN_ARG(&ins->name), SK_ARG(ins->start), SK_ARG(ins->end), - lock, LN_ARG(&lock->lock_name), + lock, LN_ARG(&lock->name), SK_ARG(lock->start), SK_ARG(lock->end)); - return -EINVAL; + return false; } if (cmp < 0) @@ -528,26 +486,22 @@ static int insert_range_node(struct super_block *sb, struct scoutfs_lock *ins) rb_link_node(&ins->range_node, parent, node); rb_insert_color(&ins->range_node, root); - return 0; + return true; } -static struct scoutfs_lock *find_alloc_scoutfs_lock(struct super_block *sb, - struct scoutfs_lock_name *lock_name, - struct ocfs2_lock_res_ops *type, - struct scoutfs_key_buf *start, - struct scoutfs_key_buf *end) +static struct scoutfs_lock *lock_rb_walk(struct super_block *sb, + struct scoutfs_lock_name *name, + struct scoutfs_lock *ins) { DECLARE_LOCK_INFO(sb, linfo); - struct scoutfs_lock *new = NULL; struct scoutfs_lock *found; struct scoutfs_lock *lock; struct rb_node *parent; struct rb_node **node; int cmp; - int ret; -search: - spin_lock(&linfo->lock); + assert_spin_locked(&linfo->lock); + node = &linfo->lock_tree.rb_node; parent = NULL; found = NULL; @@ -555,7 +509,7 @@ search: parent = *node; lock = container_of(*node, struct scoutfs_lock, node); - cmp = cmp_lock_names(lock_name, &lock->lock_name); + cmp = cmp_lock_names(name, &lock->name); if (cmp < 0) { node = &(*node)->rb_left; } else if (cmp > 0) { @@ -567,216 +521,320 @@ search: lock = NULL; } - if (!found) { - if (!new) { - spin_unlock(&linfo->lock); - new = alloc_scoutfs_lock(sb, lock_name, type, start, - end); - if (!new) - return NULL; - - goto search; - } - found = new; - new = NULL; - found->refcnt = 1; /* Freed by shrinker or on umount */ - found->sequence = ++linfo->seq_cnt; - - ret = insert_range_node(sb, found); - if (ret < 0) { - spin_unlock(&linfo->lock); - free_scoutfs_lock(found); - return NULL; - } - - trace_scoutfs_lock_rb_insert(sb, found); - rb_link_node(&found->node, parent, node); - rb_insert_color(&found->node, &linfo->lock_tree); - scoutfs_inc_counter(sb, lock_alloc); - } - found->refcnt++; - if (test_bit(SCOUTFS_LOCK_RECLAIM, &found->flags)) { - spin_unlock(&linfo->lock); - wait_event(found->waitq, - test_bit(SCOUTFS_LOCK_DROPPED, &found->flags)); - put_scoutfs_lock(sb, found); - goto search; + if (!found && ins) { + rb_link_node(&ins->node, parent, node); + rb_insert_color(&ins->node, &linfo->lock_tree); + found = ins; } - if (!list_empty(&found->lru_entry)) { - list_del_init(&found->lru_entry); - linfo->lru_nr--; - } - found->users++; - spin_unlock(&linfo->lock); - - free_scoutfs_lock(new); return found; } -static void scoutfs_lock_reclaim(struct work_struct *work) -{ - struct scoutfs_lock *lock = container_of(work, struct scoutfs_lock, - reclaim_work); - struct lock_info *linfo = SCOUTFS_SB(lock->sb)->lock_info; - - trace_scoutfs_lock_reclaim(lock->sb, lock); - - /* - * Drop the last ref on our lock here, allowing us to clean up - * the dlm lock. We might race with another process in - * find_alloc_scoutfs_lock(), hence the dropped flag telling - * those processes to go ahead and drop the lock ref as well. - */ - BUG_ON(lock->users); - - set_bit(SCOUTFS_LOCK_DROPPED, &lock->flags); - wake_up(&lock->waitq); - - put_scoutfs_lock(linfo->sb, lock); -} - -void scoutfs_free_unused_locks(struct super_block *sb, unsigned long nr) -{ - struct lock_info *linfo = SCOUTFS_SB(sb)->lock_info; - struct scoutfs_lock *lock; - struct scoutfs_lock *tmp; - unsigned long flags; - - spin_lock_irqsave(&linfo->lock, flags); - list_for_each_entry_safe(lock, tmp, &linfo->lru_list, lru_entry) { - if (nr-- == 0) - break; - - trace_shrink_lock_tree(linfo->sb, lock); - - WARN_ON(lock->users); - - set_bit(SCOUTFS_LOCK_RECLAIM, &lock->flags); - list_del_init(&lock->lru_entry); - linfo->lru_nr--; - - queue_work(linfo->lock_reclaim_wq, &lock->reclaim_work); - } - spin_unlock_irqrestore(&linfo->lock, flags); -} - -static int shrink_lock_tree(struct shrinker *shrink, struct shrink_control *sc) -{ - struct lock_info *linfo = container_of(shrink, struct lock_info, - shrinker); - unsigned long nr; - int ret; - - nr = sc->nr_to_scan; - if (nr) - scoutfs_free_unused_locks(linfo->sb, nr); - - ret = min_t(unsigned long, linfo->lru_nr, INT_MAX); - trace_scoutfs_lock_shrink_exit(linfo->sb, sc->nr_to_scan, ret); - return ret; -} - -static void free_lock_tree(struct super_block *sb) +/* + * A dlm lock, conversion, or unlock call has finished. We don't + * strictly serialize the arrival of basts and our dlm calls. It's + * possible and safe for us to get a deadlock notification because we + * tried to convert in conflict with a received bast. We ignore the + * result of the deadlock conversion and processing will retry and this + * time prefer the bast. + */ +static void scoutfs_lock_ast(void *arg) { + struct scoutfs_lock *lock = arg; + struct super_block *sb = lock->sb; DECLARE_LOCK_INFO(sb, linfo); - struct rb_node *node = rb_first(&linfo->lock_tree); + int status = lock->lksb.sb_status; - while (node) { - struct scoutfs_lock *lock; + scoutfs_inc_counter(sb, lock_ast); - lock = rb_entry(node, struct scoutfs_lock, node); - node = rb_next(node); - put_scoutfs_lock(sb, lock); + spin_lock(&linfo->lock); + + if (status == 0) { + if (lock_mode_can_read(lock->work_mode) && + !lock_mode_can_read(lock->work_prev_mode)) { + lock->refresh_gen = + atomic64_inc_return(&linfo->next_refresh_gen); + } + lock->granted_mode = lock->work_mode; + + } else if (status == -DLM_EUNLOCK) { + lock->granted_mode = DLM_LOCK_IV; + + } else if (status == -EDEADLK) { + /* dlm request conflicted with racing bast, try again */ + scoutfs_inc_counter(sb, lock_ast_edeadlk); + + } else if (!lock->error) { + scoutfs_inc_counter(sb, lock_ast_error); + lock->error = status; } + + lock->work_prev_mode = DLM_LOCK_IV; + lock->work_mode = DLM_LOCK_IV; + + trace_scoutfs_lock_ast(sb, lock); + lock_process(linfo, lock); + + spin_unlock(&linfo->lock); } /* - * Acquire a coherent lock on the given range of keys. While the lock - * is held other lockers are serialized. Cache coherency is maintained - * by the locking infrastructure. Lock acquisition causes writeout from - * or invalidation of other caches. + * A lock on this node has blocked a lock request on another node. * - * The caller provides the opaque lock structure used for storage and - * their start and end pointers will be accessed while the lock is held. + * We can down convert to a PR if we had an EX and they're trying to get + * a PR but all other conflicts cause us to drop our lock and invalidate + * our cache. + */ +static void scoutfs_lock_bast(void *arg, int blocked_mode) +{ + struct scoutfs_lock *lock = arg; + struct super_block *sb = lock->sb; + DECLARE_LOCK_INFO(sb, linfo); + + scoutfs_inc_counter(sb, lock_bast); + + spin_lock(&linfo->lock); + + if (lock->granted_mode == DLM_LOCK_EX && blocked_mode == DLM_LOCK_PR) + lock->bast_mode = DLM_LOCK_PR; + else + lock->bast_mode = DLM_LOCK_NL; + + trace_scoutfs_lock_bast(sb, lock); + lock_process(linfo, lock); + + spin_unlock(&linfo->lock); +} + +/* + * The actual work of sending lock requests to the dlm. There's only + * one of these per lock and the work_mode ensures that there's only one + * transition in flight at a time. + */ +static void scoutfs_lock_work(struct work_struct *work) +{ + struct scoutfs_lock *lock = container_of(work, struct scoutfs_lock, + work); + struct super_block *sb = lock->sb; + DECLARE_LOCK_INFO(sb, linfo); + int dlm_flags; + int prev; + int mode; + int ret; + + spin_lock(&linfo->lock); + + /* don't try to call a released lockspace during shutdown */ + if (linfo->shutdown) { + spin_unlock(&linfo->lock); + return; + } + + trace_scoutfs_lock_work(sb, lock); + prev = lock->work_prev_mode; + mode = lock->work_mode; + + spin_unlock(&linfo->lock); + + if (lock->start) { + ret = lock_invalidate(sb, lock, prev, mode); + BUG_ON(ret); + } + + scoutfs_inc_counter(sb, lock_dlm_call); + + if (mode == DLM_LOCK_NL) { + ret = dlm_unlock(linfo->lockspace, lock->lksb.sb_lkid, 0, + &lock->lksb, lock); + } else { + dlm_flags = DLM_LKF_NOORDER; + if (prev >= 0) + dlm_flags |= DLM_LKF_CONVERT; + ret = dlm_lock(linfo->lockspace, mode, &lock->lksb, dlm_flags, + &lock->name, sizeof(lock->name), 0, + scoutfs_lock_ast, lock, scoutfs_lock_bast); + } + /* + * I don't think the lock error handling is correct yet. It + * probably doesn't try to unlock a lock that saw an error. + */ + if (ret) + scoutfs_inc_counter(sb, lock_dlm_call_error); + BUG_ON(ret); + + spin_lock(&linfo->lock); + + if (ret < 0) { + if (!lock->error) + lock->error = ret; + lock->work_prev_mode = DLM_LOCK_IV; + lock->work_mode = DLM_LOCK_IV; + lock_process(linfo, lock); + } + + spin_unlock(&linfo->lock); +} + +/* + * The grace period has elapsed since a down conversion attempt too soon + * after an unlock. It can now be down converted. + */ +static void scoutfs_lock_grace_work(struct work_struct *work) +{ + struct scoutfs_lock *lock = container_of(work, struct scoutfs_lock, + grace_work.work); + struct super_block *sb = lock->sb; + DECLARE_LOCK_INFO(sb, linfo); + + BUG_ON(lock->grace_pending == false); + + spin_lock(&linfo->lock); + trace_scoutfs_lock_grace_work(sb, lock); + scoutfs_inc_counter(linfo->sb, lock_grace_expired); + lock->grace_pending = false; + lock_process(linfo, lock); + spin_unlock(&linfo->lock); +} + +/* + * Wait for a lock attempt to be resolved. We return as an active user + * once our mode is satisfied by the lock or we can return errors. + */ +static bool lock_wait(struct lock_info *linfo, struct scoutfs_lock *lock, + int mode, int flags, int *ret) +{ + struct super_block *sb = linfo->sb; + bool done; + + spin_lock(&linfo->lock); + + trace_scoutfs_lock_wait(sb, lock); + + if (lock_modes_match(lock->granted_mode, mode)) { + /* the fast path where we can use the granted mode */ + lock_dec_count(lock->waiters, mode); + lock_inc_count(lock->users, mode); + *ret = 0; + done = true; + + } else if (linfo->shutdown) { + /* locking is going away */ + *ret = -ESHUTDOWN; + done = true; + + } else if (lock->error) { + /* something horrible has happened */ + *ret = lock->error; + done = true; + + } else if (flags & SCOUTFS_LKF_NONBLOCK) { + /* never wait for "nonblocking" callers */ + scoutfs_inc_counter(sb, lock_nonblock_eagain); + *ret = -EAGAIN; + done = true; + + } else { + /* still waiting :/ */ + *ret = 0; + done = false; + } + + lock_process(linfo, lock); + + spin_unlock(&linfo->lock); + + return done; +} + +/* + * Acquire a coherent lock on the given range of keys. On success the + * caller can use the given mode to interact with the item cache. While + * holding the lock the cache won't be invalidated and other conflicting + * lock users will be serialized. The item cache can be invalidated + * once the lock is unlocked. */ static int lock_name_keys(struct super_block *sb, int mode, int flags, - struct scoutfs_lock_name *lock_name, - struct ocfs2_lock_res_ops *type, - struct scoutfs_key_buf *start, - struct scoutfs_key_buf *end, - struct scoutfs_lock **ret_lock) + struct scoutfs_lock_name *name, + struct scoutfs_key_buf *start, + struct scoutfs_key_buf *end, + struct scoutfs_lock **ret_lock) { DECLARE_LOCK_INFO(sb, linfo); struct scoutfs_lock *lock; - struct task_ref *ref = NULL; - int lkm_flags; + struct scoutfs_lock *ins; + int wait_ret; int ret; + scoutfs_inc_counter(sb, lock_lock); + *ret_lock = NULL; - if (WARN_ON_ONCE(!(flags & SCOUTFS_LKF_TRYLOCK) && - scoutfs_trans_held())) - return -EINVAL; + /* maybe catch _setup() order mistakes */ + if (WARN_ON_ONCE(!linfo || linfo->lockspace == NULL)) + return -ENOLCK; - lock = find_alloc_scoutfs_lock(sb, lock_name, type, start, end); - if (!lock) - return -ENOMEM; + /* have to lock before entering transactions */ + if (WARN_ON_ONCE(scoutfs_trans_held())) + return -EDEADLK; - trace_scoutfs_lock_resource(sb, lock); + ins = NULL; +retry: + spin_lock(&linfo->lock); - if (!(flags & SCOUTFS_LKF_NO_TASK_REF)) { - ref = find_task_ref(lock, current); - if (ref) { - /* - * We found a ref, which means we have already locked - * this resource. Check that the calling task isn't - * trying to switch modes in the middle of a recursive - * lock request. - */ - BUG_ON(!ocfs2_levels_compat(&lock->lockres, mode)); - get_task_ref(ref); - dec_lock_users(lock); - put_scoutfs_lock(sb, lock); - ret = 0; - goto out; - } + /* don't create locks once we're shutdown */ + if (linfo->shutdown) { + spin_unlock(&linfo->lock); + ret = -ESHUTDOWN; + goto out; + } - ref = new_task_ref(lock, current, mode); - if (!ref) { + lock = lock_rb_walk(sb, name, ins); + if (!lock) { + spin_unlock(&linfo->lock); + ins = lock_alloc(sb, name, start, end); + if (!ins) { ret = -ENOMEM; goto out; } + goto retry; + + } else if (lock == ins) { + if (start && !insert_range_node(sb, ins)) { + lock_free(linfo, ins); + spin_unlock(&linfo->lock); + ret = -EINVAL; + goto out; + } + + } else if (ins) { + lock_free(linfo, ins); } - lkm_flags = DLM_LKF_NOORDER; - if (flags & SCOUTFS_LKF_TRYLOCK) - lkm_flags |= DLM_LKF_NOQUEUE; /* maybe also NONBLOCK? */ + lock_inc_count(lock->waiters, mode); + spin_unlock(&linfo->lock); - ret = ocfs2_cluster_lock(&linfo->dlmglue, &lock->lockres, mode, - lkm_flags, 0); -out: + ret = wait_event_interruptible(lock->waitq, + lock_wait(linfo, lock, mode, flags, + &wait_ret)); + if (ret == 0) + ret = wait_ret; if (ret) { - put_task_ref(lock, ref); - dec_lock_users(lock); - put_scoutfs_lock(sb, lock); + scoutfs_inc_counter(sb, lock_lock_error); + spin_lock(&linfo->lock); + lock_dec_count(lock->waiters, mode); + lock_process(linfo, lock); + spin_unlock(&linfo->lock); } else { - trace_scoutfs_lock(sb, lock); *ret_lock = lock; } - +out: return ret; } -u64 scoutfs_lock_refresh_gen(struct scoutfs_lock *lock) -{ - return ocfs2_lock_refresh_gen(&lock->lockres); -} - int scoutfs_lock_ino(struct super_block *sb, int mode, int flags, u64 ino, struct scoutfs_lock **ret_lock) { - struct scoutfs_lock_name lock_name; + struct scoutfs_lock_name name; struct scoutfs_inode_key start_ikey; struct scoutfs_inode_key end_ikey; struct scoutfs_key_buf start; @@ -784,11 +842,11 @@ int scoutfs_lock_ino(struct super_block *sb, int mode, int flags, u64 ino, ino &= ~(u64)SCOUTFS_LOCK_INODE_GROUP_MASK; - lock_name.scope = SCOUTFS_LOCK_SCOPE_FS_ITEMS; - lock_name.zone = SCOUTFS_FS_ZONE; - lock_name.type = SCOUTFS_INODE_TYPE; - lock_name.first = cpu_to_le64(ino); - lock_name.second = 0; + name.scope = SCOUTFS_LOCK_SCOPE_FS_ITEMS; + name.zone = SCOUTFS_FS_ZONE; + name.type = SCOUTFS_INODE_TYPE; + name.first = cpu_to_le64(ino); + name.second = 0; start_ikey.zone = SCOUTFS_FS_ZONE; start_ikey.ino = cpu_to_be64(ino); @@ -800,8 +858,7 @@ int scoutfs_lock_ino(struct super_block *sb, int mode, int flags, u64 ino, end_ikey.type = ~0; scoutfs_key_init(&end, &end_ikey, sizeof(end_ikey)); - return lock_name_keys(sb, mode, flags, &lock_name, &scoufs_ino_lops, - &start, &end, ret_lock); + return lock_name_keys(sb, mode, flags, &name, &start, &end, ret_lock); } /* @@ -926,14 +983,13 @@ int scoutfs_lock_inodes(struct super_block *sb, int mode, int flags, int scoutfs_lock_global(struct super_block *sb, int mode, int flags, int type, struct scoutfs_lock **lock) { - struct scoutfs_lock_name lock_name; + struct scoutfs_lock_name name; - memset(&lock_name, 0, sizeof(lock_name)); - lock_name.scope = SCOUTFS_LOCK_SCOPE_GLOBAL; - lock_name.type = type; + memset(&name, 0, sizeof(name)); + name.scope = SCOUTFS_LOCK_SCOPE_GLOBAL; + name.type = type; - return lock_name_keys(sb, mode, flags, &lock_name, &scoutfs_global_lops, - NULL, NULL, lock); + return lock_name_keys(sb, mode, flags, &name, NULL, NULL, lock); } /* @@ -987,7 +1043,7 @@ int scoutfs_lock_inode_index(struct super_block *sb, int mode, u8 type, u64 major, u64 ino, struct scoutfs_lock **ret_lock) { - struct scoutfs_lock_name lock_name; + struct scoutfs_lock_name name; struct scoutfs_inode_index_key start_ikey; struct scoutfs_inode_index_key end_ikey; struct scoutfs_key_buf start; @@ -996,17 +1052,16 @@ int scoutfs_lock_inode_index(struct super_block *sb, int mode, scoutfs_lock_get_index_item_range(type, major, ino, &start_ikey, &end_ikey); - lock_name.scope = SCOUTFS_LOCK_SCOPE_FS_ITEMS; - lock_name.zone = start_ikey.zone; - lock_name.type = start_ikey.type; - lock_name.first = be64_to_le64(start_ikey.major); - lock_name.second = be64_to_le64(start_ikey.ino); + name.scope = SCOUTFS_LOCK_SCOPE_FS_ITEMS; + name.zone = start_ikey.zone; + name.type = start_ikey.type; + name.first = be64_to_le64(start_ikey.major); + name.second = be64_to_le64(start_ikey.ino); scoutfs_key_init(&start, &start_ikey, sizeof(start_ikey)); scoutfs_key_init(&end, &end_ikey, sizeof(end_ikey)); - return lock_name_keys(sb, mode, 0, &lock_name, - &scoufs_ino_index_lops, &start, &end, ret_lock); + return lock_name_keys(sb, mode, 0, &name, &start, &end, ret_lock); } /* @@ -1023,17 +1078,17 @@ int scoutfs_lock_inode_index(struct super_block *sb, int mode, int scoutfs_lock_node_id(struct super_block *sb, int mode, int flags, u64 node_id, struct scoutfs_lock **lock) { - struct scoutfs_lock_name lock_name; + struct scoutfs_lock_name name; struct scoutfs_orphan_key start_okey; struct scoutfs_orphan_key end_okey; struct scoutfs_key_buf start; struct scoutfs_key_buf end; - lock_name.scope = SCOUTFS_LOCK_SCOPE_FS_ITEMS; - lock_name.zone = SCOUTFS_NODE_ZONE; - lock_name.type = 0; - lock_name.first = cpu_to_le64(node_id); - lock_name.second = 0; + name.scope = SCOUTFS_LOCK_SCOPE_FS_ITEMS; + name.zone = SCOUTFS_NODE_ZONE; + name.type = 0; + name.first = cpu_to_le64(node_id); + name.second = 0; start_okey.zone = SCOUTFS_NODE_ZONE; start_okey.node_id = cpu_to_be64(node_id); @@ -1047,119 +1102,90 @@ int scoutfs_lock_node_id(struct super_block *sb, int mode, int flags, end_okey.ino = cpu_to_be64(~0ULL); scoutfs_key_init(&end, &end_okey, sizeof(end_okey)); - return lock_name_keys(sb, mode, flags, &lock_name, - &scoutfs_node_id_lops, &start, &end, lock); -} - -void scoutfs_unlock_flags(struct super_block *sb, struct scoutfs_lock *lock, - int level, int flags) -{ - struct task_ref *ref; - DECLARE_LOCK_INFO(sb, linfo); - - if (!lock) - return; - - trace_scoutfs_unlock(sb, lock); - - if (!(flags & SCOUTFS_LKF_NO_TASK_REF)) { - ref = find_task_ref(lock, current); - BUG_ON(!ref); - if (put_task_ref(lock, ref)) - return; - } - - ocfs2_cluster_unlock(&linfo->dlmglue, &lock->lockres, level); - - dec_lock_users(lock); - - put_scoutfs_lock(sb, lock); -} - -void scoutfs_unlock(struct super_block *sb, struct scoutfs_lock *lock, - int level) -{ - scoutfs_unlock_flags(sb, lock, level, 0); + return lock_name_keys(sb, mode, flags, &name, &start, &end, lock); } /* - * The moment this is done we can have other mounts start asking - * us to write back and invalidate, so do this very very late. + * As we unlock we start a grace period. If a bast arrives before the + * grace period we'll wait for another full grace period we downconvert + * and invalidate the lock. Each unlock resets the downconvert delay. */ -static int init_lock_info(struct super_block *sb) +void scoutfs_unlock(struct super_block *sb, struct scoutfs_lock *lock, int mode) { - struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); - struct lock_info *linfo; - int ret; - - linfo = kzalloc(sizeof(struct lock_info), GFP_KERNEL); - if (!linfo) - return -ENOMEM; - - ret = ocfs2_init_super(&linfo->dlmglue, 0); - if (ret) - goto out; - - spin_lock_init(&linfo->lock); - INIT_LIST_HEAD(&linfo->lru_list); - idr_init(&linfo->debug_locks_idr); - linfo->shrinker.shrink = shrink_lock_tree; - linfo->shrinker.seeks = DEFAULT_SEEKS; - register_shrinker(&linfo->shrinker); - linfo->sb = sb; - linfo->lock_tree = RB_ROOT; - linfo->lock_range_tree = RB_ROOT; - - snprintf(linfo->ls_name, DLM_LOCKSPACE_LEN, "%llx", - le64_to_cpu(sbi->super.hdr.fsid)); - - sbi->lock_info = linfo; - - trace_init_lock_info(sb, linfo); -out: - if (ret) - kfree(linfo); - - return 0; -} - -void scoutfs_lock_destroy(struct super_block *sb) -{ - struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); DECLARE_LOCK_INFO(sb, linfo); - if (linfo) { - /* XXX does anything synchronize with open debugfs fds? */ - debugfs_remove(linfo->debug_locks_dentry); + if (IS_ERR_OR_NULL(lock)) + return; - unregister_shrinker(&linfo->shrinker); - if (linfo->lock_reclaim_wq) - destroy_workqueue(linfo->lock_reclaim_wq); - /* - * Do this before uninitializing the dlm and after - * draining the reclaim workqueue. - */ - free_lock_tree(sb); - idr_destroy(&linfo->debug_locks_idr); + scoutfs_inc_counter(sb, lock_unlock); - if (linfo->dlmglue_online) { - /* - * fs/dlm has a harmless but unannotated - * inversion between their connection and socket - * locking that triggers during shutdown and - * disables lockdep. - */ - lockdep_off(); - ocfs2_dlm_shutdown(&linfo->dlmglue, 0); - lockdep_on(); - } + spin_lock(&linfo->lock); + trace_scoutfs_lock_unlock(sb, lock); - sbi->lock_info = NULL; - - trace_scoutfs_lock_destroy(sb, linfo); - - kfree(linfo); + lock_dec_count(lock->users, mode); + lock->grace_deadline = ktime_add(ktime_get(), GRACE_UNLOCK_DEADLINE_KT); + if (cancel_delayed_work(&lock->grace_work)) { + scoutfs_inc_counter(linfo->sb, lock_grace_extended); + queue_delayed_work(linfo->workq, &lock->grace_work, + GRACE_WORK_DELAY_JIFFIES); } + + lock_process(linfo, lock); + spin_unlock(&linfo->lock); +} + +static int scoutfs_lock_shrink(struct shrinker *shrink, + struct shrink_control *sc) +{ + struct lock_info *linfo = container_of(shrink, struct lock_info, + shrinker); + struct super_block *sb = linfo->sb; + struct scoutfs_lock *lock; + struct scoutfs_lock *tmp; + unsigned long nr; + int ret; + + nr = sc->nr_to_scan; + if (nr == 0) + goto out; + + spin_lock(&linfo->lock); + + list_for_each_entry_safe(lock, tmp, &linfo->lru_list, lru_head) { + + if (nr-- == 0) + break; + + trace_scoutfs_lock_shrink(sb, lock); + scoutfs_inc_counter(sb, lock_shrink); + + WARN_ON_ONCE(!lock_idle(lock)); + + lock->work_prev_mode = lock->granted_mode; + lock->work_mode = DLM_LOCK_NL; + lock->granted_mode = DLM_LOCK_NL; + queue_work(linfo->workq, &lock->work); + + list_del_init(&lock->lru_head); + linfo->lru_nr--; + } + spin_unlock(&linfo->lock); + +out: + ret = min_t(unsigned long, linfo->lru_nr, INT_MAX); + trace_scoutfs_lock_shrink_exit(sb, sc->nr_to_scan, ret); + return ret; +} + +void scoutfs_free_unused_locks(struct super_block *sb, unsigned long nr) +{ + struct lock_info *linfo = SCOUTFS_SB(sb)->lock_info; + struct shrink_control sc = { + .gfp_mask = GFP_NOFS, + .nr_to_scan = INT_MAX, + }; + + linfo->shrinker.shrink(&linfo->shrinker, &sc); } /* _stop is always called no matter what start returns */ @@ -1203,22 +1229,24 @@ static void scoutfs_debug_locks_seq_stop(struct seq_file *m, void *v) spin_unlock(&linfo->lock); } -/* print an upper or lower case char depending on if the flag is set */ -#define locks_flag_char(lock, nr, c) \ - (test_bit(nr, &(lock)->flags) ? c : tolower(c)) - -#define locks_flags(lock) \ - locks_flag_char(lock, SCOUTFS_LOCK_RECLAIM, 'R'), \ - locks_flag_char(lock, SCOUTFS_LOCK_DROPPED, 'D') - static int scoutfs_debug_locks_seq_show(struct seq_file *m, void *v) { struct scoutfs_lock *lock = v; - SK_PCPU(seq_printf(m, "name "LN_FMT" start "SK_FMT" end "SK_FMT" sequence %u refcnt %u users %u flags %c%c\n", - LN_ARG(&lock->lock_name), SK_ARG(lock->start), - SK_ARG(lock->end), lock->sequence, lock->refcnt, - lock->users, locks_flags(lock))); + SK_PCPU(seq_printf(m, "name "LN_FMT" start "SK_FMT" end "SK_FMT" refresh_gen %llu error %d granted %d bast %d prev %d work %d waiters: pr %u ex %u cw %u users: pr %u ex %u cw %u dlmlksb: status %d lkid 0x%x flags 0x%x\n", + LN_ARG(&lock->name), SK_ARG(lock->start), + SK_ARG(lock->end), lock->refresh_gen, lock->error, + lock->granted_mode, lock->bast_mode, + lock->work_prev_mode, lock->work_mode, + lock->waiters[DLM_LOCK_PR], + lock->waiters[DLM_LOCK_EX], + lock->waiters[DLM_LOCK_CW], + lock->users[DLM_LOCK_PR], + lock->users[DLM_LOCK_EX], + lock->users[DLM_LOCK_CW], + lock->lksb.sb_status, + lock->lksb.sb_lkid, + lock->lksb.sb_flags)); return 0; } @@ -1250,16 +1278,153 @@ static const struct file_operations scoutfs_debug_locks_fops = { .llseek = seq_lseek, }; -int scoutfs_lock_setup(struct super_block *sb) +/* + * We're going to be destroying the locks soon. We shouldn't have any + * normal task holders that would have prevented unmount. We can have + * internal threads blocked in locks. We force all currently blocked + * and future lock calls to return -ESHUTDOWN. + */ +void scoutfs_lock_shutdown(struct super_block *sb) +{ + DECLARE_LOCK_INFO(sb, linfo); + struct scoutfs_lock *lock; + struct rb_node *node; + + if (!linfo) + return; + + trace_scoutfs_lock_shutdown(sb, linfo); + + spin_lock(&linfo->lock); + + linfo->shutdown = true; + for (node = rb_first(&linfo->lock_tree); node; node = rb_next(node)) { + lock = rb_entry(node, struct scoutfs_lock, node); + wake_up(&lock->waitq); + } + + spin_unlock(&linfo->lock); +} + +/* + * By the time we get here the caller should have called _shutdown() and + * then called into all the subsystems that held locks to drop them. + * There should be no active users of locks and all future lock calls + * should fail. + * + * Our job is to make sure nothing references the locks and free them. + */ +void scoutfs_lock_destroy(struct super_block *sb) { - struct lock_info *linfo; struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + DECLARE_LOCK_INFO(sb, linfo); + struct scoutfs_lock *lock; + struct rb_node *node; + int mode; int ret; - ret = init_lock_info(sb); - if (ret) - return ret; - linfo = sbi->lock_info; + if (!linfo) + return; + + BUG_ON(!linfo->shutdown); + + trace_scoutfs_lock_destroy(sb, linfo); + + /* stop the shrinker from queueing work */ + unregister_shrinker(&linfo->shrinker); + + /* make sure that no one's actively using locks */ + spin_lock(&linfo->lock); + for (node = rb_first(&linfo->lock_tree); node; node = rb_next(node)) { + lock = rb_entry(node, struct scoutfs_lock, node); + + for (mode = 0; mode < SCOUTFS_LOCK_NR_MODES; mode++) { + if (lock->waiters[mode] || lock->users[mode]) { + scoutfs_warn_sk(sb, "lock name "LN_FMT" start "SK_FMT" end "SK_FMT" has mode %d user after shutdown", + LN_ARG(&lock->name), + SK_ARG(lock->start), + SK_ARG(lock->end), mode); + break; + } + } + + if (cancel_delayed_work(&lock->grace_work)) + lock->grace_pending = false; + + } + spin_unlock(&linfo->lock); + + /* stop the dlm from calling our asts or basts to queue work */ + if (linfo->lockspace) { + /* + * fs/dlm has a harmless but unannotated inversion between their + * connection and socket locking that triggers during shutdown + * and disables lockdep. + */ + lockdep_off(); + ret = dlm_release_lockspace(linfo->lockspace, 2); + lockdep_on(); + if (ret) + scoutfs_warn(sb, "dlm lockspace leave failure: %d", + ret); + } + + if (linfo->workq) { + /* pending grace work queues normal work */ + flush_workqueue(linfo->workq); + /* now all work won't queue itself */ + destroy_workqueue(linfo->workq); + } + + /* XXX does anything synchronize with open debugfs fds? */ + debugfs_remove(linfo->debug_locks_dentry); + + /* free our stale locks that now describe released dlm locks */ + spin_lock(&linfo->lock); + node = rb_first(&linfo->lock_tree); + while (node) { + lock = rb_entry(node, struct scoutfs_lock, node); + node = rb_next(node); + lock_free(linfo, lock); + } + spin_unlock(&linfo->lock); + + idr_destroy(&linfo->debug_locks_idr); + kfree(linfo); + sbi->lock_info = NULL; +} + +int scoutfs_lock_setup(struct super_block *sb) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + char name[DLM_LOCKSPACE_LEN]; + struct lock_info *linfo; + int ret; + + /* we use >= 0 to test iv and use modes as an array index */ + BUILD_BUG_ON(DLM_LOCK_IV >= 0); + BUILD_BUG_ON(DLM_LOCK_NL >= SCOUTFS_LOCK_NR_MODES); + BUILD_BUG_ON(DLM_LOCK_PR >= SCOUTFS_LOCK_NR_MODES); + BUILD_BUG_ON(DLM_LOCK_EX >= SCOUTFS_LOCK_NR_MODES); + BUILD_BUG_ON(DLM_LOCK_CW >= SCOUTFS_LOCK_NR_MODES); + + linfo = kzalloc(sizeof(struct lock_info), GFP_KERNEL); + if (!linfo) + return -ENOMEM; + + linfo->sb = sb; + spin_lock_init(&linfo->lock); + linfo->lock_tree = RB_ROOT; + linfo->lock_range_tree = RB_ROOT; + linfo->shrinker.shrink = scoutfs_lock_shrink; + linfo->shrinker.seeks = DEFAULT_SEEKS; + register_shrinker(&linfo->shrinker); + INIT_LIST_HEAD(&linfo->lru_list); + idr_init(&linfo->debug_locks_idr); + atomic64_set(&linfo->next_refresh_gen, 0); + + sbi->lock_info = linfo; + trace_scoutfs_lock_setup(sb, linfo); linfo->debug_locks_dentry = debugfs_create_file("locks", S_IFREG|S_IRUSR, sbi->debug_root, sb, @@ -1269,20 +1434,22 @@ int scoutfs_lock_setup(struct super_block *sb) goto out; } - linfo->lock_reclaim_wq = alloc_workqueue("scoutfs_reclaim", - WQ_UNBOUND|WQ_HIGHPRI, 0); - if (!linfo->lock_reclaim_wq) { + linfo->workq = alloc_workqueue("scoutfs_lock_work", + WQ_UNBOUND|WQ_HIGHPRI, 0); + if (!linfo->workq) { ret = -ENOMEM; goto out; } - ret = ocfs2_dlm_init(&linfo->dlmglue, sb, "null", - sbi->opts.cluster_name, linfo->ls_name, - sbi->debug_root); - if (ret) - goto out; - linfo->dlmglue_online = true; + snprintf(name, DLM_LOCKSPACE_LEN, "scoutfs_fsid_%llx", + le64_to_cpu(sbi->super.hdr.fsid)); + ret = dlm_new_lockspace(name, sbi->opts.cluster_name, + DLM_LSFL_FS | DLM_LSFL_NEWEXCL, 8, + NULL, NULL, NULL, &linfo->lockspace); + if (ret) + scoutfs_warn(sb, "dlm lockspace [%s, %s] join failure: %d", + sbi->opts.cluster_name, name, ret); out: if (ret) scoutfs_lock_destroy(sb); diff --git a/kmod/src/lock.h b/kmod/src/lock.h index 75510b49..1cabe132 100644 --- a/kmod/src/lock.h +++ b/kmod/src/lock.h @@ -3,40 +3,42 @@ #include #include "key.h" -#include "dlmglue.h" #define SCOUTFS_LKF_REFRESH_INODE 0x01 /* update stale inode from item */ -#define SCOUTFS_LKF_TRYLOCK 0x02 /* EAGAIN if contention */ -#define SCOUTFS_LKF_NO_TASK_REF 0x04 /* don't create a task ref */ +#define SCOUTFS_LKF_NONBLOCK 0x02 /* only use already held locks */ -/* flags for scoutfs_lock->flags */ -enum { - SCOUTFS_LOCK_RECLAIM = 0, /* lock is queued for reclaim */ - SCOUTFS_LOCK_DROPPED, /* lock is going away, drop reference */ -}; +#define SCOUTFS_LOCK_NR_MODES (DLM_LOCK_EX + 1) +/* + * A few fields (start, end, refresh_gen, granted_mode) are referenced + * by code outside lock.c. + */ struct scoutfs_lock { struct super_block *sb; - struct scoutfs_lock_name lock_name; + struct scoutfs_lock_name name; struct scoutfs_key_buf *start; struct scoutfs_key_buf *end; - struct dlm_lksb lksb; - unsigned int sequence; /* for debugging and sanity checks */ struct rb_node node; struct rb_node range_node; - unsigned int refcnt; unsigned int debug_locks_id; - struct ocfs2_lock_res lockres; - struct list_head lru_entry; - struct work_struct reclaim_work; - unsigned int users; /* Tracks active users of this lock */ - unsigned long flags; + u64 refresh_gen; + struct list_head lru_head; wait_queue_head_t waitq; - struct rb_root task_refs; - spinlock_t task_refs_lock; + struct work_struct work; + struct dlm_lksb lksb; + ktime_t grace_deadline; + struct delayed_work grace_work; + bool grace_pending; + + int error; + int granted_mode; + int bast_mode; + int work_prev_mode; + int work_mode; + unsigned int waiters[SCOUTFS_LOCK_NR_MODES]; + unsigned int users[SCOUTFS_LOCK_NR_MODES]; }; -u64 scoutfs_lock_refresh_gen(struct scoutfs_lock *lock); int scoutfs_lock_inode(struct super_block *sb, int mode, int flags, struct inode *inode, struct scoutfs_lock **ret_lock); int scoutfs_lock_ino(struct super_block *sb, int mode, int flags, u64 ino, @@ -64,6 +66,7 @@ void scoutfs_unlock_flags(struct super_block *sb, struct scoutfs_lock *lock, void scoutfs_free_unused_locks(struct super_block *sb, unsigned long nr); int scoutfs_lock_setup(struct super_block *sb); +void scoutfs_lock_shutdown(struct super_block *sb); void scoutfs_lock_destroy(struct super_block *sb); #endif diff --git a/kmod/src/scoutfs_trace.h b/kmod/src/scoutfs_trace.h index f8e512eb..b2ce8ce9 100644 --- a/kmod/src/scoutfs_trace.h +++ b/kmod/src/scoutfs_trace.h @@ -35,8 +35,6 @@ #include "ioctl.h" #include "count.h" #include "bio.h" -#include "dlmglue.h" -#include "stackglue.h" #include "export.h" struct lock_info; @@ -1046,7 +1044,12 @@ DECLARE_EVENT_CLASS(scoutfs_lock_info_class, TP_printk(FSID_FMT" linfo %p", __entry->fsid, __entry->linfo) ); -DEFINE_EVENT(scoutfs_lock_info_class, init_lock_info, +DEFINE_EVENT(scoutfs_lock_info_class, scoutfs_lock_setup, + TP_PROTO(struct super_block *sb, struct lock_info *linfo), + TP_ARGS(sb, linfo) +); + +DEFINE_EVENT(scoutfs_lock_info_class, scoutfs_lock_shutdown, TP_PROTO(struct super_block *sb, struct lock_info *linfo), TP_ARGS(sb, linfo) ); @@ -1594,94 +1597,85 @@ DECLARE_EVENT_CLASS(scoutfs_lock_class, __field(u8, name_type) __field(u64, name_first) __field(u64, name_second) - __field(unsigned int, seq) - __field(unsigned int, refcnt) - __field(unsigned int, users) - __field(unsigned char, level) - __field(unsigned char, blocking) - __field(unsigned int, cw) - __field(unsigned int, pr) - __field(unsigned int, ex) + __field(u64, refresh_gen) + __field(int, error) + __field(int, granted_mode) + __field(int, bast_mode) + __field(int, work_prev_mode) + __field(int, work_mode) + __field(unsigned int, waiters_cw) + __field(unsigned int, waiters_pr) + __field(unsigned int, waiters_ex) + __field(unsigned int, users_cw) + __field(unsigned int, users_pr) + __field(unsigned int, users_ex) ), TP_fast_assign( __entry->fsid = FSID_ARG(sb); - __entry->name_scope = lck->lock_name.scope; - __entry->name_zone = lck->lock_name.zone; - __entry->name_type = lck->lock_name.type; - __entry->name_first = le64_to_cpu(lck->lock_name.first); - __entry->name_second = le64_to_cpu(lck->lock_name.second); - __entry->seq = lck->sequence; - __entry->refcnt = lck->refcnt; - __entry->users = lck->users; - /* racey, but safe refs of embedded struct */ - __entry->level = lck->lockres.l_level; - __entry->blocking = lck->lockres.l_blocking; - __entry->cw = lck->lockres.l_cw_holders; - __entry->pr = lck->lockres.l_ro_holders; - __entry->ex = lck->lockres.l_ex_holders; + __entry->name_scope = lck->name.scope; + __entry->name_zone = lck->name.zone; + __entry->name_type = lck->name.type; + __entry->name_first = le64_to_cpu(lck->name.first); + __entry->name_second = le64_to_cpu(lck->name.second); + + __entry->refresh_gen = lck->refresh_gen; + __entry->error = lck->error; + __entry->granted_mode = lck->granted_mode; + __entry->bast_mode = lck->bast_mode; + __entry->work_prev_mode = lck->work_prev_mode; + __entry->work_mode = lck->work_mode; + __entry->waiters_pr = lck->waiters[DLM_LOCK_PR]; + __entry->waiters_ex = lck->waiters[DLM_LOCK_EX]; + __entry->waiters_cw = lck->waiters[DLM_LOCK_CW]; + __entry->users_pr = lck->users[DLM_LOCK_PR]; + __entry->users_ex = lck->users[DLM_LOCK_EX]; + __entry->users_cw = lck->users[DLM_LOCK_CW]; ), - TP_printk("fsid "FSID_FMT" name %u.%u.%u.%llu.%llu seq %u refs %d users %d level %u blocking %u cw %u pr %u ex %u", + TP_printk("fsid "FSID_FMT" name %u.%u.%u.%llu.%llu refresh_gen %llu error %d granted %d bast %d prev %d work %d waiters: pr %u ex %u cw %u users: pr %u ex %u cw %u", __entry->fsid, __entry->name_scope, __entry->name_zone, - __entry->name_type, __entry->name_first, - __entry->name_second, __entry->seq, __entry->refcnt, - __entry->users, __entry->level, __entry->blocking, - __entry->cw, __entry->pr, __entry->ex) + __entry->name_type, __entry->name_first, __entry->name_second, + __entry->refresh_gen, __entry->error, __entry->granted_mode, + __entry->bast_mode, __entry->work_prev_mode, + __entry->work_mode, __entry->waiters_pr, + __entry->waiters_ex, __entry->waiters_cw, __entry->users_pr, + __entry->users_ex, __entry->users_cw) ); - -DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_resource, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - -DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - -DEFINE_EVENT(scoutfs_lock_class, scoutfs_unlock, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - -DEFINE_EVENT(scoutfs_lock_class, scoutfs_ast, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - -DEFINE_EVENT(scoutfs_lock_class, scoutfs_bast, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - -DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_invalidate, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - -DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_invalidate_ret, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - -DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_reclaim, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - -DEFINE_EVENT(scoutfs_lock_class, shrink_lock_tree, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - -DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_rb_insert, - TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), - TP_ARGS(sb, lck) -); - DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_free, TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), TP_ARGS(sb, lck) ); +DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_alloc, + TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), + TP_ARGS(sb, lck) +); +DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_ast, + TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), + TP_ARGS(sb, lck) +); +DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_bast, + TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), + TP_ARGS(sb, lck) +); +DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_work, + TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), + TP_ARGS(sb, lck) +); +DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_grace_work, + TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), + TP_ARGS(sb, lck) +); +DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_wait, + TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), + TP_ARGS(sb, lck) +); +DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_unlock, + TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), + TP_ARGS(sb, lck) +); +DEFINE_EVENT(scoutfs_lock_class, scoutfs_lock_shrink, + TP_PROTO(struct super_block *sb, struct scoutfs_lock *lck), + TP_ARGS(sb, lck) +); DECLARE_EVENT_CLASS(scoutfs_seg_class, TP_PROTO(struct scoutfs_segment *seg), @@ -1954,128 +1948,6 @@ DEFINE_EVENT(scoutfs_super_lifecycle_class, scoutfs_kill_sb, TP_ARGS(sb) ); -TRACE_EVENT(ocfs2_cluster_lock, - TP_PROTO(struct ocfs2_super *osb, struct ocfs2_lock_res *lockres, - int requested, unsigned int lkm_flags, unsigned int arg_flags), - - TP_ARGS(osb, lockres, requested, lkm_flags, arg_flags), - - TP_STRUCT__entry( - __string(lockspace, osb->cconn->cc_name) - __string(lockname, lockres->l_pretty_name) - __field(int, requested) - __field(unsigned int, lkm_flags) - __field(unsigned int, arg_flags) - __field(unsigned int, lockres_flags) - __field(int, lockres_level) - __field(int, blocking) - __field(unsigned int, cw_holders) - __field(unsigned int, pr_holders) - __field(unsigned int, ex_holders) - ), - - TP_fast_assign( - __assign_str(lockspace, osb->cconn->cc_name); - __assign_str(lockname, lockres->l_pretty_name); - __entry->requested = requested; - __entry->lkm_flags = lkm_flags; - __entry->arg_flags = arg_flags; - __entry->lockres_flags = lockres->l_flags; - __entry->lockres_level = lockres->l_level; - __entry->blocking = lockres->l_blocking; - __entry->cw_holders = lockres->l_cw_holders; - __entry->pr_holders = lockres->l_ro_holders; - __entry->ex_holders = lockres->l_ex_holders; - ), - - TP_printk("lockspace %s lock %s requested %d lkm_flags 0x%x arg_flags 0x%x lockres->level %d lockres->flags 0x%x lockres->blocking %d holders cw/pr/ex %u/%u/%u", - __get_str(lockspace), __get_str(lockname), __entry->requested, - __entry->lkm_flags, __entry->arg_flags, - __entry->lockres_level, __entry->lockres_flags, - __entry->blocking, __entry->cw_holders, __entry->pr_holders, - __entry->ex_holders) -); - -TRACE_EVENT(ocfs2_cluster_unlock, - TP_PROTO(struct ocfs2_super *osb, struct ocfs2_lock_res *lockres, - int level), - - TP_ARGS(osb, lockres, level), - - TP_STRUCT__entry( - __string(lockspace, osb->cconn->cc_name) - __string(lockname, lockres->l_pretty_name) - __field(int, level) - __field(unsigned int, lockres_flags) - __field(int, lockres_level) - __field(int, blocking) - __field(unsigned int, cw_holders) - __field(unsigned int, pr_holders) - __field(unsigned int, ex_holders) - ), - - TP_fast_assign( - __assign_str(lockspace, osb->cconn->cc_name); - __assign_str(lockname, lockres->l_pretty_name); - __entry->level = level; - __entry->lockres_flags = lockres->l_flags; - __entry->lockres_level = lockres->l_level; - __entry->blocking = lockres->l_blocking; - __entry->cw_holders = lockres->l_cw_holders; - __entry->pr_holders = lockres->l_ro_holders; - __entry->ex_holders = lockres->l_ex_holders; - ), - - TP_printk("lockspace %s lock %s level %d lockres->level %d lockres->flags 0x%x lockres->blocking %d holders cw/pr/ex: %u/%u/%u", - __get_str(lockspace), __get_str(lockname), __entry->level, - __entry->lockres_level, __entry->lockres_flags, - __entry->blocking, __entry->cw_holders, __entry->pr_holders, - __entry->ex_holders) -); - -DECLARE_EVENT_CLASS(ocfs2_lock_res_class, - TP_PROTO(struct ocfs2_super *osb, struct ocfs2_lock_res *lockres), - - TP_ARGS(osb, lockres), - - TP_STRUCT__entry( - __string(lockspace, osb->cconn->cc_name) - __string(lockname, lockres->l_pretty_name) - __field(unsigned int, lockres_flags) - __field(int, lockres_level) - __field(int, blocking) - __field(unsigned int, cw_holders) - __field(unsigned int, pr_holders) - __field(unsigned int, ex_holders) - ), - - TP_fast_assign( - __assign_str(lockspace, osb->cconn->cc_name); - __assign_str(lockname, lockres->l_pretty_name); - __entry->lockres_flags = lockres->l_flags; - __entry->lockres_level = lockres->l_level; - __entry->blocking = lockres->l_blocking; - __entry->cw_holders = lockres->l_cw_holders; - __entry->pr_holders = lockres->l_ro_holders; - __entry->ex_holders = lockres->l_ex_holders; - ), - - TP_printk("lockspace %s lock %s lockres->level %d lockres->flags 0x%x lockres->blocking %d holders cw/pr/ex: %u/%u/%u", - __get_str(lockspace), __get_str(lockname), - __entry->lockres_level, __entry->lockres_flags, - __entry->blocking, __entry->cw_holders, - __entry->pr_holders, __entry->ex_holders) -); - -DEFINE_EVENT(ocfs2_lock_res_class, ocfs2_simple_drop_lockres, - TP_PROTO(struct ocfs2_super *osb, struct ocfs2_lock_res *lockres), - TP_ARGS(osb, lockres) -); -DEFINE_EVENT(ocfs2_lock_res_class, ocfs2_unblock_lock, - TP_PROTO(struct ocfs2_super *osb, struct ocfs2_lock_res *lockres), - TP_ARGS(osb, lockres) -); - DECLARE_EVENT_CLASS(scoutfs_fileid_class, TP_PROTO(struct super_block *sb, int fh_type, struct scoutfs_fid *fid), TP_ARGS(sb, fh_type, fid), diff --git a/kmod/src/server.c b/kmod/src/server.c index 1b206f42..91146e81 100644 --- a/kmod/src/server.c +++ b/kmod/src/server.c @@ -932,10 +932,8 @@ static void scoutfs_server_func(struct work_struct *work) init_waitqueue_head(&waitq); - ret = scoutfs_lock_global(sb, DLM_LOCK_EX, - SCOUTFS_LKF_TRYLOCK, - SCOUTFS_LOCK_TYPE_GLOBAL_SERVER, - &lock); + ret = scoutfs_lock_global(sb, DLM_LOCK_EX, 0, + SCOUTFS_LOCK_TYPE_GLOBAL_SERVER, &lock); if (ret) goto out; diff --git a/kmod/src/stackglue.c b/kmod/src/stackglue.c deleted file mode 100644 index b8a5fbe3..00000000 --- a/kmod/src/stackglue.c +++ /dev/null @@ -1,412 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8; -*- - * vim: noexpandtab sw=8 ts=8 sts=0: - * - * stackglue.c - * - * Code which implements an OCFS2 specific interface to underlying - * cluster stacks. - * - * Copyright (C) 2007, 2009 Oracle. All rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "stackglue.h" - -static void fsdlm_lock_ast_wrapper(void *astarg) -{ - struct ocfs2_dlm_lksb *lksb = astarg; - int status = lksb->lksb_fsdlm.sb_status; - - /* - * For now we're punting on the issue of other non-standard errors - * where we can't tell if the unlock_ast or lock_ast should be called. - * The main "other error" that's possible is EINVAL which means the - * function was called with invalid args, which shouldn't be possible - * since the caller here is under our control. Other non-standard - * errors probably fall into the same category, or otherwise are fatal - * which means we can't carry on anyway. - */ - - if (status == -DLM_EUNLOCK || status == -DLM_ECANCEL) - lksb->lksb_conn->cc_proto->lp_unlock_ast(lksb, 0); - else - lksb->lksb_conn->cc_proto->lp_lock_ast(lksb); -} - -static void fsdlm_blocking_ast_wrapper(void *astarg, int level) -{ - struct ocfs2_dlm_lksb *lksb = astarg; - - lksb->lksb_conn->cc_proto->lp_blocking_ast(lksb, level); -} - -static int user_dlm_lock(struct ocfs2_cluster_connection *conn, - int mode, - struct ocfs2_dlm_lksb *lksb, - u32 flags, - void *name, - unsigned int namelen) -{ - int ret; - - if (!lksb->lksb_fsdlm.sb_lvbptr) - lksb->lksb_fsdlm.sb_lvbptr = (char *)lksb + - sizeof(struct dlm_lksb); - - ret = dlm_lock(conn->cc_lockspace, mode, &lksb->lksb_fsdlm, - flags|DLM_LKF_NODLCKWT, name, namelen, 0, - fsdlm_lock_ast_wrapper, lksb, - fsdlm_blocking_ast_wrapper); - return ret; -} - -/* - * The ocfs2_dlm_lock() and ocfs2_dlm_unlock() functions take no argument - * for the ast and bast functions. They will pass the lksb to the ast - * and bast. The caller can wrap the lksb with their own structure to - * get more information. - */ -int ocfs2_dlm_lock(struct ocfs2_cluster_connection *conn, - int mode, - struct ocfs2_dlm_lksb *lksb, - u32 flags, - void *name, - unsigned int namelen) -{ - if (!lksb->lksb_conn) - lksb->lksb_conn = conn; - else - BUG_ON(lksb->lksb_conn != conn); - return user_dlm_lock(conn, mode, lksb, flags, name, namelen); -} - -static int user_dlm_unlock(struct ocfs2_cluster_connection *conn, - struct ocfs2_dlm_lksb *lksb, - u32 flags) -{ - int ret; - - ret = dlm_unlock(conn->cc_lockspace, lksb->lksb_fsdlm.sb_lkid, - flags, &lksb->lksb_fsdlm, lksb); - return ret; -} - -int ocfs2_dlm_unlock(struct ocfs2_cluster_connection *conn, - struct ocfs2_dlm_lksb *lksb, - u32 flags) -{ - BUG_ON(lksb->lksb_conn == NULL); - - return user_dlm_unlock(conn, lksb, flags); -} - -static int user_dlm_lock_status(struct ocfs2_dlm_lksb *lksb) -{ - return lksb->lksb_fsdlm.sb_status; -} - -int ocfs2_dlm_lock_status(struct ocfs2_dlm_lksb *lksb) -{ - return user_dlm_lock_status(lksb); -} - -static int user_dlm_lvb_valid(struct ocfs2_dlm_lksb *lksb) -{ - int invalid = lksb->lksb_fsdlm.sb_flags & DLM_SBF_VALNOTVALID; - - return !invalid; -} - -int ocfs2_dlm_lvb_valid(struct ocfs2_dlm_lksb *lksb) -{ - return user_dlm_lvb_valid(lksb); -} - -static void *user_dlm_lvb(struct ocfs2_dlm_lksb *lksb) -{ - if (!lksb->lksb_fsdlm.sb_lvbptr) - lksb->lksb_fsdlm.sb_lvbptr = (char *)lksb + - sizeof(struct dlm_lksb); - return (void *)(lksb->lksb_fsdlm.sb_lvbptr); -} - -void *ocfs2_dlm_lvb(struct ocfs2_dlm_lksb *lksb) -{ - return user_dlm_lvb(lksb); -} - -void ocfs2_dlm_dump_lksb(struct ocfs2_dlm_lksb *lksb) -{ -} - -#if 0 -static int user_plock(struct ocfs2_cluster_connection *conn, - u64 ino, - struct file *file, - int cmd, - struct file_lock *fl) -{ - /* - * This more or less just demuxes the plock request into any - * one of three dlm calls. - * - * Internally, fs/dlm will pass these to a misc device, which - * a userspace daemon will read and write to. - * - * For now, cancel requests (which happen internally only), - * are turned into unlocks. Most of this function taken from - * gfs2_lock. - */ - - if (cmd == F_CANCELLK) { - cmd = F_SETLK; - fl->fl_type = F_UNLCK; - } - - if (IS_GETLK(cmd)) - return dlm_posix_get(conn->cc_lockspace, ino, file, fl); - else if (fl->fl_type == F_UNLCK) - return dlm_posix_unlock(conn->cc_lockspace, ino, file, fl); - else - return dlm_posix_lock(conn->cc_lockspace, ino, file, cmd, fl); -} - -int ocfs2_plock(struct ocfs2_cluster_connection *conn, u64 ino, - struct file *file, int cmd, struct file_lock *fl) -{ - return user_plock(conn, ino, file, cmd, fl); -} -#endif - -static void user_recover_prep(void *arg) -{ - /* XXX: Set FS in recovery here */ -} - -static void user_recover_slot(void *arg, struct dlm_slot *slot) -{ - printk(KERN_INFO "scoutfs: Node %d/%d down. Initiating recovery.\n", - slot->nodeid, slot->slot); -} - -static void user_recover_done(void *arg, struct dlm_slot *slots, - int num_slots, int our_slot, - uint32_t generation) -{ - /* XXX: Do actual fs recovery here */ -} - -static const struct dlm_lockspace_ops ocfs2_ls_ops = { - .recover_prep = user_recover_prep, - .recover_slot = user_recover_slot, - .recover_done = user_recover_done, -}; - -static int user_cluster_connect(struct ocfs2_cluster_connection *conn) -{ - dlm_lockspace_t *fsdlm; -// struct ocfs2_live_connection *lc; - int rc, ops_rv; - - BUG_ON(conn == NULL); - -#if 0 - lc = kzalloc(sizeof(struct ocfs2_live_connection), GFP_KERNEL); - if (!lc) - return -ENOMEM; - - init_waitqueue_head(&lc->oc_wait); - init_completion(&lc->oc_sync_wait); - atomic_set(&lc->oc_this_node, 0); - conn->cc_private = lc; - lc->oc_type = NO_CONTROLD; -#endif - - rc = dlm_new_lockspace(conn->cc_name, conn->cc_cluster_name, - DLM_LSFL_FS | DLM_LSFL_NEWEXCL, DLM_LVB_LEN, - &ocfs2_ls_ops, conn, &ops_rv, &fsdlm); - if (rc) { - if (rc == -EEXIST || rc == -EPROTO) - printk(KERN_ERR "scoutfs: Unable to create the " - "lockspace %s (%d), because a scoutfs-utils " - "program is running on this file system " - "with the same name lockspace\n", - conn->cc_name, rc); - goto out; - } - - if (ops_rv == -EOPNOTSUPP) { - /* - * If we get this return code, we're on a very old - * version of fs/dlm that doesn't have recovery - * callbacks enabled. - */ -// lc->oc_type = WITH_CONTROLD; - printk(KERN_NOTICE "scoutfs: You seem to be using an older " - "version of dlm_controld and/or scoutfs-utils." - " Please consider upgrading.\n"); - } else if (ops_rv) { - rc = ops_rv; - goto out; - } - conn->cc_lockspace = fsdlm; - -#if 0 - rc = ocfs2_live_connection_attach(conn, lc); - if (rc) - goto out; - - if (lc->oc_type == NO_CONTROLD) { - rc = get_protocol_version(conn); - if (rc) { - printk(KERN_ERR "ocfs2: Could not determine" - " locking version\n"); - user_cluster_disconnect(conn); - goto out; - } - wait_event(lc->oc_wait, (atomic_read(&lc->oc_this_node) > 0)); - } - - /* - * running_proto must have been set before we allowed any mounts - * to proceed. - */ - if (fs_protocol_compare(&running_proto, &conn->cc_version)) { - printk(KERN_ERR - "Unable to mount with fs locking protocol version " - "%u.%u because negotiated protocol is %u.%u\n", - conn->cc_version.pv_major, conn->cc_version.pv_minor, - running_proto.pv_major, running_proto.pv_minor); - rc = -EPROTO; - ocfs2_live_connection_drop(lc); - lc = NULL; - } -#endif -out: -#if 0 - if (rc) - kfree(lc); -#endif - return rc; -} - -int ocfs2_cluster_connect(const char *stack_name, - const char *cluster_name, - int cluster_name_len, - const char *group, - int grouplen, - struct ocfs2_locking_protocol *lproto, - void (*recovery_handler)(int node_num, - void *recovery_data), - void *recovery_data, - struct ocfs2_cluster_connection **conn) -{ - int rc = 0; - struct ocfs2_cluster_connection *new_conn; - - BUG_ON(group == NULL); - BUG_ON(conn == NULL); - BUG_ON(recovery_handler == NULL); - - if (grouplen > GROUP_NAME_MAX) { - rc = -EINVAL; - goto out; - } - -#if 0 - if (memcmp(&lproto->lp_max_version, &locking_max_version, - sizeof(struct ocfs2_protocol_version))) { - rc = -EINVAL; - goto out; - } -#endif - new_conn = kzalloc(sizeof(struct ocfs2_cluster_connection), - GFP_KERNEL); - if (!new_conn) { - rc = -ENOMEM; - goto out; - } - - strlcpy(new_conn->cc_name, group, GROUP_NAME_MAX + 1); - new_conn->cc_namelen = grouplen; - if (cluster_name_len) - strlcpy(new_conn->cc_cluster_name, cluster_name, - CLUSTER_NAME_MAX + 1); - new_conn->cc_cluster_name_len = cluster_name_len; - new_conn->cc_recovery_handler = recovery_handler; - new_conn->cc_recovery_data = recovery_data; - - new_conn->cc_proto = lproto; - /* Start the new connection at our maximum compatibility level */ - new_conn->cc_version = lproto->lp_max_version; - -#if 0 - /* This will pin the stack driver if successful */ - rc = ocfs2_stack_driver_get(stack_name); - if (rc) - goto out_free; -#endif - - rc = user_cluster_connect(new_conn); - if (rc) { -// ocfs2_stack_driver_put(); - goto out_free; - } - - *conn = new_conn; - -out_free: - if (rc) - kfree(new_conn); - -out: - return rc; -} - -static int user_cluster_disconnect(struct ocfs2_cluster_connection *conn) -{ - dlm_release_lockspace(conn->cc_lockspace, 2); - conn->cc_lockspace = NULL; - conn->cc_private = NULL; - return 0; -} - -/* If hangup_pending is 0, the stack driver will be dropped */ -int ocfs2_cluster_disconnect(struct ocfs2_cluster_connection *conn, - int hangup_pending) -{ - int ret; - - BUG_ON(conn == NULL); - - ret = user_cluster_disconnect(conn); - - /* XXX Should we free it anyway? */ - if (!ret) { - kfree(conn); -#if 0 - if (!hangup_pending) - ocfs2_stack_driver_put(); -#endif - } - - return ret; -} diff --git a/kmod/src/stackglue.h b/kmod/src/stackglue.h deleted file mode 100644 index e3db7678..00000000 --- a/kmod/src/stackglue.h +++ /dev/null @@ -1,149 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8; -*- - * vim: noexpandtab sw=8 ts=8 sts=0: - * - * stackglue.h - * - * Glue to the underlying cluster stack. - * - * Copyright (C) 2007 Oracle. All rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - */ - - -#ifndef STACKGLUE_H -#define STACKGLUE_H - -#include -#include -#include - -#include -#include - -#define DLM_LVB_LEN 64 - -/* Needed for plock-related prototypes */ -struct file; -struct file_lock; - -/* Scoutfs never uses this flag, we define it to zero to avoid errors */ -#define DLM_LKF_LOCAL 0 - -/* - * This shadows DLM_LOCKSPACE_LEN in fs/dlm/dlm_internal.h. That probably - * wants to be in a public header. - */ -#define GROUP_NAME_MAX 64 - -/* This shadows OCFS2_CLUSTER_NAME_LEN */ -#define CLUSTER_NAME_MAX 16 - -/* - * ocfs2_protocol_version changes when ocfs2 does something different in - * its inter-node behavior. See dlmglue.c for more information. - */ -struct ocfs2_protocol_version { - u8 pv_major; - u8 pv_minor; -}; - -/* - * The dlm_lockstatus struct includes lvb space, but the dlm_lksb struct only - * has a pointer to separately allocated lvb space. This struct exists only to - * include in the lksb union to make space for a combined dlm_lksb and lvb. - */ -struct fsdlm_lksb_plus_lvb { - struct dlm_lksb lksb; - char lvb[DLM_LVB_LEN]; -}; - -/* - * A union of all lock status structures. We define it here so that the - * size of the union is known. Lock status structures are embedded in - * ocfs2 inodes. - */ -struct ocfs2_cluster_connection; -struct ocfs2_dlm_lksb { - union { - struct dlm_lksb lksb_fsdlm; - struct fsdlm_lksb_plus_lvb padding; - }; - struct ocfs2_cluster_connection *lksb_conn; -}; - -/* - * The ocfs2_locking_protocol defines the handlers called on ocfs2's behalf. - */ -struct ocfs2_locking_protocol { - struct ocfs2_protocol_version lp_max_version; - void (*lp_lock_ast)(struct ocfs2_dlm_lksb *lksb); - void (*lp_blocking_ast)(struct ocfs2_dlm_lksb *lksb, int level); - void (*lp_unlock_ast)(struct ocfs2_dlm_lksb *lksb, int error); -}; - -/* - * A cluster connection. Mostly opaque to ocfs2, the connection holds - * state for the underlying stack. ocfs2 does use cc_version to determine - * locking compatibility. - */ -struct ocfs2_cluster_connection { - char cc_name[GROUP_NAME_MAX + 1]; - int cc_namelen; - char cc_cluster_name[CLUSTER_NAME_MAX + 1]; - int cc_cluster_name_len; - struct ocfs2_protocol_version cc_version; - struct ocfs2_locking_protocol *cc_proto; - void (*cc_recovery_handler)(int node_num, void *recovery_data); - void *cc_recovery_data; - void *cc_lockspace; - void *cc_private; -}; - -/* In ocfs2_downconvert_lock(), we need to know which stack we are using */ -static inline int ocfs2_is_o2cb_active(void) -{ - return 0; -} - -/* Used by the filesystem */ -int ocfs2_cluster_connect(const char *stack_name, - const char *cluster_name, - int cluster_name_len, - const char *group, - int grouplen, - struct ocfs2_locking_protocol *lproto, - void (*recovery_handler)(int node_num, - void *recovery_data), - void *recovery_data, - struct ocfs2_cluster_connection **conn); -int ocfs2_cluster_disconnect(struct ocfs2_cluster_connection *conn, - int hangup_pending); - -struct ocfs2_lock_res; -int ocfs2_dlm_lock(struct ocfs2_cluster_connection *conn, - int mode, - struct ocfs2_dlm_lksb *lksb, - u32 flags, - void *name, - unsigned int namelen); -int ocfs2_dlm_unlock(struct ocfs2_cluster_connection *conn, - struct ocfs2_dlm_lksb *lksb, - u32 flags); - -int ocfs2_dlm_lock_status(struct ocfs2_dlm_lksb *lksb); -int ocfs2_dlm_lvb_valid(struct ocfs2_dlm_lksb *lksb); -void *ocfs2_dlm_lvb(struct ocfs2_dlm_lksb *lksb); -void ocfs2_dlm_dump_lksb(struct ocfs2_dlm_lksb *lksb); - -int ocfs2_plock(struct ocfs2_cluster_connection *conn, u64 ino, - struct file *file, int cmd, struct file_lock *fl); - -#endif /* STACKGLUE_H */ diff --git a/kmod/src/super.c b/kmod/src/super.c index 00667954..ab2bedba 100644 --- a/kmod/src/super.c +++ b/kmod/src/super.c @@ -122,8 +122,7 @@ static void scoutfs_put_super(struct super_block *sb) sbi->shutdown = true; - scoutfs_unlock_flags(sb, sbi->node_id_lock, DLM_LOCK_EX, - SCOUTFS_LKF_NO_TASK_REF); + scoutfs_unlock(sb, sbi->node_id_lock, DLM_LOCK_EX); sbi->node_id_lock = NULL; scoutfs_shutdown_trans(sb); @@ -133,6 +132,7 @@ static void scoutfs_put_super(struct super_block *sb) scoutfs_item_destroy(sb); /* the server locks the listen address and compacts */ + scoutfs_lock_shutdown(sb); scoutfs_server_destroy(sb); scoutfs_seg_destroy(sb); scoutfs_lock_destroy(sb);