#include <scx/common.bpf.h>
#include <bpf_arena_common.bpf.h>
#include <lib/topology.h>
#include <lib/cgroup.h>
#include <lib/atq.h>
extern int scx_cgroup_bw_enqueue_cb(u64 taskc);
enum scx_cgroup_consts {
SCX_CACHELINE_SIZE = 64,
CBW_CLOCK_BOOTTIME = 7,
CBW_NPERIOD = (100ULL * 1000ULL * 1000ULL),
CBW_NR_CGRP_MAX = 2048,
CBW_NR_CGRP_LLC_MAX = (CBW_NR_CGRP_MAX * 32),
CBW_CGRP_TREE_HEIGHT_MAX = 16,
CBW_RUNTUME_INF_RAW = ((u64)~0ULL),
CBW_RUNTUME_INF = ((s64)~((u64)1 << 63)),
CBW_BUDGET_XFER_LB = (100ULL * 1000ULL * 1000ULL),
CBW_BUDGET_XFER_MIN = (20ULL * 1000ULL * 1000ULL),
CBW_BUDGET_XFER_MAX_SHIFT = 2,
CBW_REENQ_MAX_BATCH = 2,
};
struct scx_cgroup_ctx {
u64 id;
u64 quota;
u64 period;
u64 burst;
u64 nquota;
u64 nquota_ub;
u64 period_start_clk;
s64 burst_remaining;
s64 budget_remaining;
s64 runtime_total_sloppy;
s64 runtime_total_last;
u64 budget_p2c;
u64 budget_c2l;
int nr_taskable_descendents;
bool has_llcx;
bool is_throttled;
};
struct scx_cgroup_llc_ctx {
u64 id;
s64 budget_remaining;
s64 runtime_total;
scx_atq_t *btq;
} __attribute__((aligned(SCX_CACHELINE_SIZE)));
static struct scx_cgroup_bw_config cbw_config;
struct {
__uint(type, BPF_MAP_TYPE_CGRP_STORAGE);
__uint(map_flags, BPF_F_NO_PREALLOC);
__type(key, int);
__type(value, struct scx_cgroup_ctx);
} cbw_cgrp_map SEC(".maps");
struct cgroup_llc_id {
u64 cgrp_id;
int llc_id;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, struct cgroup_llc_id);
__type(value, struct scx_cgroup_llc_ctx);
__uint(map_flags, BPF_F_NO_PREALLOC);
__uint(max_entries, CBW_NR_CGRP_LLC_MAX);
} cbw_cgrp_llc_map SEC(".maps");
struct tree_levels {
s64 levels[CBW_CGRP_TREE_HEIGHT_MAX];
};
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, struct tree_levels);
__uint(max_entries, 1);
} tree_levels_map SEC(".maps");
static u64 cbw_nr_taskable_cgroups;
static u64 cbw_taskable_cgroup_ids[CBW_NR_CGRP_MAX];
static u64 cbw_nr_throttled_cgroups;
static u64 cbw_throttled_cgroup_ids[CBW_NR_CGRP_MAX];
struct replenish_timer {
struct bpf_timer timer;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, struct replenish_timer);
} replenish_timer SEC(".maps") __weak;
static u64 cbw_last_replenish_at;
static
int replenish_timerfn(void *map, int *key, struct bpf_timer *timer);
enum replenish_stats {
CBW_REPLENISH_STAT_IDLE = 0,
CBW_REPLENISH_STAT_TOP_HALF_RUNNING = 1,
CBW_REPLENISH_STAT_BOTTOM_HALF_READY = 2,
CBW_REPLENISH_STAT_BOTTOM_HALF_RUNNING = 3,
};
struct replenish_stat {
int s;
} __attribute__((aligned(SCX_CACHELINE_SIZE)));
static struct replenish_stat cbw_replenish_stat;
#define cbw_err(fmt, ...) do { \
bpf_printk("[%s:%d] ERROR: " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while(0)
#define cbw_warn(fmt, ...) do { \
bpf_printk("[%s:%d] WARNING: " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while(0)
#define cbw_info(fmt, ...) do { \
bpf_printk("[%s:%d] INFO: " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while(0)
#define cbw_dbg(fmt, ...) do { \
if (cbw_config.verbose > 0) \
bpf_printk("[%s:%d] " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while(0)
#define cbw_dbg_cgrp(fmt, ...) do { \
if (cbw_config.verbose > 0) \
bpf_printk("[%s:%d/cgid%llu] " fmt, __func__, __LINE__, \
cgrp->kn->id, ##__VA_ARGS__); \
} while(0)
#define dbg_cgx(cgx, str, ...) do { \
cbw_dbg(str "cgid%llu -- cgx:budget_remaining: %lld -- " \
"cgx:runtime_total_last: %lld -- " \
"cgx:runtime_total_sloppy: %lld -- " \
"cgx:nquota: %lld -- " \
"cgx:nquota_ub: %lld -- " \
"cgx:is_throttled: %d -- cgx:nr_taskable_descendents: %d -- " \
"cgx:budget_p2c: %llu -- cgx:budget_c2l: %llu", \
##__VA_ARGS__, \
cgx->id, cgx->budget_remaining, \
cgx->runtime_total_last, cgx->runtime_total_sloppy, \
cgx->nquota, cgx->nquota_ub, cgx->is_throttled, \
cgx->nr_taskable_descendents, cgx->budget_p2c, cgx->budget_c2l);\
} while (0);
#define dbg_llcx(llcx, str, ...) do { \
cbw_dbg(str "cgid%llu -- llcx:budget_remaining: %lld -- " \
"llcx:runtime_total: %lld", \
##__VA_ARGS__, \
llcx->id, \
llcx->budget_remaining, llcx->runtime_total); \
} while (0);
#define info_llcx(llcx, str, ...) do { \
cbw_dbg(str "cgid%llu -- llcx:budget_remaining: %lld -- " \
"llcx:runtime_total: %lld", \
##__VA_ARGS__, \
llcx->id, \
llcx->budget_remaining, llcx->runtime_total); \
} while (0);
#define info_cgx(cgx, str, ...) do { \
cbw_info(str "cgid%llu -- cgx:budget_remaining: %lld -- " \
"cgx:runtime_total_last: %lld -- " \
"cgx:runtime_total_sloppy: %lld -- " \
"cgx:nquota: %lld -- " \
"cgx:nquota_ub: %lld -- " \
"cgx:is_throttled: %d -- cgx:nr_taskable_descendents: %d -- " \
"cgx:budget_p2c: %llu -- cgx:budget_c2l: %llu", \
##__VA_ARGS__, \
cgx->id, cgx->budget_remaining, \
cgx->runtime_total_last, cgx->runtime_total_sloppy, \
cgx->nquota, cgx->nquota_ub, cgx->is_throttled, \
cgx->nr_taskable_descendents, cgx->budget_p2c, cgx->budget_c2l);\
} while (0);
#ifndef min
#define min(X, Y) (((X) < (Y)) ? (X) : (Y))
#endif
#ifndef max
#define max(X, Y) (((X) < (Y)) ? (Y) : (X))
#endif
#ifndef clamp
#define clamp(val, lo, hi) min(max(val, lo), hi)
#endif
static
bool is_kernel_compatible(void)
{
return bpf_core_field_exists(struct scx_cgroup_init_args, bw_period_us);
}
__hidden
int scx_cgroup_bw_lib_init(struct scx_cgroup_bw_config *config)
{
struct bpf_timer *timer;
int ret;
u32 key = 0;
if (!is_kernel_compatible()) {
cbw_err("The kernel does not support the cpu.max for scx.");
return -ENOTSUP;
}
if (!config)
return -EINVAL;
cbw_config = *config;
timer = bpf_map_lookup_elem(&replenish_timer, &key);
if (!timer) {
cbw_err("Failed to lookup replenish timer");
return -ESRCH;
}
cbw_last_replenish_at = scx_bpf_now();
bpf_timer_init(timer, &replenish_timer, CBW_CLOCK_BOOTTIME);
bpf_timer_set_callback(timer, replenish_timerfn);
if ((ret = bpf_timer_start(timer, CBW_NPERIOD, 0))) {
cbw_err("Failed to start replenish timer");
return ret;
}
return 0;
}
static
bool cgroup_is_threaded(struct cgroup *cgrp)
{
return cgrp->dom_cgrp != cgrp;
}
static
u64 cgroup_get_id(struct cgroup *cgrp)
{
return cgrp->kn->id;
}
static
struct scx_cgroup_ctx *cbw_get_cgroup_ctx(struct cgroup *cgrp)
{
return bpf_cgrp_storage_get(&cbw_cgrp_map, cgrp, 0, 0);
}
long cbw_del_cgroup_ctx(struct cgroup *cgrp)
{
return bpf_cgrp_storage_delete(&cbw_cgrp_map, cgrp);
}
static
struct scx_cgroup_llc_ctx *cbw_alloc_llc_ctx(struct cgroup *cgrp,
struct scx_cgroup_ctx *cgx,
int llc_id)
{
static const struct scx_cgroup_llc_ctx llcx0;
struct scx_cgroup_llc_ctx *llcx;
struct cgroup_llc_id key = {
.cgrp_id = cgroup_get_id(cgrp),
.llc_id = llc_id,
};
if (bpf_map_update_elem(&cbw_cgrp_llc_map, &key, &llcx0, BPF_NOEXIST))
return NULL;
llcx = bpf_map_lookup_elem(&cbw_cgrp_llc_map, &key);
if (!llcx)
return NULL;
llcx->id = cgroup_get_id(cgrp);
llcx->btq = (scx_atq_t *)scx_atq_create(false);
if (!llcx->btq) {
cbw_err("Fail to allocate a BTQ");
bpf_map_delete_elem(&cbw_cgrp_llc_map, &key);
return NULL;
}
if (cgx->nquota_ub == CBW_RUNTUME_INF)
llcx->budget_remaining = CBW_RUNTUME_INF;
return llcx;
}
static
struct scx_cgroup_llc_ctx *cbw_get_llc_ctx_with_id(u64 cgrp_id, int llc_id)
{
struct cgroup_llc_id key = {
.cgrp_id = cgrp_id,
.llc_id = llc_id,
};
return bpf_map_lookup_elem(&cbw_cgrp_llc_map, &key);
}
static
struct scx_cgroup_llc_ctx *cbw_get_llc_ctx(struct cgroup *cgrp, int llc_id)
{
return cbw_get_llc_ctx_with_id(cgroup_get_id(cgrp), llc_id);
}
static
long cbw_del_llc_ctx(struct cgroup *cgrp, int llc_id)
{
struct cgroup_llc_id key = {
.cgrp_id = cgroup_get_id(cgrp),
.llc_id = llc_id,
};
return bpf_map_delete_elem(&cbw_cgrp_llc_map, &key);
}
static
int cbw_init_llc_ctx(struct cgroup *cgrp, struct scx_cgroup_ctx *cgx)
{
int i;
if (!cgx || !cgrp)
return -EINVAL;
bpf_for(i, 0, TOPO_NR(LLC)) {
struct scx_cgroup_llc_ctx *llcx;
llcx = cbw_alloc_llc_ctx(cgrp, cgx, i);
if (!llcx)
return -ENOMEM;
}
cgx->has_llcx = true;
return 0;
}
static
void cbw_free_llc_ctx(struct cgroup *cgrp, struct scx_cgroup_ctx *cgx)
{
struct scx_cgroup_llc_ctx *llcx;
scx_atq_t *btq;
int i;
if (!cgrp)
return;
if (cgx) {
if (!cgx->has_llcx)
return;
cgx->has_llcx = false;
}
bpf_for(i, 0, TOPO_NR(LLC)) {
llcx = cbw_get_llc_ctx(cgrp, i);
if (!llcx || !(btq = llcx->btq))
break;
if (scx_atq_nr_queued(btq)) {
cbw_err("Throttled tasks should not be in an existing cgroup: [%llu/%d]",
cgroup_get_id(cgrp), i);
}
if (cbw_del_llc_ctx(cgrp, i)) {
cbw_err("Failed to delete an LLC context: [%llu/%d]",
cgroup_get_id(cgrp), i);
continue;
}
scx_atq_destroy(btq);
}
}
static
void cbw_set_bandwidth(struct cgroup *cgrp, struct scx_cgroup_ctx *cgx,
u64 period_us, u64 quota_us, u64 burst_us)
{
cgx->quota = quota_us * 1000;
cgx->period = period_us * 1000;
cgx->period_start_clk = scx_bpf_now();
if (quota_us == CBW_RUNTUME_INF_RAW) {
cgx->nquota = CBW_RUNTUME_INF;
cgx->burst = 0;
} else {
cgx->nquota = div_round_up(quota_us * CBW_NPERIOD, period_us);
cgx->burst = burst_us * 1000;
}
cgx->burst_remaining = cgx->burst;
}
static
s64 cbw_calc_budget_tx(struct scx_cgroup_ctx *cgx, s64 base_unit, int nr_branch)
{
s64 tgt_unit, budget_tx;
if (nr_branch <= 0)
nr_branch = 1;
if (nr_branch > 1)
nr_branch <<= CBW_BUDGET_XFER_MAX_SHIFT;
if (base_unit == 0)
base_unit = cgx->nquota_ub;
tgt_unit = div_round_up((u64)base_unit, (u64)nr_branch);
if (cgx->nquota_ub <= CBW_BUDGET_XFER_LB)
budget_tx = clamp(tgt_unit, CBW_BUDGET_XFER_MIN, cgx->nquota_ub);
else
budget_tx = clamp(tgt_unit, CBW_BUDGET_XFER_LB, cgx->nquota_ub);
cbw_dbg("cgid%llu -- base_unit: %lld -- nr_branch: %d -- "
"tgt_unit: %lld -- budget_tx: %lld -- nquota_ub: %lld",
cgx->id, base_unit, nr_branch, tgt_unit, budget_tx, cgx->nquota_ub);
return budget_tx;
}
static
void cbw_update_budget_tx(struct scx_cgroup_ctx *subroot_cgx,
struct scx_cgroup_ctx *cgx)
{
int nr_branch_cgs;
s64 base;
base = (subroot_cgx == cgx) ? subroot_cgx->nquota_ub :
min(subroot_cgx->budget_p2c, cgx->nquota_ub);
if (base != CBW_RUNTUME_INF) {
nr_branch_cgs = ((subroot_cgx == cgx) ? cgx->nr_taskable_descendents : 0) +
(cgx->has_llcx ? 1 : 0);
cgx->budget_p2c = cbw_calc_budget_tx(cgx, base, nr_branch_cgs);
} else
cgx->budget_p2c = CBW_RUNTUME_INF;
base = cgx->budget_p2c;
if (base != CBW_RUNTUME_INF)
cgx->budget_c2l = cbw_calc_budget_tx(cgx, base, TOPO_NR(LLC));
else
cgx->budget_c2l = CBW_RUNTUME_INF;
}
__noinline
int cbw_update_nquota_ub(struct cgroup *cgrp __arg_trusted, struct scx_cgroup_ctx *cgx)
{
struct scx_cgroup_ctx *parentx, *subroot_cgx;
struct cgroup *parent, *subroot_cgrp;
struct scx_cgroup_llc_ctx *llcx;
int i;
if (!cgx || !cgrp)
return -EINVAL;
cgx->nquota_ub = cgx->nquota;
if ((cgrp->level > 1) &&
(parent = bpf_cgroup_ancestor(cgrp, cgrp->level - 1))) {
parentx = cbw_get_cgroup_ctx(parent);
if (!parentx) {
cbw_err("Fail to lookup a cgroup context: %llu",
cgroup_get_id(parent));
bpf_cgroup_release(parent);
return -ESRCH;
}
cgx->nquota_ub = min(cgx->nquota_ub, parentx->nquota);
bpf_cgroup_release(parent);
}
if (cgrp->level > 1) {
subroot_cgrp = bpf_cgroup_ancestor(cgrp, 1);
if (!subroot_cgrp) {
cbw_err("Failed to lookup a subroot cgroup: %llu",
cgroup_get_id(cgrp));
return -ESRCH;
}
subroot_cgx = cbw_get_cgroup_ctx(subroot_cgrp);
if (!subroot_cgx) {
cbw_err("Failed to lookup a subroot context: %llu",
cgroup_get_id(subroot_cgrp));
bpf_cgroup_release(subroot_cgrp);
return -ESRCH;
}
bpf_cgroup_release(subroot_cgrp);
} else
subroot_cgx = cgx;
cbw_update_budget_tx(subroot_cgx, cgx);
if (!cgx->has_llcx)
goto out;
bpf_for(i, 0, TOPO_NR(LLC)) {
llcx = cbw_get_llc_ctx(cgrp, i);
if (!llcx)
break;
if (cgx->nquota_ub == CBW_RUNTUME_INF) {
WRITE_ONCE(llcx->budget_remaining, CBW_RUNTUME_INF);
} else if (READ_ONCE(llcx->budget_remaining) == CBW_RUNTUME_INF) {
WRITE_ONCE(llcx->budget_remaining, 0);
}
}
out:
return 0;
}
static
int cbw_update_nr_taskable_descendents(struct cgroup *cgrp, int delta)
{
struct cgroup_subsys_state *subroot_css, *pos;
struct scx_cgroup_ctx *subroot_cgx, *cur_cgx;
struct cgroup *subroot_cgrp, *cur_cgrp;
if (cgrp->level < 1)
return 0;
subroot_cgrp = bpf_cgroup_ancestor(cgrp, 1);
if (!subroot_cgrp) {
cbw_err("Failed to lookup a subroot cgroup: %llu",
cgroup_get_id(cgrp));
return -ESRCH;
}
subroot_cgx = cbw_get_cgroup_ctx(subroot_cgrp);
if (!subroot_cgx) {
cbw_err("Failed to lookup a subroot context: %llu",
cgroup_get_id(subroot_cgrp));
bpf_cgroup_release(subroot_cgrp);
return -ESRCH;
}
subroot_cgx->nr_taskable_descendents += delta;
cbw_update_budget_tx(subroot_cgx, subroot_cgx);
bpf_rcu_read_lock();
subroot_css = &subroot_cgrp->self;
bpf_for_each(css, pos, subroot_css, BPF_CGROUP_ITER_DESCENDANTS_PRE) {
cur_cgrp = pos->cgroup;
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp);
if (!cur_cgx)
continue;
cbw_update_budget_tx(subroot_cgx, cur_cgx);
}
bpf_rcu_read_unlock();
bpf_cgroup_release(subroot_cgrp);
return 0;
}
__hidden
int scx_cgroup_bw_init(struct cgroup *cgrp __arg_trusted, struct scx_cgroup_init_args *args __arg_trusted)
{
struct scx_cgroup_ctx *cgx, *parentx;
struct cgroup *parent;
int ret;
cbw_dbg_cgrp(" level: %d -- period_us: %llu -- quota_us: %llu -- burst_us: %llu ",
cgrp->level, args->bw_period_us, args->bw_quota_us, args->bw_burst_us);
cgx = bpf_cgrp_storage_get(&cbw_cgrp_map, cgrp, 0,
BPF_LOCAL_STORAGE_GET_F_CREATE);
if (!cgx) {
cbw_err("Failed to allocate cgroup ctx: %llu",
cgroup_get_id(cgrp));
return -ENOMEM;
}
cgx->id = cgroup_get_id(cgrp);
cbw_set_bandwidth(cgrp, cgx, args->bw_period_us, args->bw_quota_us,
args->bw_burst_us);
cbw_update_nquota_ub(cgrp, cgx);
cgx->runtime_total_sloppy = 0;
cgx->budget_remaining = (cgrp->level == 1)? cgx->nquota : 0;
cgx->is_throttled = false;
if ((cgrp->level > 0) &&
(parent = bpf_cgroup_ancestor(cgrp, cgrp->level - 1))) {
parentx = cbw_get_cgroup_ctx(parent);
if (parentx && !cgroup_is_threaded(parent)) {
cbw_free_llc_ctx(parent, parentx);
cbw_update_nr_taskable_descendents(parent, -1);
}
bpf_cgroup_release(parent);
}
ret = cbw_init_llc_ctx(cgrp, cgx);
if (ret)
return ret;
cgx->nr_taskable_descendents = 1;
return cbw_update_nr_taskable_descendents(cgrp, 1);
}
__hidden
int scx_cgroup_bw_exit(struct cgroup *cgrp __arg_trusted)
{
int ret = 0;
cbw_dbg_cgrp();
if (cgrp->level > 1)
ret = cbw_update_nr_taskable_descendents(cgrp, -1);
cbw_del_cgroup_ctx(cgrp);
cbw_free_llc_ctx(cgrp, NULL);
return ret;
}
__hidden
int scx_cgroup_bw_set(struct cgroup *cgrp __arg_trusted, u64 period_us, u64 quota_us, u64 burst_us)
{
struct cgroup *cur_cgrp, *cur_cgrp_trusted;
struct scx_cgroup_ctx *cgx, *cur_cgx;
struct cgroup_subsys_state *subroot_css, *pos;
int ret = 0;
cbw_dbg_cgrp();
cgx = cbw_get_cgroup_ctx(cgrp);
if (!cgx) {
cbw_err("Failed to lookup a cgroup ctx: %llu",
cgroup_get_id(cgrp));
return -ESRCH;
}
cbw_set_bandwidth(cgrp, cgx, period_us, quota_us, burst_us);
bpf_rcu_read_lock();
subroot_css = &cgrp->self;
bpf_for_each(css, pos, subroot_css, BPF_CGROUP_ITER_DESCENDANTS_PRE) {
cur_cgrp = pos->cgroup;
cur_cgrp_trusted = bpf_cgroup_from_id(cgroup_get_id(cur_cgrp));
if (!cur_cgrp_trusted)
continue;
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp_trusted);
if (!cur_cgx) {
bpf_cgroup_release(cur_cgrp_trusted);
continue;
}
ret = cbw_update_nquota_ub(cur_cgrp_trusted, cur_cgx);
bpf_cgroup_release(cur_cgrp_trusted);
if (ret)
goto unlock_out;
}
unlock_out:
bpf_rcu_read_unlock();
return ret;
}
static
bool is_llc_id_valid(int llc_id)
{
return llc_id >= 0 && llc_id < TOPO_NR(LLC);
}
static
s64 cbw_sum_rumtime_total_llcx(struct cgroup *cgrp, struct scx_cgroup_ctx *cgx)
{
struct scx_cgroup_llc_ctx *llcx;
s64 sum;
int i;
if (!cgx->has_llcx)
return 0;
sum = 0;
bpf_for(i, 0, TOPO_NR(LLC)) {
llcx = cbw_get_llc_ctx(cgrp, i);
if (!llcx)
break;
sum += READ_ONCE(llcx->runtime_total);
}
return sum;
}
static
struct tree_levels *get_clean_tree_levels(void)
{
const u32 idx = 0;
struct tree_levels *tree;
tree = bpf_map_lookup_elem(&tree_levels_map, &idx);
if (tree)
__builtin_memset(tree, 0, sizeof(*tree));
return tree;
}
static
int cbw_update_runtime_total_sloppy(struct cgroup *cgrp)
{
u32 cur_level, prev_level = CBW_CGRP_TREE_HEIGHT_MAX;
struct cgroup_subsys_state *subroot_css, *pos;
struct scx_cgroup_ctx *cur_cgx = NULL;
struct tree_levels *tree;
struct cgroup *cur_cgrp;
s64 rt_llcx;
int ret = 0;
tree = get_clean_tree_levels();
if (!tree)
return -ENOMEM;
bpf_rcu_read_lock();
subroot_css = &cgrp->self;
bpf_for_each(css, pos, subroot_css, BPF_CGROUP_ITER_DESCENDANTS_POST) {
cur_cgrp = pos->cgroup;
cur_level = cur_cgrp->level;
if (cur_level == 0 && can_loop)
break;
if (cur_level >= CBW_CGRP_TREE_HEIGHT_MAX) {
ret = -E2BIG;
break;
}
if (prev_level == CBW_CGRP_TREE_HEIGHT_MAX)
prev_level = cur_level;
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp);
if (!cur_cgx) {
continue;
}
rt_llcx = cbw_sum_rumtime_total_llcx(cur_cgrp, cur_cgx);
if (prev_level == cur_level) {
WRITE_ONCE(cur_cgx->runtime_total_sloppy, rt_llcx);
}
else if (prev_level < cur_level) {
WRITE_ONCE(cur_cgx->runtime_total_sloppy, rt_llcx);
}
else if (prev_level > cur_level) {
WRITE_ONCE(cur_cgx->runtime_total_sloppy,
tree->levels[prev_level] + rt_llcx);
tree->levels[prev_level] = 0;
}
if (cur_cgx->runtime_total_sloppy >= cur_cgx->nquota_ub)
WRITE_ONCE(cur_cgx->is_throttled, true);
tree->levels[cur_level] += cur_cgx->runtime_total_sloppy;
prev_level = cur_level;
cbw_dbg("cgid%llu -- rt_llcx: %lld -- runtime_total_sloppy: %lld",
cur_cgx->id, rt_llcx, cur_cgx->runtime_total_sloppy);
}
bpf_rcu_read_unlock();
return ret;
}
static
s64 cbw_transfer_budget_c2l(struct scx_cgroup_ctx *src_cgx, int src_level,
struct scx_cgroup_llc_ctx *tgt_llcx)
{
s64 remaining, debt, b, tgt_br = 0;
do {
remaining = READ_ONCE(tgt_llcx->budget_remaining);
if (remaining > 0)
return remaining;
debt = -remaining;
remaining = READ_ONCE(src_cgx->budget_remaining);
if (remaining <= 0)
break;
b = min(debt + src_cgx->budget_c2l, remaining);
__sync_fetch_and_sub(&src_cgx->budget_remaining, b);
__sync_fetch_and_add(&tgt_llcx->budget_remaining, b);
} while ( ((tgt_br = (READ_ONCE(tgt_llcx->budget_remaining))) <= 0) &&
(READ_ONCE(src_cgx->budget_remaining) > 0) && can_loop);
if ((src_level == 1) && (READ_ONCE(tgt_llcx->budget_remaining) < 0))
WRITE_ONCE(src_cgx->is_throttled, true);
return tgt_br;
}
static
s64 cbw_transfer_budget_p2c(struct scx_cgroup_ctx *subroot_cgx,
struct scx_cgroup_ctx *tgt_cgx)
{
s64 remaining, debt, b, tgt_br = 0;
do {
remaining = READ_ONCE(tgt_cgx->budget_remaining);
if (remaining > 0)
return remaining;
if (subroot_cgx->nquota_ub == CBW_RUNTUME_INF) {
return READ_ONCE(tgt_cgx->budget_remaining);
} else {
debt = -remaining;
remaining = READ_ONCE(subroot_cgx->budget_remaining);
if (remaining <= 0)
break;
b = min(debt + subroot_cgx->budget_p2c, remaining);
__sync_fetch_and_sub(&subroot_cgx->budget_remaining, b);
__sync_fetch_and_add(&tgt_cgx->budget_remaining, b);
}
} while ( ((tgt_br = READ_ONCE(tgt_cgx->budget_remaining)) <= 0) &&
(READ_ONCE(subroot_cgx->budget_remaining) > 0) && can_loop);
if (READ_ONCE(subroot_cgx->budget_remaining) < 0)
WRITE_ONCE(subroot_cgx->is_throttled, true);
return tgt_br;
}
static
s64 cbw_transfer_budget_p2l(struct scx_cgroup_ctx *subroot_cgx,
struct scx_cgroup_ctx *tgt_cgx,
int tgt_level,
struct scx_cgroup_llc_ctx *tgt_llcx)
{
s64 remaining;
if (READ_ONCE(subroot_cgx->budget_remaining) <= 0)
return READ_ONCE(tgt_llcx->budget_remaining);
do {
remaining = cbw_transfer_budget_p2c(subroot_cgx, tgt_cgx);
if (remaining <= 0) {
WRITE_ONCE(tgt_cgx->is_throttled, true);
break;
}
remaining = cbw_transfer_budget_c2l(tgt_cgx, tgt_level, tgt_llcx);
} while((remaining <= 0) && can_loop);
return READ_ONCE(tgt_llcx->budget_remaining);
}
static
void cbw_consume_budget(struct scx_cgroup_ctx *cgx,
struct scx_cgroup_llc_ctx *llcx, u64 consumed_ns)
{
s64 period_duration;
if (llcx->budget_remaining == CBW_RUNTUME_INF)
return;
period_duration = time_delta(scx_bpf_now(),
READ_ONCE(cgx->period_start_clk));
if (consumed_ns > period_duration) {
consumed_ns = period_duration;
}
__sync_fetch_and_sub(&llcx->budget_remaining, consumed_ns);
__sync_fetch_and_add(&llcx->runtime_total, consumed_ns);
}
static
int cbw_get_current_llc_id(void)
{
u32 cpu = bpf_get_smp_processor_id();
return topo_cpu_to_llc_id(cpu);
}
int cbw_cgroup_bw_throttled(struct cgroup *cgrp __arg_trusted, int llc_id)
{
struct scx_cgroup_ctx *cgx, *subroot_cgx;
struct scx_cgroup_llc_ctx *llcx;
struct cgroup *subroot_cgrp;
int ret;
if (cgrp->level == 0)
return 0;
if (!is_llc_id_valid(llc_id)) {
cbw_err("Invalid LLC id: %d", llc_id);
return -EINVAL;
}
llcx = cbw_get_llc_ctx(cgrp, llc_id);
if (!llcx) {
cbw_dbg("Failed to lookup an LLC ctx: [%llu/%d]",
cgroup_get_id(cgrp), llc_id);
return -ESRCH;
}
cbw_dbg_cgrp(" llc_id: %d -- llcx:budget_remaining: %lld",
llc_id, READ_ONCE(llcx->budget_remaining));
if (READ_ONCE(llcx->budget_remaining) > 0)
return 0;
cgx = cbw_get_cgroup_ctx(cgrp);
if (!cgx) {
cbw_err("Failed to lookup a cgroup ctx: %llu",
cgroup_get_id(cgrp));
return -ESRCH;
}
if (READ_ONCE(cgx->is_throttled)) {
dbg_cgx(cgx, "throttled: ");
return -EAGAIN;
}
if (cbw_transfer_budget_c2l(cgx, cgrp->level, llcx) > 0) {
dbg_cgx(cgx, "budget-transfer-to-leaf: ");
dbg_llcx(llcx, "budget-transfer-to-llcx: ");
return 0;
}
cbw_update_runtime_total_sloppy(cgrp);
if (READ_ONCE(cgx->is_throttled)) {
dbg_cgx(cgx, "throttled: ");
return -EAGAIN;
}
if (cgrp->level == 1) {
dbg_cgx(cgx, "throttled: ");
return -EAGAIN;
}
subroot_cgrp = bpf_cgroup_ancestor(cgrp, 1);
if (!subroot_cgrp) {
cbw_err("Failed to lookup a subroot cgroup: %llu",
cgroup_get_id(cgrp));
return -ESRCH;
}
subroot_cgx = cbw_get_cgroup_ctx(subroot_cgrp);
if (!subroot_cgx) {
cbw_err("Failed to lookup a cgroup ctx: %llu",
cgroup_get_id(subroot_cgrp));
ret = -ESRCH;
goto release_out;
}
if (cbw_transfer_budget_p2l(subroot_cgx, cgx, cgrp->level, llcx) > 0) {
dbg_cgx(subroot_cgx, "budget-transfer-to-subroot_cgx: ");
dbg_cgx(cgx, "budget-transfer-to-cgx: ");
dbg_llcx(llcx, "budget-transfer-to-llcx: ");
ret = 0;
goto release_out;
}
ret = -EAGAIN;
dbg_cgx(subroot_cgx, "subroot_cgx:throttled: ");
dbg_cgx(cgx, "cgx:throttled: ");
dbg_llcx(llcx, "llcx:throttled: ");
release_out:
bpf_cgroup_release(subroot_cgrp);
return ret;
}
__hidden
int scx_cgroup_bw_throttled(struct cgroup *cgrp __arg_trusted)
{
int llc_id;
if ((llc_id = cbw_get_current_llc_id()) < 0) {
cbw_err("Invalid LLC id: %d", llc_id);
return -EINVAL;
}
return cbw_cgroup_bw_throttled(cgrp, llc_id);
}
__hidden
int scx_cgroup_bw_consume(struct cgroup *cgrp __arg_trusted, u64 consumed_ns)
{
struct scx_cgroup_llc_ctx *llcx;
struct scx_cgroup_ctx *cgx;
int llc_id;
if (cgrp->level == 0)
return 0;
if ((llc_id = cbw_get_current_llc_id()) < 0) {
cbw_err("Invalid LLC id: %d", llc_id);
return -EINVAL;
}
cgx = cbw_get_cgroup_ctx(cgrp);
llcx = cbw_get_llc_ctx(cgrp, llc_id);
if (!cgx || !llcx) {
return 0;
}
cbw_consume_budget(cgx, llcx, consumed_ns);
cbw_dbg_cgrp(" llc_id: %d -- reserved_ns: %llu -- consumed_ns: %llu -- llcx:budget_remaining: %lld -- llcx:runtime_total: %lld",
llc_id, consumed_ns, READ_ONCE(llcx->budget_remaining),
READ_ONCE(llcx->runtime_total));
return 0;
}
__hidden
int scx_cgroup_bw_put_aside(struct task_struct *p __arg_trusted, u64 ctx, u64 vtime, struct cgroup *cgrp __arg_trusted)
{
scx_task_common *taskc = (scx_task_common *)ctx;
struct scx_cgroup_llc_ctx *llcx;
int llc_id, ret;
cbw_dbg_cgrp(" [%s/%d]", p->comm, p->pid);
if ((llc_id = cbw_get_current_llc_id()) < 0) {
cbw_err("Invalid LLC id: %d", llc_id);
return -EINVAL;
}
llcx = cbw_get_llc_ctx(cgrp, llc_id);
if (!llcx) {
cbw_err("Failed to lookup an LLC ctx: [%llu/%d]",
cgroup_get_id(cgrp), llc_id);
return -ESRCH;
}
if (!llcx->btq) {
cbw_err("BTQ of an LLC ctx is not properly initialized.");
return -ESRCH;
}
ret = scx_atq_lock(llcx->btq);
if (ret) {
cbw_err("Failed to lock ATQ.");
return -EBUSY;
}
if (taskc->atq != NULL) {
cbw_dbg("Possible double enqueue detected.");
scx_atq_unlock(llcx->btq);
return 0;
}
ret = scx_atq_insert_vtime_unlocked(llcx->btq, taskc, vtime);
if (ret)
cbw_err("Failed to insert a task to BTQ: %d", ret);
scx_atq_unlock(llcx->btq);
return ret;
}
static
bool cbw_has_backlogged_tasks(struct scx_cgroup_ctx *cgx)
{
struct scx_cgroup_llc_ctx *llcx;
int i;
if (!cgx || !cgx->has_llcx)
return false;
bpf_for(i, 0, TOPO_NR(LLC)) {
llcx = cbw_get_llc_ctx_with_id(cgx->id, i);
if (!llcx)
break;
if (scx_atq_nr_queued(llcx->btq))
return true;
}
return false;
}
static
bool cbw_replenish_taskable_cgroup(struct scx_cgroup_ctx *subroot_cgx,
struct scx_cgroup_ctx *cgx, u64 now)
{
struct scx_cgroup_llc_ctx *llcx;
s64 burst = 0, debt = 0, base, budget;
bool period_end;
int i;
if (cgx->nquota == CBW_RUNTUME_INF)
goto out_no_replenish;
period_end = time_delta(now, cgx->period_start_clk) >= cgx->period;
if (period_end)
WRITE_ONCE(cgx->period_start_clk, now);
debt = cgx->runtime_total_last - cgx->nquota_ub;
if ((cgx->burst > 0) && (debt < 0)) {
burst = min(-debt, cgx->burst_remaining);
if (period_end)
cgx->burst_remaining = cgx->burst;
else
cgx->burst_remaining -= burst;
}
dbg_cgx(cgx, "replenishing: ");
bpf_for(i, 0, TOPO_NR(LLC)) {
llcx = cbw_get_llc_ctx_with_id(cgx->id, i);
if (llcx && (READ_ONCE(llcx->budget_remaining) < 0))
WRITE_ONCE(llcx->budget_remaining, 0);
}
base = ((subroot_cgx == cgx) ||
(subroot_cgx->nquota_ub == CBW_RUNTUME_INF)) ?
cgx->nquota_ub : 0;
budget = base + ((debt > 0) ? -debt : burst);
WRITE_ONCE(cgx->budget_remaining, budget);
dbg_cgx(cgx, "replenished: ");
out_no_replenish:
WRITE_ONCE(cgx->is_throttled, false);
return READ_ONCE(cgx->is_throttled) || cbw_has_backlogged_tasks(cgx);
}
static
int cbw_get_replenish_stat(void)
{
return READ_ONCE(cbw_replenish_stat.s);
}
static
bool cbw_transit_replenish_stat(int from, int to)
{
if (cbw_get_replenish_stat() != from)
return false;
return __sync_bool_compare_and_swap(&cbw_replenish_stat.s, from, to);
}
__hidden
int scx_cgroup_bw_cancel(u64 ctx)
{
return scx_atq_cancel((scx_task_common *)ctx);
}
static
int replenish_timerfn(void *map, int *key, struct bpf_timer *timer)
{
struct cgroup *root_cgrp, *cur_cgrp, *subroot_cgrp;
struct cgroup_subsys_state *subroot_css, *pos;
struct scx_cgroup_ctx *cur_cgx, *subroot_cgx;
struct scx_cgroup_llc_ctx *cur_llcx;
const struct cpumask *online_mask;
s64 interval, jitter, period;
u64 *ids, now;
s32 idle_cpu;
int i, ret;
scx_arena_subprog_init();
now = scx_bpf_now();
if (!cbw_transit_replenish_stat(CBW_REPLENISH_STAT_IDLE,
CBW_REPLENISH_STAT_TOP_HALF_RUNNING) &&
!cbw_transit_replenish_stat(CBW_REPLENISH_STAT_BOTTOM_HALF_RUNNING,
CBW_REPLENISH_STAT_TOP_HALF_RUNNING)) {
cbw_err("Incorrect replenish state: %d -- %d => %d",
cbw_replenish_stat.s, CBW_REPLENISH_STAT_IDLE,
CBW_REPLENISH_STAT_TOP_HALF_RUNNING);
return 0;
}
cbw_dbg("at %llu", now);
root_cgrp = bpf_cgroup_from_id(1);
if (!root_cgrp) {
cbw_err("Failed to fetch the root cgroup pointer.");
goto idle_out;
}
cbw_update_runtime_total_sloppy(root_cgrp);
bpf_rcu_read_lock();
subroot_css = &root_cgrp->self;
bpf_for_each(css, pos, subroot_css, BPF_CGROUP_ITER_DESCENDANTS_POST) {
cur_cgrp = pos->cgroup;
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp);
if (!cur_cgx) {
continue;
}
if (cur_cgx->has_llcx) {
bpf_for(i, 0, TOPO_NR(LLC)) {
cur_llcx = cbw_get_llc_ctx(cur_cgrp, i);
if (cur_llcx)
WRITE_ONCE(cur_llcx->runtime_total, 0);
}
}
WRITE_ONCE(cur_cgx->runtime_total_last,
READ_ONCE(cur_cgx->runtime_total_sloppy));
WRITE_ONCE(cur_cgx->runtime_total_sloppy, 0);
}
bpf_rcu_read_unlock();
bpf_rcu_read_lock();
cbw_nr_taskable_cgroups = 0;
subroot_css = &root_cgrp->self;
bpf_for_each(css, pos, subroot_css, BPF_CGROUP_ITER_DESCENDANTS_PRE) {
cur_cgrp = pos->cgroup;
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp);
if (!cur_cgx) {
continue;
}
if (cur_cgx->has_llcx || cur_cgrp->level == 1) {
ids = MEMBER_VPTR(cbw_taskable_cgroup_ids,
[cbw_nr_taskable_cgroups]);
if (!ids) {
cbw_err("Failed to fetch a taskable cgroup table.");
continue;
}
*ids = cgroup_get_id(cur_cgrp);
cbw_nr_taskable_cgroups++;
}
}
bpf_rcu_read_unlock();
bpf_cgroup_release(root_cgrp);
cbw_dbg("Start replenish %llu taskable cgroups.", cbw_nr_taskable_cgroups);
cbw_nr_throttled_cgroups = 0;
bpf_for(i, 0, cbw_nr_taskable_cgroups) {
ids = MEMBER_VPTR(cbw_taskable_cgroup_ids, [i]);
if (!ids) {
cbw_err("Failed to fetch a taskable cgroup table.");
continue;
}
cur_cgrp = bpf_cgroup_from_id(ids[0]);
if (!cur_cgrp) {
cbw_dbg("Failed to fetch a cgroup pointer: cgid%llu", ids[0]);
continue;
}
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp);
if (!cur_cgx) {
cbw_err("Failed to lookup a cgroup ctx: cgid%llu", ids[0]);
bpf_cgroup_release(cur_cgrp);
continue;
}
if (cur_cgrp->level > 1) {
subroot_cgrp = bpf_cgroup_ancestor(cur_cgrp, 1);
if (!subroot_cgrp) {
cbw_err("Failed to lookup a subroot cgroup: cgid%llu",
cgroup_get_id(cur_cgrp));
bpf_cgroup_release(cur_cgrp);
continue;
}
subroot_cgx = cbw_get_cgroup_ctx(subroot_cgrp);
if (!subroot_cgx) {
cbw_err("Failed to lookup a subroot context: cgid%llu",
cgroup_get_id(subroot_cgrp));
bpf_cgroup_release(cur_cgrp);
bpf_cgroup_release(subroot_cgrp);
continue;
}
bpf_cgroup_release(subroot_cgrp);
} else
subroot_cgx = cur_cgx;
bpf_cgroup_release(cur_cgrp);
if (cbw_replenish_taskable_cgroup(subroot_cgx, cur_cgx, now)) {
ids = MEMBER_VPTR(cbw_throttled_cgroup_ids,
[cbw_nr_throttled_cgroups]);
if (!ids) {
cbw_err("Failed to fetch a throttled cgroup table.");
continue;
}
WRITE_ONCE(ids[0], cur_cgx->id);
cbw_nr_throttled_cgroups++;
}
}
if (cbw_nr_throttled_cgroups > 0) {
if (!cbw_transit_replenish_stat(
CBW_REPLENISH_STAT_TOP_HALF_RUNNING,
CBW_REPLENISH_STAT_BOTTOM_HALF_READY)) {
cbw_err("Fail to transit the replenish state");
}
online_mask = scx_bpf_get_online_cpumask();
idle_cpu = scx_bpf_pick_idle_cpu(online_mask, SCX_PICK_IDLE_CORE);
if (idle_cpu == -EBUSY)
idle_cpu = scx_bpf_pick_idle_cpu(online_mask, 0);
if (idle_cpu >= 0)
scx_bpf_kick_cpu(idle_cpu, SCX_KICK_IDLE);
scx_bpf_put_cpumask(online_mask);
}
else {
idle_out:
if (!cbw_transit_replenish_stat(
CBW_REPLENISH_STAT_TOP_HALF_RUNNING,
CBW_REPLENISH_STAT_IDLE)) {
cbw_err("Fail to transit the replenish state");
}
}
interval = time_delta(now, cbw_last_replenish_at);
jitter = time_delta(interval, CBW_NPERIOD);
period = max(time_delta(CBW_NPERIOD, jitter), CBW_BUDGET_XFER_MIN);
if ((ret = bpf_timer_start(timer, period, 0)))
cbw_err("Failed to re-arm replenish timer: %d", ret);
cbw_last_replenish_at = now;
return 0;
}
static
int cbw_drain_btq_until_throttled(struct scx_cgroup_ctx *cgx,
struct scx_cgroup_llc_ctx *llcx)
{
scx_task_common *taskc;
int i;
for (i = 0; i < CBW_REENQ_MAX_BATCH &&
!READ_ONCE(cgx->is_throttled) &&
(taskc = (scx_task_common *)scx_atq_pop(llcx->btq)) &&
can_loop; i++) {
scx_cgroup_bw_enqueue_cb((u64)taskc);
cbw_dbg("cgid%llu", cgx->id);
}
return i;
}
static
int cbw_reenqueue_cgroup(struct cgroup *cgrp, struct scx_cgroup_ctx *cgx,
u64 cgrp_id, u64 nuance)
{
struct scx_cgroup_llc_ctx *llcx;
int i, idx, nr_enq = 0;
if (!cgx->has_llcx)
return false;
cbw_dbg("cgid%llu", cgrp_id);
bpf_for(i, 0, TOPO_NR(LLC)) {
idx = (nuance + i) % TOPO_NR(LLC);
llcx = cbw_get_llc_ctx_with_id(cgrp_id, idx);
if (!llcx) {
cbw_err("Failed to lookup an LLC context: cgid%llu", cgrp_id);
continue;
}
if (cbw_cgroup_bw_throttled(cgrp, idx) == -EAGAIN) {
cbw_dbg("Give up on re-enqueueing tasks since cgroup "
"is already throttled: cgid%llu", cgrp_id);
break;
}
nr_enq += cbw_drain_btq_until_throttled(cgx, llcx);
if (nr_enq >= CBW_REENQ_MAX_BATCH)
break;
}
return nr_enq;
}
static
bool cbw_try_lock(u64 *lock)
{
if (READ_ONCE(*lock) == 1)
return false;
return __sync_bool_compare_and_swap(lock, 0, 1);
}
static
void cbw_unlock(u64 *lock)
{
WRITE_ONCE(*lock, 0);
}
__hidden
int scx_cgroup_bw_reenqueue(void)
{
static u64 reenq_lock = 0;
struct scx_cgroup_ctx *cur_cgx;
struct cgroup *cur_cgrp;
int i, idx, n, nr_enq = 0;
u64 nuance, nuance2, nr_tcgs;
u64 *ids, cur_cgrp_id;
if ((cbw_get_replenish_stat() !=
CBW_REPLENISH_STAT_BOTTOM_HALF_RUNNING) &&
(!cbw_transit_replenish_stat(
CBW_REPLENISH_STAT_BOTTOM_HALF_READY,
CBW_REPLENISH_STAT_BOTTOM_HALF_RUNNING))) {
return 0;
}
if (!cbw_try_lock(&reenq_lock))
return 0;
cbw_dbg();
nuance = bpf_get_prandom_u32();
nr_tcgs = READ_ONCE(cbw_nr_throttled_cgroups);
bpf_for(i, 0, nr_tcgs) {
nuance2 = nuance + i;
idx = nuance2 % nr_tcgs;
ids = MEMBER_VPTR(cbw_throttled_cgroup_ids, [idx]);
if (!ids) {
cbw_err("Failed to fetch a throttled cgroup table.");
continue;
}
cur_cgrp_id = READ_ONCE(ids[0]);
if (cur_cgrp_id == 0)
continue;
cur_cgrp = bpf_cgroup_from_id(cur_cgrp_id);
if (!cur_cgrp) {
cbw_err("Failed to fetch a cgroup pointer: %llu", ids[0]);
continue;
}
cur_cgx = cbw_get_cgroup_ctx(cur_cgrp);
if (!cur_cgx) {
cbw_err("Failed to lookup a cgroup ctx");
bpf_cgroup_release(cur_cgrp);
continue;
}
n = cbw_reenqueue_cgroup(cur_cgrp, cur_cgx, cur_cgrp_id, nuance2);
bpf_cgroup_release(cur_cgrp);
if (n < CBW_REENQ_MAX_BATCH) {
__sync_bool_compare_and_swap(ids, cur_cgrp_id, 0);
}
nr_enq += n;
if (nr_enq >= CBW_REENQ_MAX_BATCH)
break;
}
cbw_unlock(&reenq_lock);
if (nr_enq < CBW_REENQ_MAX_BATCH) {
cbw_transit_replenish_stat(
CBW_REPLENISH_STAT_BOTTOM_HALF_RUNNING,
CBW_REPLENISH_STAT_IDLE);
}
return 0;
}
__hidden
int scx_cgroup_bw_is_cgroup_throttled(u64 cgrp_id)
{
struct scx_cgroup_ctx *cgx;
struct cgroup *cgrp;
cgrp = bpf_cgroup_from_id(cgrp_id);
if (!cgrp)
return 0;
cgx = cbw_get_cgroup_ctx(cgrp);
bpf_cgroup_release(cgrp);
if (!cgx)
return 0;
return READ_ONCE(cgx->is_throttled);
}
__hidden
int scx_cgroup_bw_is_task_throttled(u64 taskc)
{
scx_task_common *ctx = (scx_task_common *)taskc;
return ctx && (ctx->atq != NULL);
}