Files
erofs-freebsd-out-tree/zutil.c
T
2026-10-19 08:35:59 +08:00

666 lines
21 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/* FreeBSD stream context pool and compression runtime accounting. */
#include <sys/param.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/sysctl.h>
#include <vm/uma.h>
#include "internal.h"
#define EROFS_STREAM_MOUNT_HARD_BUDGET (32UL * 1024 * 1024)
#define EROFS_STREAM_GLOBAL_HARD_BUDGET (128UL * 1024 * 1024)
#define EROFS_STREAM_ALLOCATION_HARD_MAX (4UL * 1024 * 1024)
#define EROFS_STREAM_MOUNT_HARD_CONTEXTS (16U)
#define EROFS_STREAM_GLOBAL_HARD_CONTEXTS (64U)
#define EROFS_STREAM_MOUNT_CACHED_PER_CODEC (2U)
#define EROFS_STREAM_GLOBAL_CACHED_PER_CODEC (16U)
static unsigned long erofs_stream_mount_budget =
EROFS_STREAM_MOUNT_HARD_BUDGET;
static unsigned long erofs_stream_global_budget =
EROFS_STREAM_GLOBAL_HARD_BUDGET;
static int erofs_stream_mount_contexts =
EROFS_STREAM_MOUNT_HARD_CONTEXTS;
static int erofs_stream_global_contexts_limit =
EROFS_STREAM_GLOBAL_HARD_CONTEXTS;
static int erofs_stream_mount_cached =
EROFS_STREAM_MOUNT_CACHED_PER_CODEC;
static int erofs_stream_global_cached =
EROFS_STREAM_GLOBAL_CACHED_PER_CODEC;
TUNABLE_ULONG("vfs.erofs.stream_pool.mount_budget",
&erofs_stream_mount_budget);
TUNABLE_ULONG("vfs.erofs.stream_pool.global_budget",
&erofs_stream_global_budget);
TUNABLE_INT("vfs.erofs.stream_pool.mount_contexts",
&erofs_stream_mount_contexts);
TUNABLE_INT("vfs.erofs.stream_pool.global_contexts",
&erofs_stream_global_contexts_limit);
TUNABLE_INT("vfs.erofs.stream_pool.mount_cached_per_codec",
&erofs_stream_mount_cached);
TUNABLE_INT("vfs.erofs.stream_pool.global_cached_per_codec",
&erofs_stream_global_cached);
static struct mtx erofs_stream_lock;
static struct cv erofs_stream_global_cv;
static unsigned int erofs_stream_global_waiters;
static LIST_HEAD(, erofs_sb_info) erofs_stream_mounts =
LIST_HEAD_INITIALIZER(erofs_stream_mounts);
static uma_zone_t erofs_stream_zone;
static eventhandler_tag erofs_stream_lowmem_tag;
static size_t erofs_stream_global_resident;
static unsigned int erofs_stream_global_contexts;
static unsigned int erofs_stream_global_cached_by_codec[
Z_EROFS_COMPRESSION_MAX];
static unsigned long erofs_stream_cached_contexts;
static unsigned long erofs_stream_borrowed_contexts;
static unsigned long erofs_stream_idle_contexts;
static unsigned long erofs_stream_creations;
static unsigned long erofs_stream_reuses;
static unsigned long erofs_stream_fallbacks;
static unsigned long erofs_stream_exhaustions;
static unsigned long erofs_stream_waits;
static unsigned long erofs_stream_destroys;
static unsigned long erofs_stream_reclaims;
MTX_SYSINIT(erofs_stream_pool, &erofs_stream_lock, "erofs stream pool",
MTX_DEF);
SYSCTL_DECL(_vfs_erofs);
SYSCTL_NODE(_vfs_erofs, OID_AUTO, stream_pool,
CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "EROFS stream context pool");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, mount_budget, CTLFLAG_RD,
&erofs_stream_mount_budget, 0, "Effective per-mount byte budget");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, global_budget, CTLFLAG_RD,
&erofs_stream_global_budget, 0, "Effective global byte budget");
SYSCTL_INT(_vfs_erofs_stream_pool, OID_AUTO, mount_contexts, CTLFLAG_RD,
&erofs_stream_mount_contexts, 0,
"Effective per-mount, per-algorithm context limit");
SYSCTL_INT(_vfs_erofs_stream_pool, OID_AUTO, global_contexts_limit,
CTLFLAG_RD, &erofs_stream_global_contexts_limit, 0,
"Effective global context limit");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, resident_bytes, CTLFLAG_RD,
&erofs_stream_global_resident, 0, "Currently charged global bytes");
SYSCTL_UINT(_vfs_erofs_stream_pool, OID_AUTO, contexts, CTLFLAG_RD,
&erofs_stream_global_contexts, 0, "Currently allocated contexts");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, cached, CTLFLAG_RD,
&erofs_stream_cached_contexts, 0, "Currently cacheable contexts");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, borrowed, CTLFLAG_RD,
&erofs_stream_borrowed_contexts, 0, "Currently borrowed contexts");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, idle, CTLFLAG_RD,
&erofs_stream_idle_contexts, 0, "Currently idle contexts");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, creations, CTLFLAG_RD,
&erofs_stream_creations, 0, "Successful context creations");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, reuses, CTLFLAG_RD,
&erofs_stream_reuses, 0, "Idle context acquisitions");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, fallbacks, CTLFLAG_RD,
&erofs_stream_fallbacks, 0, "Bounded temporary context fallbacks");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, exhaustions, CTLFLAG_RD,
&erofs_stream_exhaustions, 0, "Unsatisfiable limit or budget rejections");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, waits, CTLFLAG_RD,
&erofs_stream_waits, 0, "Sleeps waiting for a context or budget release");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, destroys, CTLFLAG_RD,
&erofs_stream_destroys, 0, "Destroyed contexts");
SYSCTL_ULONG(_vfs_erofs_stream_pool, OID_AUTO, reclaims, CTLFLAG_RD,
&erofs_stream_reclaims, 0, "Low-memory idle context reclaims");
struct erofs_stream_alloc_header {
struct erofs_stream_ctx *ctx;
size_t bytes;
};
static bool
erofs_stream_mount_budget_available_locked(const struct erofs_sb_info *sbi,
size_t bytes)
{
mtx_assert(&erofs_stream_lock, MA_OWNED);
return (bytes <= erofs_stream_mount_budget &&
sbi->stream_pool.resident_bytes <= erofs_stream_mount_budget -
bytes);
}
static bool
erofs_stream_global_budget_available_locked(size_t bytes)
{
mtx_assert(&erofs_stream_lock, MA_OWNED);
return (bytes <= erofs_stream_global_budget &&
erofs_stream_global_resident <= erofs_stream_global_budget - bytes);
}
static bool
erofs_stream_reserve_locked(struct erofs_sb_info *sbi, size_t bytes)
{
struct erofs_stream_pool *pool;
mtx_assert(&erofs_stream_lock, MA_OWNED);
pool = &sbi->stream_pool;
if (!erofs_stream_mount_budget_available_locked(sbi, bytes) ||
!erofs_stream_global_budget_available_locked(bytes))
return (false);
pool->resident_bytes += bytes;
erofs_stream_global_resident += bytes;
return (true);
}
/*
* Wake threads sleeping in z_erofs_stream_ctx_get() after a context or byte
* budget was released. Mount-level sleepers and the unmount drain share the
* per-mount cv; sleepers blocked on a global limit use the global cv.
*/
static void
erofs_stream_wake_locked(struct erofs_sb_info *sbi)
{
mtx_assert(&erofs_stream_lock, MA_OWNED);
if (sbi->stream_pool.waiters != 0 || sbi->stream_pool.closing)
cv_broadcast(&sbi->stream_pool.cv);
if (erofs_stream_global_waiters != 0)
cv_broadcast(&erofs_stream_global_cv);
}
static void
erofs_stream_release_locked(struct erofs_sb_info *sbi, size_t bytes)
{
mtx_assert(&erofs_stream_lock, MA_OWNED);
KASSERT(bytes <= sbi->stream_pool.resident_bytes &&
bytes <= erofs_stream_global_resident,
("erofs stream pool byte accounting underflow"));
sbi->stream_pool.resident_bytes -= bytes;
erofs_stream_global_resident -= bytes;
}
static struct erofs_stream_ctx *
erofs_stream_take_idle_locked(struct erofs_sb_info *sbi)
{
struct erofs_stream_pool_codec *codec;
struct erofs_stream_ctx *ctx;
unsigned int algorithm;
mtx_assert(&erofs_stream_lock, MA_OWNED);
for (algorithm = 0; algorithm < Z_EROFS_COMPRESSION_MAX; ++algorithm) {
codec = &sbi->stream_pool.codec[algorithm];
ctx = STAILQ_FIRST(&codec->idle);
if (ctx == NULL)
continue;
STAILQ_REMOVE_HEAD(&codec->idle, link);
KASSERT(codec->idle_count != 0 &&
erofs_stream_idle_contexts != 0,
("erofs stream idle accounting underflow"));
--codec->idle_count;
--erofs_stream_idle_contexts;
++codec->borrowed;
++erofs_stream_borrowed_contexts;
return (ctx);
}
return (NULL);
}
/*
* Detach one idle context so that the caller can destroy it after dropping
* the lock. Idle contexts of this mount are preferred; other mounts are only
* considered when the limit being hit is a global one.
*/
static struct erofs_stream_ctx *
erofs_stream_take_reclaimable_locked(struct erofs_sb_info *sbi, bool global)
{
struct erofs_stream_ctx *ctx;
struct erofs_sb_info *other;
ctx = erofs_stream_take_idle_locked(sbi);
if (ctx != NULL || !global)
return (ctx);
LIST_FOREACH(other, &erofs_stream_mounts, stream_pool_link) {
ctx = erofs_stream_take_idle_locked(other);
if (ctx != NULL)
break;
}
return (ctx);
}
static void erofs_stream_ctx_destroy(struct erofs_stream_ctx *ctx);
/*
* Charging never sleeps for budget: the caller already owns a context, and
* waiting here would hold part of the budget while waiting for the rest. Idle
* contexts are the only budget that can be returned immediately, so reclaim
* them first and fail with ENOMEM only when the budget is truly exhausted.
*/
int
z_erofs_stream_ctx_charge(struct erofs_stream_ctx *ctx, size_t bytes)
{
struct erofs_stream_ctx *idle;
bool reserved;
if (bytes == 0 || bytes > EROFS_STREAM_ALLOCATION_HARD_MAX) {
ctx->allocation_failed = true;
mtx_lock(&erofs_stream_lock);
++erofs_stream_exhaustions;
mtx_unlock(&erofs_stream_lock);
return (ENOMEM);
}
mtx_lock(&erofs_stream_lock);
for (;;) {
if (ctx->charged_bytes > SIZE_MAX - bytes ||
bytes > erofs_stream_mount_budget ||
bytes > erofs_stream_global_budget) {
reserved = false;
break;
}
reserved = erofs_stream_reserve_locked(ctx->sbi, bytes);
if (reserved)
break;
idle = erofs_stream_take_reclaimable_locked(ctx->sbi,
erofs_stream_mount_budget_available_locked(ctx->sbi,
bytes));
if (idle == NULL)
break;
mtx_unlock(&erofs_stream_lock);
erofs_stream_ctx_destroy(idle);
mtx_lock(&erofs_stream_lock);
}
if (reserved)
ctx->charged_bytes += bytes;
else
++erofs_stream_exhaustions;
mtx_unlock(&erofs_stream_lock);
if (!reserved) {
ctx->allocation_failed = true;
return (ENOMEM);
}
return (0);
}
void
z_erofs_stream_ctx_uncharge(struct erofs_stream_ctx *ctx, size_t bytes)
{
mtx_lock(&erofs_stream_lock);
KASSERT(bytes <= ctx->charged_bytes,
("erofs stream context byte accounting underflow"));
ctx->charged_bytes -= bytes;
erofs_stream_release_locked(ctx->sbi, bytes);
erofs_stream_wake_locked(ctx->sbi);
mtx_unlock(&erofs_stream_lock);
}
void *
z_erofs_stream_ctx_alloc(struct erofs_stream_ctx *ctx, size_t bytes)
{
struct erofs_stream_alloc_header *header;
size_t total;
if (__builtin_add_overflow(bytes, sizeof(*header), &total) ||
z_erofs_stream_ctx_charge(ctx, total) != 0)
return (NULL);
header = malloc(total, M_EROFS, M_WAITOK | M_ZERO);
header->ctx = ctx;
header->bytes = total;
return (header + 1);
}
void
z_erofs_stream_ctx_free(struct erofs_stream_ctx *ctx, void *address)
{
struct erofs_stream_alloc_header *header;
size_t bytes;
if (address == NULL)
return;
header = (struct erofs_stream_alloc_header *)address - 1;
KASSERT(header->ctx == ctx, ("erofs stream allocation owner mismatch"));
bytes = header->bytes;
free(header, M_EROFS);
z_erofs_stream_ctx_uncharge(ctx, bytes);
}
static void
erofs_stream_ctx_destroy(struct erofs_stream_ctx *ctx)
{
struct erofs_stream_pool_codec *codec;
struct erofs_sb_info *sbi;
bool cached;
uint8_t algorithm;
sbi = ctx->sbi;
algorithm = ctx->algorithm;
cached = ctx->cached;
ctx->fini(ctx);
KASSERT(ctx->charged_bytes == EROFS_STREAM_CTX_WRAPPER_SIZE,
("erofs stream backend allocation leaked"));
uma_zfree(erofs_stream_zone, ctx);
mtx_lock(&erofs_stream_lock);
codec = &sbi->stream_pool.codec[algorithm];
KASSERT(codec->contexts != 0 && codec->borrowed != 0 &&
erofs_stream_global_contexts != 0 &&
erofs_stream_borrowed_contexts != 0,
("erofs stream context accounting underflow"));
--codec->contexts;
--codec->borrowed;
--erofs_stream_global_contexts;
--erofs_stream_borrowed_contexts;
if (cached) {
KASSERT(codec->cached != 0 &&
erofs_stream_global_cached_by_codec[algorithm] != 0 &&
erofs_stream_cached_contexts != 0,
("erofs stream cache accounting underflow"));
--codec->cached;
--erofs_stream_global_cached_by_codec[algorithm];
--erofs_stream_cached_contexts;
}
erofs_stream_release_locked(sbi, EROFS_STREAM_CTX_WRAPPER_SIZE);
++erofs_stream_destroys;
erofs_stream_wake_locked(sbi);
mtx_unlock(&erofs_stream_lock);
}
/*
* Sleep until a context or budget is released. Limits that only this mount
* can release are waited for on the mount cv; anything involving a global
* limit is waited for on the global cv, which every release also signals.
*/
static void
erofs_stream_wait_locked(struct erofs_sb_info *sbi, bool global)
{
struct erofs_stream_pool *pool;
mtx_assert(&erofs_stream_lock, MA_OWNED);
pool = &sbi->stream_pool;
++pool->waiters;
++erofs_stream_waits;
if (global) {
++erofs_stream_global_waiters;
cv_wait(&erofs_stream_global_cv, &erofs_stream_lock);
--erofs_stream_global_waiters;
} else {
cv_wait(&pool->cv, &erofs_stream_lock);
}
KASSERT(pool->waiters != 0, ("erofs stream waiter underflow"));
--pool->waiters;
if (pool->closing)
cv_broadcast(&pool->cv);
}
int
z_erofs_stream_ctx_get(struct erofs_sb_info *sbi, uint8_t algorithm,
size_t context_size, erofs_stream_ctx_init_t *init,
erofs_stream_ctx_fini_t *fini, struct erofs_stream_ctx **ctxp)
{
struct erofs_stream_pool_codec *codec;
struct erofs_stream_ctx *ctx;
bool cached, global_full, mount_full, reserved __diagused;
int error;
*ctxp = NULL;
if (algorithm == Z_EROFS_COMPRESSION_LZ4 ||
algorithm >= Z_EROFS_COMPRESSION_MAX ||
context_size < sizeof(*ctx) ||
context_size > EROFS_STREAM_CTX_WRAPPER_SIZE)
return (EINVAL);
mtx_lock(&erofs_stream_lock);
for (;;) {
if (!sbi->stream_pool_initialized || sbi->stream_pool.closing) {
mtx_unlock(&erofs_stream_lock);
return (ENXIO);
}
codec = &sbi->stream_pool.codec[algorithm];
ctx = STAILQ_FIRST(&codec->idle);
if (ctx != NULL) {
STAILQ_REMOVE_HEAD(&codec->idle, link);
KASSERT(codec->idle_count != 0 &&
erofs_stream_idle_contexts != 0,
("erofs stream idle accounting underflow"));
--codec->idle_count;
--erofs_stream_idle_contexts;
++codec->borrowed;
++erofs_stream_borrowed_contexts;
++erofs_stream_reuses;
ctx->allocation_failed = false;
mtx_unlock(&erofs_stream_lock);
*ctxp = ctx;
return (0);
}
/*
* Limits that no release can ever satisfy are configuration
* errors; waiting for them would never end.
*/
if (erofs_stream_mount_contexts == 0 ||
erofs_stream_global_contexts_limit == 0 ||
EROFS_STREAM_CTX_WRAPPER_SIZE > erofs_stream_mount_budget ||
EROFS_STREAM_CTX_WRAPPER_SIZE >
erofs_stream_global_budget) {
++erofs_stream_exhaustions;
mtx_unlock(&erofs_stream_lock);
return (ENOMEM);
}
mount_full = codec->contexts >=
(unsigned int)erofs_stream_mount_contexts;
global_full = erofs_stream_global_contexts >=
(unsigned int)erofs_stream_global_contexts_limit ||
!erofs_stream_global_budget_available_locked(
EROFS_STREAM_CTX_WRAPPER_SIZE);
if (!mount_full && !global_full &&
erofs_stream_mount_budget_available_locked(sbi,
EROFS_STREAM_CTX_WRAPPER_SIZE))
break;
/*
* Contexts of this codec are all borrowed when mount_full, so
* only a release can help. Otherwise idle contexts of other
* codecs (or mounts, for a global limit) hold the resource and
* are never released unless reclaimed here.
*/
if (!mount_full) {
ctx = erofs_stream_take_reclaimable_locked(sbi,
global_full);
if (ctx != NULL) {
mtx_unlock(&erofs_stream_lock);
erofs_stream_ctx_destroy(ctx);
mtx_lock(&erofs_stream_lock);
continue;
}
}
erofs_stream_wait_locked(sbi, !mount_full && global_full);
}
reserved = erofs_stream_reserve_locked(sbi,
EROFS_STREAM_CTX_WRAPPER_SIZE);
KASSERT(reserved, ("erofs stream wrapper reservation failed"));
cached = codec->cached < (unsigned int)erofs_stream_mount_cached &&
erofs_stream_global_cached_by_codec[algorithm] <
(unsigned int)erofs_stream_global_cached;
++codec->contexts;
++codec->borrowed;
++erofs_stream_global_contexts;
++erofs_stream_borrowed_contexts;
if (cached) {
++codec->cached;
++erofs_stream_global_cached_by_codec[algorithm];
++erofs_stream_cached_contexts;
} else {
++erofs_stream_fallbacks;
}
mtx_unlock(&erofs_stream_lock);
ctx = uma_zalloc(erofs_stream_zone, M_WAITOK | M_ZERO);
ctx->sbi = sbi;
ctx->fini = fini;
ctx->charged_bytes = EROFS_STREAM_CTX_WRAPPER_SIZE;
ctx->algorithm = algorithm;
ctx->cached = cached;
error = init(ctx);
if (error != 0) {
erofs_stream_ctx_destroy(ctx);
return (error > 0 ? error : EIO);
}
mtx_lock(&erofs_stream_lock);
++erofs_stream_creations;
mtx_unlock(&erofs_stream_lock);
*ctxp = ctx;
return (0);
}
void
z_erofs_stream_ctx_put(struct erofs_stream_ctx *ctx, bool reusable)
{
struct erofs_stream_pool_codec *codec;
mtx_lock(&erofs_stream_lock);
codec = &ctx->sbi->stream_pool.codec[ctx->algorithm];
if (reusable && ctx->cached && !ctx->sbi->stream_pool.closing) {
KASSERT(codec->borrowed != 0 &&
erofs_stream_borrowed_contexts != 0,
("erofs stream borrowed accounting underflow"));
--codec->borrowed;
--erofs_stream_borrowed_contexts;
STAILQ_INSERT_HEAD(&codec->idle, ctx, link);
++codec->idle_count;
++erofs_stream_idle_contexts;
erofs_stream_wake_locked(ctx->sbi);
mtx_unlock(&erofs_stream_lock);
return;
}
mtx_unlock(&erofs_stream_lock);
erofs_stream_ctx_destroy(ctx);
}
static bool
erofs_stream_pool_empty_locked(const struct erofs_stream_pool *pool)
{
unsigned int algorithm;
mtx_assert(&erofs_stream_lock, MA_OWNED);
for (algorithm = 0; algorithm < Z_EROFS_COMPRESSION_MAX; ++algorithm) {
if (pool->codec[algorithm].contexts != 0)
return (false);
}
return (true);
}
void
z_erofs_stream_pool_init(struct erofs_sb_info *sbi)
{
unsigned int algorithm;
bzero(&sbi->stream_pool, sizeof(sbi->stream_pool));
for (algorithm = 0; algorithm < Z_EROFS_COMPRESSION_MAX; ++algorithm)
STAILQ_INIT(&sbi->stream_pool.codec[algorithm].idle);
cv_init(&sbi->stream_pool.cv, "erofs stream drain");
mtx_lock(&erofs_stream_lock);
sbi->stream_pool_initialized = true;
LIST_INSERT_HEAD(&erofs_stream_mounts, sbi, stream_pool_link);
mtx_unlock(&erofs_stream_lock);
}
void
z_erofs_stream_pool_fini(struct erofs_sb_info *sbi)
{
struct erofs_stream_ctx *ctx;
if (!sbi->stream_pool_initialized)
return;
mtx_lock(&erofs_stream_lock);
sbi->stream_pool.closing = true;
erofs_stream_wake_locked(sbi);
for (;;) {
ctx = erofs_stream_take_idle_locked(sbi);
if (ctx == NULL)
break;
mtx_unlock(&erofs_stream_lock);
erofs_stream_ctx_destroy(ctx);
mtx_lock(&erofs_stream_lock);
}
while (!erofs_stream_pool_empty_locked(&sbi->stream_pool) ||
sbi->stream_pool.waiters != 0)
cv_wait(&sbi->stream_pool.cv, &erofs_stream_lock);
KASSERT(sbi->stream_pool.resident_bytes == 0,
("erofs stream mount bytes remain at unmount"));
LIST_REMOVE(sbi, stream_pool_link);
sbi->stream_pool_initialized = false;
mtx_unlock(&erofs_stream_lock);
cv_destroy(&sbi->stream_pool.cv);
}
static void
erofs_stream_pool_lowmem(void *arg __unused, int howto __unused)
{
struct erofs_stream_ctx *ctx;
struct erofs_sb_info *sbi;
for (;;) {
ctx = NULL;
mtx_lock(&erofs_stream_lock);
LIST_FOREACH(sbi, &erofs_stream_mounts, stream_pool_link) {
ctx = erofs_stream_take_idle_locked(sbi);
if (ctx != NULL) {
++erofs_stream_reclaims;
break;
}
}
mtx_unlock(&erofs_stream_lock);
if (ctx == NULL)
break;
erofs_stream_ctx_destroy(ctx);
}
}
static void
erofs_stream_pool_global_init(void *arg __unused)
{
erofs_stream_mount_budget = MIN(erofs_stream_mount_budget,
EROFS_STREAM_MOUNT_HARD_BUDGET);
erofs_stream_global_budget = MIN(erofs_stream_global_budget,
EROFS_STREAM_GLOBAL_HARD_BUDGET);
erofs_stream_mount_contexts = MAX(0, MIN(erofs_stream_mount_contexts,
(int)EROFS_STREAM_MOUNT_HARD_CONTEXTS));
erofs_stream_global_contexts_limit = MAX(0, MIN(
erofs_stream_global_contexts_limit,
(int)EROFS_STREAM_GLOBAL_HARD_CONTEXTS));
erofs_stream_mount_cached = MAX(0, MIN(erofs_stream_mount_cached,
(int)EROFS_STREAM_MOUNT_CACHED_PER_CODEC));
erofs_stream_global_cached = MAX(0, MIN(erofs_stream_global_cached,
(int)EROFS_STREAM_GLOBAL_CACHED_PER_CODEC));
cv_init(&erofs_stream_global_cv, "erofs stream wait");
/*
* The context count is bounded by erofs_stream_global_contexts_limit;
* a UMA item limit would only add a second, unrelated place to sleep.
*/
erofs_stream_zone = uma_zcreate("erofs stream ctx",
EROFS_STREAM_CTX_WRAPPER_SIZE, NULL, NULL, NULL, NULL,
UMA_ALIGN_PTR, 0);
KASSERT(erofs_stream_zone != NULL,
("cannot create erofs stream UMA zone"));
uma_zone_set_maxcache(erofs_stream_zone,
EROFS_STREAM_GLOBAL_CACHED_PER_CODEC * 3);
erofs_stream_lowmem_tag = EVENTHANDLER_REGISTER(vm_lowmem,
erofs_stream_pool_lowmem, NULL, LOWMEM_PRI_DEFAULT);
}
static void
erofs_stream_pool_global_fini(void *arg __unused)
{
if (erofs_stream_lowmem_tag != NULL)
EVENTHANDLER_DEREGISTER(vm_lowmem, erofs_stream_lowmem_tag);
KASSERT(LIST_EMPTY(&erofs_stream_mounts),
("erofs stream mounts remain at unload"));
KASSERT(erofs_stream_global_contexts == 0 &&
erofs_stream_global_waiters == 0 &&
erofs_stream_global_resident == 0 &&
erofs_stream_borrowed_contexts == 0 &&
erofs_stream_idle_contexts == 0 &&
erofs_stream_cached_contexts == 0,
("erofs stream resources remain at unload"));
uma_zdestroy(erofs_stream_zone);
cv_destroy(&erofs_stream_global_cv);
}
SYSINIT(erofs_stream_pool_global, SI_SUB_VFS, SI_ORDER_FIRST,
erofs_stream_pool_global_init, NULL);
SYSUNINIT(erofs_stream_pool_global, SI_SUB_VFS, SI_ORDER_FIRST,
erofs_stream_pool_global_fini, NULL);