#include "hammer.h"
static int rebalance_node(struct hammer_ioc_rebalance *rebal,
hammer_cursor_t cursor, hammer_node_lock_t lcache);
static void rebalance_closeout(hammer_node_lock_t base_item, int base_count,
hammer_btree_elm_t elm);
static void rebalance_parent_ptrs(hammer_node_lock_t base_item, int index,
hammer_node_lock_t item, hammer_node_lock_t chld_item);
int
hammer_ioc_rebalance(hammer_transaction_t trans, hammer_inode_t ip,
struct hammer_ioc_rebalance *rebal)
{
struct hammer_cursor cursor;
struct hammer_node_lock lcache;
hammer_btree_leaf_elm_t elm;
int error;
int seq;
uint32_t key_end_localization;
if ((rebal->key_beg.localization | rebal->key_end.localization) &
HAMMER_LOCALIZE_PSEUDOFS_MASK) {
return(EINVAL);
}
if (rebal->key_beg.localization > rebal->key_end.localization)
return(EINVAL);
if (rebal->key_beg.localization == rebal->key_end.localization) {
if (rebal->key_beg.obj_id > rebal->key_end.obj_id)
return(EINVAL);
}
if (rebal->saturation < HAMMER_BTREE_INT_ELMS / 2)
rebal->saturation = HAMMER_BTREE_INT_ELMS / 2;
if (rebal->saturation > HAMMER_BTREE_INT_ELMS)
rebal->saturation = HAMMER_BTREE_INT_ELMS;
rebal->key_cur = rebal->key_beg;
rebal->key_cur.localization &= HAMMER_LOCALIZE_MASK;
if (rebal->allpfs == 0)
rebal->key_cur.localization |= ip->obj_localization;
key_end_localization = rebal->key_end.localization;
key_end_localization &= HAMMER_LOCALIZE_MASK;
if (rebal->allpfs == 0)
key_end_localization |= ip->obj_localization;
else
key_end_localization |= pfs_to_lo(HAMMER_MAX_PFSID);
hammer_btree_lcache_init(trans->hmp, &lcache, 2);
seq = trans->hmp->flusher.done;
retry:
error = hammer_init_cursor(trans, &cursor, NULL, NULL);
if (error) {
hammer_done_cursor(&cursor);
goto failed;
}
cursor.key_beg = rebal->key_cur;
cursor.key_end = rebal->key_end;
cursor.key_end.localization = key_end_localization;
cursor.flags |= HAMMER_CURSOR_END_INCLUSIVE;
cursor.flags |= HAMMER_CURSOR_BACKEND;
cursor.flags |= HAMMER_CURSOR_REBLOCKING;
error = hammer_btree_first(&cursor);
while (error == 0) {
if (vm_test_nominal()) {
hammer_unlock_cursor(&cursor);
vm_wait_nominal();
hammer_lock_cursor(&cursor);
}
if (trans->hmp->ronly) {
error = EROFS;
break;
}
if (cursor.node->ondisk->type == HAMMER_BTREE_TYPE_INTERNAL) {
if (cursor.index == cursor.node->ondisk->count - 1) {
hammer_sync_lock_sh(trans);
error = rebalance_node(rebal, &cursor, &lcache);
hammer_sync_unlock(trans);
}
} else {
cursor.index = cursor.node->ondisk->count - 1;
}
if (error)
break;
elm = &cursor.node->ondisk->elms[cursor.index].leaf;
rebal->key_cur = elm->base;
++rebal->stat_ncount;
while (hammer_flusher_meta_halflimit(trans->hmp) ||
hammer_flusher_undo_exhausted(trans, 2)) {
hammer_unlock_cursor(&cursor);
hammer_flusher_wait(trans->hmp, seq);
hammer_lock_cursor(&cursor);
seq = hammer_flusher_async_one(trans->hmp);
}
if (hammer_btree_cmp(&rebal->key_cur, &cursor.key_end) > 0) {
rebal->key_cur = cursor.key_end;
break;
}
if ((error = hammer_signal_check(trans->hmp)) != 0)
break;
error = hammer_btree_iterate(&cursor);
}
if (error == ENOENT)
error = 0;
hammer_done_cursor(&cursor);
if (error == EDEADLK) {
++rebal->stat_collisions;
goto retry;
}
if (error == EINTR) {
rebal->head.flags |= HAMMER_IOC_HEAD_INTR;
error = 0;
}
failed:
rebal->key_cur.localization &= HAMMER_LOCALIZE_MASK;
hammer_btree_lcache_free(trans->hmp, &lcache);
return(error);
}
static int
rebalance_node(struct hammer_ioc_rebalance *rebal, hammer_cursor_t cursor,
hammer_node_lock_t lcache)
{
struct hammer_node_lock lockroot;
hammer_node_lock_t base_item;
hammer_node_lock_t chld_item;
hammer_node_lock_t item;
hammer_btree_elm_t elm;
hammer_node_t node;
hammer_tid_t tid;
uint8_t type1 __debugvar;
int base_count;
int root_count;
int avg_elms;
int count;
int error;
int i;
int n;
hammer_node_lock_init(&lockroot, cursor->node);
error = hammer_cursor_upgrade(cursor);
if (error)
goto done;
error = hammer_btree_lock_children(cursor, 2, &lockroot, lcache);
if (error)
goto done;
hammer_btree_lock_copy(cursor, &lockroot);
if (TAILQ_FIRST(&lockroot.list) == NULL)
goto done;
type1 = TAILQ_FIRST(&lockroot.list)->node->ondisk->type;
if (hammer_debug_general & 0x1000)
hdkprintf("lockroot %p count %d\n", &lockroot, lockroot.count);
count = 0;
TAILQ_FOREACH(item, &lockroot.list, entry) {
if (hammer_debug_general & 0x1000)
hdkprintf("add count %d\n", item->count);
count += item->count;
KKASSERT(item->node->ondisk->type == type1);
}
avg_elms = howmany(count, lockroot.count);
KKASSERT(avg_elms >= 0);
if (count && avg_elms < rebal->saturation) {
n = howmany(count, rebal->saturation);
avg_elms = howmany(count, n);
}
base_item = TAILQ_FIRST(&lockroot.list);
base_count = 0;
root_count = 0;
TAILQ_FOREACH(item, &lockroot.list, entry) {
node = item->node;
KKASSERT(item->count == node->ondisk->count);
chld_item = TAILQ_FIRST(&item->list);
for (i = 0; i < item->count; ++i) {
if (base_count == avg_elms) {
if (i == 0) {
elm = &lockroot.node->ondisk->elms[
item->index];
} else {
elm = &node->ondisk->elms[i];
}
rebalance_closeout(base_item, base_count, elm);
base_item = TAILQ_NEXT(base_item, entry);
KKASSERT(base_item);
base_count = 0;
++root_count;
}
if (item == base_item && i == base_count) {
++base_count;
if (chld_item)
chld_item = TAILQ_NEXT(chld_item, entry);
continue;
}
elm = &base_item->copy->elms[base_count];
*elm = node->ondisk->elms[i];
base_item->flags |= HAMMER_NODE_LOCK_UPDATED;
tid = node->ondisk->mirror_tid;
if (base_item->copy->mirror_tid < tid) {
base_item->copy->mirror_tid = tid;
if (lockroot.copy->mirror_tid < tid) {
lockroot.copy->mirror_tid = tid;
lockroot.flags |=
HAMMER_NODE_LOCK_UPDATED;
}
if (lockroot.copy->elms[root_count].
internal.mirror_tid < tid) {
lockroot.copy->elms[root_count].
internal.mirror_tid = tid;
lockroot.flags |=
HAMMER_NODE_LOCK_UPDATED;
}
base_item->flags |= HAMMER_NODE_LOCK_UPDATED;
}
if (item != base_item &&
node->ondisk->type == HAMMER_BTREE_TYPE_INTERNAL) {
KKASSERT(chld_item);
rebalance_parent_ptrs(base_item, base_count,
item, chld_item);
}
hammer_cursor_moved_element(item->parent->node,
item->index,
node, i,
base_item->node,
base_count);
++base_count;
if (chld_item)
chld_item = TAILQ_NEXT(chld_item, entry);
}
hammer_cursor_moved_element(item->parent->node, item->index,
node, i,
base_item->node, base_count);
}
elm = &lockroot.node->ondisk->elms[lockroot.node->ondisk->count];
rebalance_closeout(base_item, base_count, elm);
++root_count;
if (lockroot.copy->count != root_count) {
lockroot.copy->count = root_count;
lockroot.copy->elms[root_count] = *elm;
lockroot.flags |= HAMMER_NODE_LOCK_UPDATED;
}
while ((base_item = TAILQ_NEXT(base_item, entry)) != NULL) {
hammer_cursor_removed_node(base_item->node, lockroot.node,
base_count);
hammer_cursor_deleted_element(lockroot.node, base_count);
base_item->copy->type = HAMMER_BTREE_TYPE_DELETED;
base_item->copy->count = 0;
base_item->flags |= HAMMER_NODE_LOCK_UPDATED;
if (cursor->index > lockroot.copy->count)
--cursor->index;
++rebal->stat_deletions;
}
rebal->stat_nrebal += hammer_btree_sync_copy(cursor, &lockroot);
done:
hammer_btree_unlock_children(cursor->trans->hmp, &lockroot, lcache);
hammer_cursor_downgrade(cursor);
return (error);
}
static
void
rebalance_closeout(hammer_node_lock_t base_item, int base_count,
hammer_btree_elm_t elm)
{
hammer_node_lock_t parent;
hammer_btree_elm_t base_elm;
hammer_btree_elm_t rbound_elm;
uint8_t save;
if (hammer_debug_general & 0x1000) {
hdkprintf("%016jx:", (intmax_t)base_item->node->node_offset);
}
if (base_item->copy->count != base_count) {
base_item->flags |= HAMMER_NODE_LOCK_UPDATED;
base_item->copy->count = base_count;
if (hammer_debug_general & 0x1000)
kprintf(" (count update)");
}
if (base_item->copy->type == HAMMER_BTREE_TYPE_INTERNAL) {
KKASSERT(base_count != 0);
base_elm = &base_item->copy->elms[base_count];
if (bcmp(base_elm, elm, sizeof(*elm)) != 0 ||
elm->internal.subtree_offset ||
elm->internal.mirror_tid ||
elm->base.btype) {
*base_elm = *elm;
base_elm->internal.subtree_offset = 0;
base_elm->internal.mirror_tid = 0;
base_elm->base.btype = 0;
base_item->flags |= HAMMER_NODE_LOCK_UPDATED;
if (hammer_debug_general & 0x1000)
kprintf(" (rhs update)");
} else {
if (hammer_debug_general & 0x1000)
kprintf(" (rhs same)");
}
}
parent = base_item->parent;
rbound_elm = &parent->copy->elms[base_item->index + 1];
if (bcmp(&rbound_elm->base, &elm->base, sizeof(elm->base)) != 0) {
save = rbound_elm->base.btype;
rbound_elm->base = elm->base;
rbound_elm->base.btype = save;
parent->flags |= HAMMER_NODE_LOCK_UPDATED;
if (hammer_debug_general & 0x1000) {
kprintf(" (parent bound update %d)",
base_item->index + 1);
}
}
if (hammer_debug_general & 0x1000)
kprintf("\n");
}
static
void
rebalance_parent_ptrs(hammer_node_lock_t base_item, int index,
hammer_node_lock_t item, hammer_node_lock_t chld_item)
{
KKASSERT(chld_item->node->ondisk->parent == item->node->node_offset);
chld_item->copy->parent = base_item->node->node_offset;
chld_item->flags |= HAMMER_NODE_LOCK_UPDATED;
hammer_cursor_parent_changed(chld_item->node,
item->node, base_item->node, index);
}