Viewing: barrier.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017, Intel Corporation.
*
* Currently, the Lustre barrier is implemented as write barrier on all MDTs.
* For each MDT in the system, when it starts, it registers a barrier instance
* that will be used in handling subsequent barrier requests.
*
* Author: Fan, Yong <fan.yong@intel.com>
*/
#define DEBUG_SUBSYSTEM S_SNAPSHOT
#include <linux/percpu_counter.h>
#include <dt_object.h>
#include <obd.h>
#include <obd_class.h>
#include <lustre_barrier.h>
#include <uapi/linux/lustre/lustre_barrier_user.h>
#include "tgt_internal.h"
static LIST_HEAD(barrier_instance_list);
static DEFINE_SPINLOCK(barrier_instance_lock);
struct barrier_instance {
struct list_head bi_link;
struct dt_device *bi_bottom;
struct dt_device *bi_next;
wait_queue_head_t bi_waitq;
rwlock_t bi_rwlock;
struct percpu_counter bi_writers;
struct kref bi_ref;
time64_t bi_deadline;
__u32 bi_status;
};
static inline char *barrier_barrier2name(struct barrier_instance *barrier)
{
return barrier->bi_bottom->dd_lu_dev.ld_obd->obd_name;
}
static inline __u32 barrier_dev_idx(struct barrier_instance *barrier)
{
return lu_site2seq(barrier->bi_bottom->dd_lu_dev.ld_site)->ss_node_id;
}
static void barrier_instance_put_free(struct kref *kref)
{
struct barrier_instance *barrier;
barrier = container_of(kref, struct barrier_instance, bi_ref);
LASSERT(list_empty(&barrier->bi_link));
percpu_counter_destroy(&barrier->bi_writers);
OBD_FREE_PTR(barrier);
}
static inline void barrier_instance_put(struct barrier_instance *barrier)
{
kref_put(&barrier->bi_ref, barrier_instance_put_free);
}
static struct barrier_instance *
barrier_instance_find_locked(struct dt_device *key)
{
struct barrier_instance *barrier;
list_for_each_entry(barrier, &barrier_instance_list, bi_link) {
if (barrier->bi_bottom == key)
return barrier;
}
return NULL;
}
static void barrier_instance_add(struct barrier_instance *barrier)
{
struct barrier_instance *tmp;
spin_lock(&barrier_instance_lock);
tmp = barrier_instance_find_locked(barrier->bi_bottom);
LASSERT(!tmp);
list_add_tail(&barrier->bi_link, &barrier_instance_list);
spin_unlock(&barrier_instance_lock);
}
static struct barrier_instance *barrier_instance_find(struct dt_device *key)
{
struct barrier_instance *barrier;
spin_lock(&barrier_instance_lock);
barrier = barrier_instance_find_locked(key);
if (barrier)
kref_get(&barrier->bi_ref);
spin_unlock(&barrier_instance_lock);
return barrier;
}
static void barrier_set(struct barrier_instance *barrier, __u32 status)
{
if (barrier->bi_status != status) {
CDEBUG(D_SNAPSHOT, "%s: change barrier status from %u to %u\n",
barrier_barrier2name(barrier),
barrier->bi_status, status);
barrier->bi_status = status;
}
}
/**
* barrier_freeze() - Create the barrier for the given instance.
* @env: pointer to the thread context
* @barrier: pointer to the barrier instance
* @phase1: indicate whether it is phase1 barrier or not
*
* We use two-phases barrier to guarantee that after the barrier setup:
* 1) All the MDT side pending async modification have been flushed.
* 2) Any subsequent modification will be blocked.
* 3) All async transactions on the MDTs have been committed.
*
* For phase1, we do the following:
*
* Firstly, it sets barrier flag on the instance that will block subsequent
* modifications from clients. (Note: server sponsored modification will be
* allowed for flush pending modifications)
*
* Secondly, it will flush all pending modification via dt_sync(), such as
* async OST-object destroy, async OST-object owner changes, and so on.
*
* If there are some on-handling clients sponsored modifications during the
* barrier freezing, then related modifications may cause pending requests
* after the first dt_sync(), so call dt_sync() again after all on-handling
* modifications done.
*
* With the phase1 barrier set, all pending cross-servers modification have
* been flushed to remote servers, and any new modification will be blocked.
* But it does not guarantees that all the updates have been committed to
* storage on remote servers. So when all the instances have done phase1
* barrier successfully, the MGS will notify all instances to do the phase2
* barrier as following:
*
* Every barrier instance will call dt_sync() to make all async transactions
* to be committed locally.
*
* Return:
* * %positive number for timeout
* * %0 for success
* * %negative error number on failure
*/
static int barrier_freeze(const struct lu_env *env,
struct barrier_instance *barrier, bool phase1)
{
time64_t left;
int rc = 0;
__s64 inflight = 0;
ENTRY;
write_lock(&barrier->bi_rwlock);
barrier_set(barrier, phase1 ? BS_FREEZING_P1 : BS_FREEZING_P2);
/* Avoid out-of-order execution the barrier_set()
* and the check of inflight modifications count. */
smp_mb();
if (phase1)
inflight = percpu_counter_sum(&barrier->bi_writers);
write_unlock(&barrier->bi_rwlock);
rc = dt_sync(env, barrier->bi_next);
if (rc) {
CDEBUG(D_SNAPSHOT, "failed with dt_sync: %d\n", rc);
RETURN(rc);
}
LASSERT(barrier->bi_deadline != 0);
left = barrier->bi_deadline - ktime_get_real_seconds();
if (left <= 0) {
CDEBUG(D_SNAPSHOT, "timed out after dt_sync: %lld, %lld\n",
left, barrier->bi_deadline);
RETURN(1);
}
if (phase1 && inflight != 0) {
rc = wait_event_idle_timeout(
barrier->bi_waitq,
percpu_counter_sum(&barrier->bi_writers) == 0,
cfs_time_seconds(left));
if (rc <= 0) {
CDEBUG(D_SNAPSHOT, "timed out waiting for bi_writers: %lld %d\n",
percpu_counter_sum(&barrier->bi_writers),
rc);
RETURN(1);
}
/* sync again after all inflight modifications done. */
rc = dt_sync(env, barrier->bi_next);
if (rc) {
CDEBUG(D_SNAPSHOT, "failed with dt_sync: %d\n", rc);
RETURN(rc);
}
if (ktime_get_real_seconds() > barrier->bi_deadline) {
CDEBUG(D_SNAPSHOT, "timed out after dt_sync: %lld, %lld\n",
left, barrier->bi_deadline);
RETURN(1);
}
}
CDEBUG(D_SNAPSHOT, "%s: barrier freezing %s done.\n",
barrier_barrier2name(barrier), phase1 ? "phase1" : "phase2");
if (!phase1)
barrier_set(barrier, BS_FROZEN);
RETURN(0);
}
void barrier_init(void)
{
}
void barrier_fini(void)
{
LASSERT(list_empty(&barrier_instance_list));
}
bool barrier_entry(struct dt_device *key)
{
struct barrier_instance *barrier;
bool entered = false;
ENTRY;
barrier = barrier_instance_find(key);
if (unlikely(!barrier))
/* Fail open */
RETURN(true);
read_lock(&barrier->bi_rwlock);
if (likely(barrier->bi_status != BS_FREEZING_P1 &&
barrier->bi_status != BS_FREEZING_P2 &&
barrier->bi_status != BS_FROZEN) ||
ktime_get_real_seconds() > barrier->bi_deadline) {
percpu_counter_inc(&barrier->bi_writers);
entered = true;
}
read_unlock(&barrier->bi_rwlock);
barrier_instance_put(barrier);
return entered;
}
EXPORT_SYMBOL(barrier_entry);
void barrier_exit(struct dt_device *key)
{
struct barrier_instance *barrier;
barrier = barrier_instance_find(key);
if (likely(barrier)) {
percpu_counter_dec(&barrier->bi_writers);
/* Avoid out-of-order execution the decreasing inflight
* modifications count and the check of barrier status. */
smp_mb();
if (unlikely(barrier->bi_status == BS_FREEZING_P1))
wake_up(&barrier->bi_waitq);
barrier_instance_put(barrier);
}
}
EXPORT_SYMBOL(barrier_exit);
int barrier_handler(struct dt_device *key, struct ptlrpc_request *req)
{
struct ldlm_gl_barrier_desc *desc;
struct barrier_instance *barrier;
struct barrier_lvb *lvb;
struct lu_env env;
int rc = 0;
ENTRY;
/* glimpse on barrier locks always packs a glimpse descriptor */
req_capsule_extend(&req->rq_pill, &RQF_LDLM_GL_CALLBACK_DESC);
desc = req_capsule_client_get(&req->rq_pill, &RMF_DLM_GL_DESC);
if (!desc)
GOTO(out, rc = -EPROTO);
req_capsule_set_size(&req->rq_pill, &RMF_DLM_LVB, RCL_SERVER,
sizeof(struct barrier_lvb));
rc = req_capsule_server_pack(&req->rq_pill);
if (rc)
GOTO(out, rc);
lvb = req_capsule_server_get(&req->rq_pill, &RMF_DLM_LVB);
barrier = barrier_instance_find(key);
if (!barrier)
GOTO(out, rc = -ENODEV);
rc = lu_env_init(&env, LCT_MD_THREAD | LCT_DT_THREAD);
if (rc)
GOTO(out_barrier, rc);
CDEBUG(D_SNAPSHOT,
"%s: handling barrier request: status %u, timeout %u\n",
barrier_barrier2name(barrier),
desc->lgbd_status, desc->lgbd_timeout);
switch (desc->lgbd_status) {
case BS_RESCAN:
barrier_set(barrier, BS_INIT);
break;
case BS_FREEZING_P1:
case BS_FREEZING_P2:
if (CFS_FAIL_CHECK(OBD_FAIL_BARRIER_FAILURE))
GOTO(fini, rc = -EINVAL);
barrier->bi_deadline = ktime_get_real_seconds() +
desc->lgbd_timeout;
rc = barrier_freeze(&env, barrier,
desc->lgbd_status == BS_FREEZING_P1);
break;
case BS_THAWING:
case BS_FAILED:
case BS_EXPIRED:
barrier_set(barrier, BS_THAWED);
break;
default:
CWARN("%s: unexpected barrier status %u\n",
barrier_barrier2name(barrier), desc->lgbd_status);
rc = -EINVAL;
break;
}
GOTO(fini, rc);
fini:
lu_env_fini(&env);
out_barrier:
if (rc < 0)
barrier_set(barrier, BS_FAILED);
else if (rc > 0)
barrier_set(barrier, BS_EXPIRED);
lvb->lvb_status = barrier->bi_status;
lvb->lvb_index = barrier_dev_idx(barrier);
CDEBUG(D_SNAPSHOT, "%s: handled barrier request: status %u, deadline %lld: rc = %d\n",
barrier_barrier2name(barrier),
lvb->lvb_status, barrier->bi_deadline, rc);
barrier_instance_put(barrier);
rc = 0;
out:
req->rq_status = rc;
return rc;
}
EXPORT_SYMBOL(barrier_handler);
int barrier_register(struct dt_device *key, struct dt_device *next)
{
struct barrier_instance *barrier;
int rc;
ENTRY;
OBD_ALLOC_PTR(barrier);
if (!barrier)
RETURN(-ENOMEM);
INIT_LIST_HEAD(&barrier->bi_link);
barrier->bi_bottom = key;
barrier->bi_next = next;
init_waitqueue_head(&barrier->bi_waitq);
rwlock_init(&barrier->bi_rwlock);
kref_init(&barrier->bi_ref);
rc = percpu_counter_init(&barrier->bi_writers, 0, GFP_KERNEL);
if (rc)
barrier_instance_put(barrier);
else
barrier_instance_add(barrier);
RETURN(rc);
}
EXPORT_SYMBOL(barrier_register);
void barrier_deregister(struct dt_device *key)
{
struct barrier_instance *barrier;
spin_lock(&barrier_instance_lock);
barrier = barrier_instance_find_locked(key);
if (barrier)
list_del_init(&barrier->bi_link);
spin_unlock(&barrier_instance_lock);
if (barrier)
barrier_instance_put(barrier);
}
EXPORT_SYMBOL(barrier_deregister);