* that allows each layer to associate arbitrary pieces of data with each
* context (see pthread_key_create(3) for similar interface).
*
+ * On a client, lu_context is bound to a thread, see cl_env_get().
+ *
+ * \see lu_context_key
*/
struct lu_context {
- /*
- * Theoretically we'd want to use lu_objects and lu_contexts on the
- * client side too. On the other hand, we don't want to allocate
- * values of server-side keys for the client contexts and vice versa.
+ /**
+ * lu_context is used on the client side too. Yet we don't want to
+ * allocate values of server-side keys for the client contexts and
+ * vice versa.
*
* To achieve this, set of tags in introduced. Contexts and keys are
* marked with tags. Key value are created only for context whose set
* from enum lu_context_tag.
*/
__u32 lc_tags;
- /*
+ /**
* Pointer to the home service thread. NULL for other execution
* contexts.
*/
struct ptlrpc_thread *lc_thread;
- /*
+ /**
* Pointer to an array with key values. Internal implementation
* detail.
*/
void **lc_value;
enum lu_context_state lc_state;
+ /**
+ * Linkage into a list of all remembered contexts. Only
+ * `non-transient' contexts, i.e., ones created for service threads
+ * are placed here.
+ */
+ struct list_head lc_remember;
+ /**
+ * Version counter used to skip calls to lu_context_refill() when no
+ * keys were registered.
+ */
+ unsigned lc_version;
};
-/*
+/**
* lu_context_key interface. Similar to pthread_key.
*/
enum lu_context_tag {
- /*
+ /**
* Thread on md server
*/
LCT_MD_THREAD = 1 << 0,
- /*
+ /**
* Thread on dt server
*/
LCT_DT_THREAD = 1 << 1,
- /*
+ /**
* Context for transaction handle
*/
LCT_TX_HANDLE = 1 << 2,
- /*
+ /**
* Thread on client
*/
LCT_CL_THREAD = 1 << 3,
- /*
- * Per-request session on server
+ /**
+ * A per-request session on a server, and a per-system-call session on
+ * a client.
*/
LCT_SESSION = 1 << 4,
- /*
+
+ /**
+ * Set when at least one of keys, having values in this context has
+ * non-NULL lu_context_key::lct_exit() method. This is used to
+ * optimize lu_context_exit() call.
+ */
+ LCT_HAS_EXIT = 1 << 28,
+ /**
* Don't add references for modules creating key values in that context.
* This is only for contexts used internally by lu_object framework.
*/
- LCT_NOREF = 1 << 30,
- /*
+ LCT_NOREF = 1 << 29,
+ /**
+ * Key is being prepared for retiring, don't create new values for it.
+ */
+ LCT_QUIESCENT = 1 << 30,
+ /**
+ * Context should be remembered.
+ */
+ LCT_REMEMBER = 1 << 31,
+ /**
* Contexts usable in cache shrinker thread.
*/
LCT_SHRINKER = LCT_MD_THREAD|LCT_DT_THREAD|LCT_CL_THREAD|LCT_NOREF
};
-/*
+/**
* Key. Represents per-context value slot.
+ *
+ * Keys are usually registered when module owning the key is initialized, and
+ * de-registered when module is unloaded. Once key is registered, all new
+ * contexts with matching tags, will get key value. "Old" contexts, already
+ * initialized at the time of key registration, can be forced to get key value
+ * by calling lu_context_refill().
+ *
+ * Every key value is counted in lu_context_key::lct_used and acquires a
+ * reference on an owning module. This means, that all key values have to be
+ * destroyed before module can be unloaded. This is usually achieved by
+ * stopping threads started by the module, that created contexts in their
+ * entry functions. Situation is complicated by the threads shared by multiple
+ * modules, like ptlrpcd daemon on a client. To work around this problem,
+ * contexts, created in such threads, are `remembered' (see
+ * LCT_REMEMBER)---i.e., added into a global list. When module is preparing
+ * for unloading it does the following:
+ *
+ * - marks its keys as `quiescent' (lu_context_tag::LCT_QUIESCENT)
+ * preventing new key values from being allocated in the new contexts,
+ * and
+ *
+ * - scans a list of remembered contexts, destroying values of module
+ * keys, thus releasing references to the module.
+ *
+ * This is done by lu_context_key_quiesce(). If module is re-activated
+ * before key has been de-registered, lu_context_key_revive() call clears
+ * `quiescent' marker.
+ *
+ * lu_context code doesn't provide any internal synchronization for these
+ * activities---it's assumed that startup (including threads start-up) and
+ * shutdown are serialized by some external means.
+ *
+ * \see lu_context
*/
struct lu_context_key {
- /*
+ /**
* Set of tags for which values of this key are to be instantiated.
*/
__u32 lct_tags;
- /*
+ /**
* Value constructor. This is called when new value is created for a
* context. Returns pointer to new value of error pointer.
*/
void *(*lct_init)(const struct lu_context *ctx,
struct lu_context_key *key);
- /*
+ /**
* Value destructor. Called when context with previously allocated
- * value of this slot is destroyed. @data is a value that was returned
- * by a matching call to ->lct_init().
+ * value of this slot is destroyed. \a data is a value that was returned
+ * by a matching call to lu_context_key::lct_init().
*/
void (*lct_fini)(const struct lu_context *ctx,
struct lu_context_key *key, void *data);
- /*
+ /**
* Optional method called on lu_context_exit() for all allocated
* keys. Can be used by debugging code checking that locks are
* released, etc.
*/
void (*lct_exit)(const struct lu_context *ctx,
struct lu_context_key *key, void *data);
- /*
- * Internal implementation detail: index within ->lc_value[] reserved
- * for this key.
+ /**
+ * Internal implementation detail: index within lu_context::lc_value[]
+ * reserved for this key.
*/
int lct_index;
- /*
+ /**
* Internal implementation detail: number of values created for this
* key.
*/
atomic_t lct_used;
- /*
+ /**
* Internal implementation detail: module for this key.
*/
struct module *lct_owner;
+ /**
+ * References to this key. For debugging.
+ */
+ struct lu_ref lct_reference;
};
#define LU_KEY_INIT(mod, type) \
(key)->lct_owner = THIS_MODULE; \
} while (0)
-
-/*
- * Register new key.
- */
int lu_context_key_register(struct lu_context_key *key);
+void lu_context_key_degister(struct lu_context_key *key);
+void *lu_context_key_get (const struct lu_context *ctx,
+ const struct lu_context_key *key);
+void lu_context_key_quiesce (struct lu_context_key *key);
+void lu_context_key_revive (struct lu_context_key *key);
+
+
/*
- * Deregister key.
+ * LU_KEY_INIT_GENERIC() has to be a macro to correctly determine an
+ * owning module.
*/
-void lu_context_key_degister(struct lu_context_key *key);
-#define LU_KEY_REGISTER_GENERIC(mod) \
- static int mod##_key_register_generic(struct lu_context_key *k, ...) \
+#define LU_KEY_INIT_GENERIC(mod) \
+ static void mod##_key_init_generic(struct lu_context_key *k, ...) \
{ \
- struct lu_context_key* key = k; \
+ struct lu_context_key *key = k; \
va_list args; \
- int result; \
\
va_start(args, k); \
- \
do { \
LU_CONTEXT_KEY_INIT(key); \
- result = lu_context_key_register(key); \
- if (result) \
- break; \
- key = va_arg(args, struct lu_context_key*); \
+ key = va_arg(args, struct lu_context_key *); \
} while (key != NULL); \
- \
- va_end(args); \
- \
- if (result) { \
- va_start(args, k); \
- while (k != key) { \
- lu_context_key_degister(k); \
- k = va_arg(args, struct lu_context_key*); \
- } \
- va_end(args); \
- } \
- \
- return result; \
- }
-
-#define LU_KEY_DEGISTER_GENERIC(mod) \
- static void mod##_key_degister_generic(struct lu_context_key *k, ...) \
- { \
- va_list args; \
- \
- va_start(args, k); \
- \
- do { \
- lu_context_key_degister(k); \
- k = va_arg(args, struct lu_context_key*); \
- } while (k != NULL); \
- \
va_end(args); \
}
#define LU_TYPE_INIT(mod, ...) \
- LU_KEY_REGISTER_GENERIC(mod) \
+ LU_KEY_INIT_GENERIC(mod) \
static int mod##_type_init(struct lu_device_type *t) \
{ \
- return mod##_key_register_generic(__VA_ARGS__, NULL); \
+ mod##_key_init_generic(__VA_ARGS__, NULL); \
+ return lu_context_key_register_many(__VA_ARGS__, NULL); \
} \
struct __##mod##_dummy_type_init {;}
#define LU_TYPE_FINI(mod, ...) \
- LU_KEY_DEGISTER_GENERIC(mod) \
static void mod##_type_fini(struct lu_device_type *t) \
{ \
- mod##_key_degister_generic(__VA_ARGS__, NULL); \
+ lu_context_key_degister_many(__VA_ARGS__, NULL); \
} \
struct __##mod##_dummy_type_fini {;}
+
+
+
#define LU_TYPE_INIT_FINI(mod, ...) \
LU_TYPE_INIT(mod, __VA_ARGS__); \
- LU_TYPE_FINI(mod, __VA_ARGS__)
+ LU_TYPE_FINI(mod, __VA_ARGS__); \
+ LU_TYPE_START(mod, __VA_ARGS__); \
+ LU_TYPE_STOP(mod, __VA_ARGS__)
-/*
- * Return value associated with key @key in context @ctx.
- */
-void *lu_context_key_get(const struct lu_context *ctx,
- struct lu_context_key *key);
+int lu_context_init (struct lu_context *ctx, __u32 tags);
+void lu_context_fini (struct lu_context *ctx);
+void lu_context_enter (struct lu_context *ctx);
+void lu_context_exit (struct lu_context *ctx);
+int lu_context_refill(struct lu_context *ctx);
/*
- * Initialize context data-structure. Create values for all keys.
- */
-int lu_context_init(struct lu_context *ctx, __u32 tags);
-/*
- * Finalize context data-structure. Destroy key values.
+ * Helper functions to operate on multiple keys. These are used by the default
+ * device type operations, defined by LU_TYPE_INIT_FINI().
*/
-void lu_context_fini(struct lu_context *ctx);
-/*
- * Called before entering context.
- */
-void lu_context_enter(struct lu_context *ctx);
-/*
- * Called after exiting from @ctx
- */
-void lu_context_exit(struct lu_context *ctx);
-
-/*
- * Allocate for context all missing keys that were registered after context
- * creation.
- */
-int lu_context_refill(const struct lu_context *ctx);
+int lu_context_key_register_many(struct lu_context_key *k, ...);
+void lu_context_key_degister_many(struct lu_context_key *k, ...);
+void lu_context_key_revive_many (struct lu_context_key *k, ...);
+void lu_context_key_quiesce_many (struct lu_context_key *k, ...);
-/*
+/**
* Environment.
*/
struct lu_env {
- /*
+ /**
* "Local" context, used to store data instead of stack.
*/
struct lu_context le_ctx;
- /*
+ /**
* "Session" context for per-request data.
*/
struct lu_context *le_ses;
int lu_env_init(struct lu_env *env, struct lu_context *ses, __u32 tags);
void lu_env_fini(struct lu_env *env);
-/*
+/** @} lu_context */
+
+/**
+ * Output site statistical counters into a buffer. Suitable for
+ * ll_rd_*()-style functions.
+ */
+int lu_site_stats_print(const struct lu_site *s, char *page, int count);
+
+/**
* Common name structure to be passed around for various name related methods.
*/
struct lu_name {
next = ldt->ldt_ops->ldto_device_free(env, scan);
type = ldt->ldt_obd_type;
+ if (type != NULL) {
type->typ_refcnt--;
class_put_type(type);
}
+ }
}
EXPORT_SYMBOL(lu_stack_fini);
enum {
- /*
+ /**
* Maximal number of tld slots.
*/
- LU_CONTEXT_KEY_NR = 16
+ LU_CONTEXT_KEY_NR = 32
};
static struct lu_context_key *lu_keys[LU_CONTEXT_KEY_NR] = { NULL, };
static spinlock_t lu_keys_guard = SPIN_LOCK_UNLOCKED;
-/*
+/**
+ * Global counter incremented whenever key is registered, unregistered,
+ * revived or quiesced. This is used to void unnecessary calls to
+ * lu_context_refill(). No locking is provided, as initialization and shutdown
+ * are supposed to be externally serialized.
+ */
+static unsigned key_set_version = 0;
+
+/**
* Register new key.
*/
int lu_context_key_register(struct lu_context_key *key)
key->lct_index = i;
atomic_set(&key->lct_used, 1);
lu_keys[i] = key;
+ lu_ref_init(&key->lct_reference);
result = 0;
+ ++key_set_version;
break;
}
}
LASSERT(atomic_read(&key->lct_used) > 1);
key->lct_fini(ctx, key, ctx->lc_value[index]);
+ lu_ref_del(&key->lct_reference, "ctx", ctx);
atomic_dec(&key->lct_used);
LASSERT(key->lct_owner != NULL);
if (!(ctx->lc_tags & LCT_NOREF)) {
}
}
-/*
+/**
* Deregister key.
*/
void lu_context_key_degister(struct lu_context_key *key)
{
LASSERT(atomic_read(&key->lct_used) >= 1);
- LASSERT(0 <= key->lct_index && key->lct_index < ARRAY_SIZE(lu_keys));
+ LINVRNT(0 <= key->lct_index && key->lct_index < ARRAY_SIZE(lu_keys));
+ ++key_set_version;
key_fini(&lu_shrink_env.le_ctx, key->lct_index);
if (atomic_read(&key->lct_used) > 1)
}
EXPORT_SYMBOL(lu_context_key_degister);
-/*
- * Return value associated with key @key in context @ctx.
+/**
+ * Register a number of keys. This has to be called after all keys have been
+ * initialized by a call to LU_CONTEXT_KEY_INIT().
+ */
+int lu_context_key_register_many(struct lu_context_key *k, ...)
+{
+ struct lu_context_key *key = k;
+ va_list args;
+ int result;
+
+ va_start(args, k);
+ do {
+ result = lu_context_key_register(key);
+ if (result)
+ break;
+ key = va_arg(args, struct lu_context_key *);
+ } while (key != NULL);
+ va_end(args);
+
+ if (result != 0) {
+ va_start(args, k);
+ while (k != key) {
+ lu_context_key_degister(k);
+ k = va_arg(args, struct lu_context_key *);
+ }
+ va_end(args);
+ }
+
+ return result;
+}
+EXPORT_SYMBOL(lu_context_key_register_many);
+
+/**
+ * De-register a number of keys. This is a dual to
+ * lu_context_key_register_many().
+ */
+void lu_context_key_degister_many(struct lu_context_key *k, ...)
+{
+ va_list args;
+
+ va_start(args, k);
+ do {
+ lu_context_key_degister(k);
+ k = va_arg(args, struct lu_context_key*);
+ } while (k != NULL);
+ va_end(args);
+}
+EXPORT_SYMBOL(lu_context_key_degister_many);
+
+/**
+ * Revive a number of keys.
+ */
+void lu_context_key_revive_many(struct lu_context_key *k, ...)
+{
+ va_list args;
+
+ va_start(args, k);
+ do {
+ lu_context_key_revive(k);
+ k = va_arg(args, struct lu_context_key*);
+ } while (k != NULL);
+ va_end(args);
+}
+EXPORT_SYMBOL(lu_context_key_revive_many);
+
+/**
+ * Quiescent a number of keys.
+ */
+void lu_context_key_quiesce_many(struct lu_context_key *k, ...)
+{
+ va_list args;
+
+ va_start(args, k);
+ do {
+ lu_context_key_quiesce(k);
+ k = va_arg(args, struct lu_context_key*);
+ } while (k != NULL);
+ va_end(args);
+}
+EXPORT_SYMBOL(lu_context_key_quiesce_many);
+
+/**
+ * Return value associated with key \a key in context \a ctx.
*/
void *lu_context_key_get(const struct lu_context *ctx,
- struct lu_context_key *key)
+ const struct lu_context_key *key)
{
- LASSERT(ctx->lc_state == LCS_ENTERED);
- LASSERT(0 <= key->lct_index && key->lct_index < ARRAY_SIZE(lu_keys));
+ LINVRNT(ctx->lc_state == LCS_ENTERED);
+ LINVRNT(0 <= key->lct_index && key->lct_index < ARRAY_SIZE(lu_keys));
return ctx->lc_value[key->lct_index];
}
EXPORT_SYMBOL(lu_context_key_get);
+/**
+ * List of remembered contexts. XXX document me.
+ */
+static CFS_LIST_HEAD(lu_context_remembered);
+
+/**
+ * Destroy \a key in all remembered contexts. This is used to destroy key
+ * values in "shared" contexts (like service threads), when a module owning
+ * the key is about to be unloaded.
+ */
+void lu_context_key_quiesce(struct lu_context_key *key)
+{
+ struct lu_context *ctx;
+
+ if (!(key->lct_tags & LCT_QUIESCENT)) {
+ key->lct_tags |= LCT_QUIESCENT;
+ /*
+ * XXX memory barrier has to go here.
+ */
+ spin_lock(&lu_keys_guard);
+ list_for_each_entry(ctx, &lu_context_remembered, lc_remember)
+ key_fini(ctx, key->lct_index);
+ spin_unlock(&lu_keys_guard);
+ ++key_set_version;
+ }
+}
+EXPORT_SYMBOL(lu_context_key_quiesce);
+
+void lu_context_key_revive(struct lu_context_key *key)
+{
+ key->lct_tags &= ~LCT_QUIESCENT;
+ ++key_set_version;
+}
+EXPORT_SYMBOL(lu_context_key_revive);
+
static void keys_fini(struct lu_context *ctx)
{
int i;
}
}
-static int keys_fill(const struct lu_context *ctx)
+static int keys_fill(struct lu_context *ctx)
{
int i;
struct lu_context_key *key;
key = lu_keys[i];
- if (ctx->lc_value[i] == NULL &&
- key != NULL && key->lct_tags & ctx->lc_tags) {
+ if (ctx->lc_value[i] == NULL && key != NULL &&
+ (key->lct_tags & ctx->lc_tags) &&
+ /*
+ * Don't create values for a LCT_QUIESCENT key, as this
+ * will pin module owning a key.
+ */
+ !(key->lct_tags & LCT_QUIESCENT)) {
void *value;
- LASSERT(key->lct_init != NULL);
- LASSERT(key->lct_index == i);
+ LINVRNT(key->lct_init != NULL);
+ LINVRNT(key->lct_index == i);
value = key->lct_init(ctx, key);
if (unlikely(IS_ERR(value)))
LASSERT(key->lct_owner != NULL);
if (!(ctx->lc_tags & LCT_NOREF))
try_module_get(key->lct_owner);
+ lu_ref_add_atomic(&key->lct_reference, "ctx", ctx);
atomic_inc(&key->lct_used);
+ /*
+ * This is the only place in the code, where an
+ * element of ctx->lc_value[] array is set to non-NULL
+ * value.
+ */
ctx->lc_value[i] = value;
+ if (key->lct_exit != NULL)
+ ctx->lc_tags |= LCT_HAS_EXIT;
}
+ ctx->lc_version = key_set_version;
}
return 0;
}
return result;
}
-/*
+/**
* Initialize context data-structure. Create values for all keys.
*/
int lu_context_init(struct lu_context *ctx, __u32 tags)
memset(ctx, 0, sizeof *ctx);
ctx->lc_state = LCS_INITIALIZED;
ctx->lc_tags = tags;
+ if (tags & LCT_REMEMBER) {
+ spin_lock(&lu_keys_guard);
+ list_add(&ctx->lc_remember, &lu_context_remembered);
+ spin_unlock(&lu_keys_guard);
+ } else
+ CFS_INIT_LIST_HEAD(&ctx->lc_remember);
return keys_init(ctx);
}
EXPORT_SYMBOL(lu_context_init);
-/*
+/**
* Finalize context data-structure. Destroy key values.
*/
void lu_context_fini(struct lu_context *ctx)
{
- LASSERT(ctx->lc_state == LCS_INITIALIZED || ctx->lc_state == LCS_LEFT);
+ LINVRNT(ctx->lc_state == LCS_INITIALIZED || ctx->lc_state == LCS_LEFT);
ctx->lc_state = LCS_FINALIZED;
keys_fini(ctx);
+ spin_lock(&lu_keys_guard);
+ list_del_init(&ctx->lc_remember);
+ spin_unlock(&lu_keys_guard);
}
EXPORT_SYMBOL(lu_context_fini);
-/*
+/**
* Called before entering context.
*/
void lu_context_enter(struct lu_context *ctx)
{
- LASSERT(ctx->lc_state == LCS_INITIALIZED || ctx->lc_state == LCS_LEFT);
+ LINVRNT(ctx->lc_state == LCS_INITIALIZED || ctx->lc_state == LCS_LEFT);
ctx->lc_state = LCS_ENTERED;
}
EXPORT_SYMBOL(lu_context_enter);
-/*
- * Called after exiting from @ctx
+/**
+ * Called after exiting from \a ctx
*/
void lu_context_exit(struct lu_context *ctx)
{
int i;
- LASSERT(ctx->lc_state == LCS_ENTERED);
+ LINVRNT(ctx->lc_state == LCS_ENTERED);
ctx->lc_state = LCS_LEFT;
- if (ctx->lc_value != NULL) {
+ if (ctx->lc_tags & LCT_HAS_EXIT && ctx->lc_value != NULL) {
for (i = 0; i < ARRAY_SIZE(lu_keys); ++i) {
if (ctx->lc_value[i] != NULL) {
struct lu_context_key *key;
}
EXPORT_SYMBOL(lu_context_exit);
-/*
+/**
* Allocate for context all missing keys that were registered after context
* creation.
*/
-int lu_context_refill(const struct lu_context *ctx)
+int lu_context_refill(struct lu_context *ctx)
{
- LASSERT(ctx->lc_value != NULL);
- return keys_fill(ctx);
+ LINVRNT(ctx->lc_value != NULL);
+ return ctx->lc_version == key_set_version ? 0 : keys_fill(ctx);
}
EXPORT_SYMBOL(lu_context_refill);
{
int result;
- LASSERT(ergo(!noref, !(tags & LCT_NOREF)));
+ LINVRNT(ergo(!noref, !(tags & LCT_NOREF)));
env->le_ses = ses;
result = lu_context_init(&env->le_ctx, tags);
}
EXPORT_SYMBOL(lu_env_fini);
+int lu_env_refill(struct lu_env *env)
+{
+ int result;
+
+ result = lu_context_refill(&env->le_ctx);
+ if (result == 0 && env->le_ses != NULL)
+ result = lu_context_refill(env->le_ses);
+ return result;
+}
+EXPORT_SYMBOL(lu_env_refill);
+
+static struct shrinker *lu_site_shrinker = NULL;
+
+#ifdef __KERNEL__
static int lu_cache_shrink(int nr, unsigned int gfp_mask)
{
struct lu_site *s;
return cached;
}
-static struct shrinker *lu_site_shrinker = NULL;
+#else /* !__KERNEL__ */
+static int lu_cache_shrink(int nr, unsigned int gfp_mask)
+{
+ return 0;
+}
+#endif /* __KERNEL__ */
-/*
+int lu_ref_global_init(void);
+void lu_ref_global_fini(void);
+
+/**
* Initialization of global lu_* data.
*/
int lu_global_init(void)
{
int result;
+ CDEBUG(D_CONSOLE, "Lustre LU module (%p).\n", &lu_keys);
+
LU_CONTEXT_KEY_INIT(&lu_global_key);
result = lu_context_key_register(&lu_global_key);
- if (result == 0) {
+ if (result != 0)
+ return result;
/*
- * At this level, we don't know what tags are needed, so
- * allocate them conservatively. This should not be too bad,
- * because this environment is global.
+ * At this level, we don't know what tags are needed, so allocate them
+ * conservatively. This should not be too bad, because this
+ * environment is global.
*/
down(&lu_sites_guard);
result = lu_env_init_noref(&lu_shrink_env, NULL, LCT_SHRINKER);
up(&lu_sites_guard);
- if (result == 0) {
+ if (result != 0)
+ return result;
+
+ result = lu_ref_global_init();
+ if (result != 0)
+ return result;
/*
- * seeks estimation: 3 seeks to read a record from oi,
- * one to read inode, one for ea. Unfortunately
- * setting this high value results in lu_object/inode
- * cache consuming all the memory.
- */
- lu_site_shrinker = set_shrinker(DEFAULT_SEEKS,
- lu_cache_shrink);
- if (result == 0)
+ * seeks estimation: 3 seeks to read a record from oi, one to read
+ * inode, one for ea. Unfortunately setting this high value results in
+ * lu_object/inode cache consuming all the memory.
+ */
+ lu_site_shrinker = set_shrinker(DEFAULT_SEEKS, lu_cache_shrink);
+ if (lu_site_shrinker == NULL)
+ return -ENOMEM;
+
result = lu_time_global_init();
- }
- }
return result;
}
-/*
+/**
* Dual to lu_global_init().
*/
void lu_global_fini(void)
down(&lu_sites_guard);
lu_env_fini(&lu_shrink_env);
up(&lu_sites_guard);
+
+ lu_ref_global_fini();
}
struct lu_buf LU_BUF_NULL = {