#include "hammer.h"
static void hammer_flusher_master_thread(void *arg);
static void hammer_flusher_slave_thread(void *arg);
static int hammer_flusher_flush(hammer_mount_t hmp, int *nomorep);
static int hammer_flusher_flush_inode(hammer_inode_t ip, void *data);
RB_GENERATE(hammer_fls_rb_tree, hammer_inode, rb_flsnode,
hammer_ino_rb_compare);
typedef struct hammer_flusher_info {
TAILQ_ENTRY(hammer_flusher_info) entry;
hammer_mount_t hmp;
thread_t td;
int runstate;
hammer_flush_group_t flg;
struct hammer_transaction trans;
} *hammer_flusher_info_t;
void
hammer_flusher_sync(hammer_mount_t hmp)
{
int seq;
seq = hammer_flusher_async(hmp, NULL);
hammer_flusher_wait(hmp, seq);
}
int
hammer_flusher_async(hammer_mount_t hmp, hammer_flush_group_t close_flg)
{
hammer_flush_group_t flg;
int seq;
if (close_flg && close_flg->closed)
return(close_flg->seq);
while ((flg = hmp->next_flush_group) != NULL) {
KKASSERT(flg->closed == 0 && flg->running == 0);
flg->closed = 1;
hmp->next_flush_group = TAILQ_NEXT(flg, flush_entry);
if (flg == close_flg)
break;
}
if (hmp->flusher.td) {
if (hmp->flusher.signal++ == 0)
wakeup(&hmp->flusher.signal);
if (flg) {
seq = flg->seq;
} else {
seq = hmp->flusher.next;
++hmp->flusher.next;
}
} else {
seq = hmp->flusher.done;
}
return(seq);
}
int
hammer_flusher_async_one(hammer_mount_t hmp)
{
hammer_flush_group_t flg;
int seq;
if (hmp->flusher.td) {
flg = TAILQ_FIRST(&hmp->flush_group_list);
seq = hammer_flusher_async(hmp, flg);
} else {
seq = hmp->flusher.done;
}
return(seq);
}
void
hammer_flusher_wait(hammer_mount_t hmp, int seq)
{
while (seq - hmp->flusher.done > 0)
tsleep(&hmp->flusher.done, 0, "hmrfls", 0);
}
int
hammer_flusher_running(hammer_mount_t hmp)
{
int seq = hmp->flusher.next - 1;
if (seq - hmp->flusher.done > 0)
return(1);
return (0);
}
void
hammer_flusher_wait_next(hammer_mount_t hmp)
{
int seq;
seq = hammer_flusher_async_one(hmp);
hammer_flusher_wait(hmp, seq);
}
void
hammer_flusher_create(hammer_mount_t hmp)
{
hammer_flusher_info_t info;
int i;
hmp->flusher.signal = 0;
hmp->flusher.done = 0;
hmp->flusher.next = 1;
hammer_ref(&hmp->flusher.finalize_lock);
TAILQ_INIT(&hmp->flusher.run_list);
TAILQ_INIT(&hmp->flusher.ready_list);
lwkt_create(hammer_flusher_master_thread, hmp,
&hmp->flusher.td, NULL, 0, -1, "hammer-M");
for (i = 0; i < HAMMER_MAX_FLUSHERS; ++i) {
info = kmalloc(sizeof(*info), hmp->m_misc, M_WAITOK|M_ZERO);
info->hmp = hmp;
TAILQ_INSERT_TAIL(&hmp->flusher.ready_list, info, entry);
lwkt_create(hammer_flusher_slave_thread, info,
&info->td, NULL, 0, -1, "hammer-S%d", i);
}
}
void
hammer_flusher_destroy(hammer_mount_t hmp)
{
hammer_flusher_info_t info;
hmp->flusher.exiting = 1;
while (hmp->flusher.td) {
++hmp->flusher.signal;
wakeup(&hmp->flusher.signal);
tsleep(&hmp->flusher.exiting, 0, "hmrwex", hz);
}
while ((info = TAILQ_FIRST(&hmp->flusher.ready_list)) != NULL) {
KKASSERT(info->runstate == 0);
TAILQ_REMOVE(&hmp->flusher.ready_list, info, entry);
info->runstate = -1;
wakeup(&info->runstate);
while (info->td)
tsleep(&info->td, 0, "hmrwwc", 0);
kfree(info, hmp->m_misc);
}
}
static void
hammer_flusher_master_thread(void *arg)
{
hammer_mount_t hmp;
int seq;
int nomore;
hmp = arg;
lwkt_gettoken(&hmp->fs_token);
for (;;) {
for (;;) {
while (hmp->flusher.group_lock)
tsleep(&hmp->flusher.group_lock, 0, "hmrhld",0);
hammer_flusher_clean_loose_ios(hmp);
seq = hammer_flusher_flush(hmp, &nomore);
hmp->flusher.done = seq;
wakeup(&hmp->flusher.done);
if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
break;
if (nomore)
break;
}
if (hmp->flusher.exiting && TAILQ_EMPTY(&hmp->flush_group_list))
break;
while (hmp->flusher.signal == 0)
tsleep(&hmp->flusher.signal, 0, "hmrwwa", 0);
hmp->flusher.signal = 0;
}
hmp->flusher.td = NULL;
wakeup(&hmp->flusher.exiting);
lwkt_reltoken(&hmp->fs_token);
lwkt_exit();
}
static int
hammer_flusher_flush(hammer_mount_t hmp, int *nomorep)
{
hammer_flusher_info_t info;
hammer_flush_group_t flg;
hammer_reserve_t resv;
int count;
int seq;
if (TAILQ_FIRST(&hmp->flusher.ready_list) == NULL) {
*nomorep = 1;
return (hmp->flusher.done);
}
*nomorep = 0;
seq = hmp->flusher.done + 1;
flg = TAILQ_FIRST(&hmp->flush_group_list);
if (flg == NULL) {
if (seq == hmp->flusher.next) {
*nomorep = 1;
return (hmp->flusher.done);
}
} else if (seq == flg->seq) {
if (flg->closed) {
KKASSERT(flg->running == 0);
flg->running = 1;
if (hmp->fill_flush_group == flg) {
hmp->fill_flush_group =
TAILQ_NEXT(flg, flush_entry);
}
} else {
*nomorep = 1;
return (hmp->flusher.done);
}
} else {
KKASSERT(flg->seq - seq > 0 || hmp->ronly >= 2);
flg = NULL;
}
count = 0;
while (flg && flg->running) {
++count;
if (hammer_debug_general & 0x0001) {
hdkprintf("%d ttl=%d recs=%d\n",
flg->seq, flg->total_count, flg->refs);
}
if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
break;
hammer_start_transaction_fls(&hmp->flusher.trans, hmp);
if (hammer_flusher_undo_exhausted(&hmp->flusher.trans, 3))
hammer_flusher_finalize(&hmp->flusher.trans, 0);
KKASSERT(hmp->next_flush_group != flg);
while ((info = TAILQ_FIRST(&hmp->flusher.ready_list)) != NULL) {
TAILQ_REMOVE(&hmp->flusher.ready_list, info, entry);
info->flg = flg;
info->runstate = 1;
info->trans = hmp->flusher.trans;
TAILQ_INSERT_TAIL(&hmp->flusher.run_list, info, entry);
wakeup(&info->runstate);
}
while (TAILQ_FIRST(&hmp->flusher.run_list) != NULL)
tsleep(&hmp->flusher.ready_list, 0, "hmrfcc", 0);
hammer_flusher_finalize(&hmp->flusher.trans, 1);
hmp->flusher.tid = hmp->flusher.trans.tid;
hammer_done_transaction(&hmp->flusher.trans);
if (RB_EMPTY(&flg->flush_tree)) {
KKASSERT(flg->refs == 0);
TAILQ_REMOVE(&hmp->flush_group_list, flg, flush_entry);
kfree(flg, hmp->m_misc);
break;
}
KKASSERT(TAILQ_FIRST(&hmp->flush_group_list) == flg);
}
if (count == 0 && hammer_flusher_haswork(hmp)) {
hammer_start_transaction_fls(&hmp->flusher.trans, hmp);
hammer_flusher_finalize(&hmp->flusher.trans, 1);
hammer_done_transaction(&hmp->flusher.trans);
}
while ((resv = TAILQ_FIRST(&hmp->delay_list)) != NULL) {
if (resv->flg_no - seq > 0)
break;
hammer_reserve_clrdelay(hmp, resv);
}
return (seq);
}
static void
hammer_flusher_slave_thread(void *arg)
{
hammer_flush_group_t flg;
hammer_flusher_info_t info;
hammer_mount_t hmp;
info = arg;
hmp = info->hmp;
lwkt_gettoken(&hmp->fs_token);
for (;;) {
while (info->runstate == 0)
tsleep(&info->runstate, 0, "hmrssw", 0);
if (info->runstate < 0)
break;
flg = info->flg;
RB_SCAN(hammer_fls_rb_tree, &flg->flush_tree, NULL,
hammer_flusher_flush_inode, info);
info->runstate = 0;
info->flg = NULL;
TAILQ_REMOVE(&hmp->flusher.run_list, info, entry);
TAILQ_INSERT_TAIL(&hmp->flusher.ready_list, info, entry);
wakeup(&hmp->flusher.ready_list);
}
info->td = NULL;
wakeup(&info->td);
lwkt_reltoken(&hmp->fs_token);
lwkt_exit();
}
void
hammer_flusher_clean_loose_ios(hammer_mount_t hmp)
{
hammer_buffer_t buffer;
hammer_io_t io;
if ((io = RB_ROOT(&hmp->lose_root)) != NULL) {
lwkt_gettoken(&hmp->io_token);
while ((io = RB_ROOT(&hmp->lose_root)) != NULL) {
KKASSERT(io->mod_root == &hmp->lose_root);
RB_REMOVE(hammer_mod_rb_tree, io->mod_root, io);
io->mod_root = NULL;
hammer_ref(&io->lock);
buffer = (void *)io;
hammer_rel_buffer(buffer, 0);
}
lwkt_reltoken(&hmp->io_token);
}
}
static
int
hammer_flusher_flush_inode(hammer_inode_t ip, void *data)
{
hammer_flusher_info_t info = data;
hammer_mount_t hmp = info->hmp;
hammer_transaction_t trans = &info->trans;
int error;
if (ip->flags & HAMMER_INODE_SLAVEFLUSH)
return(0);
ip->flags |= HAMMER_INODE_SLAVEFLUSH;
++hammer_stats_inode_flushes;
hammer_flusher_clean_loose_ios(hmp);
vm_wait_nominal();
error = hammer_sync_inode(trans, ip);
if (error) {
ip->flags |= HAMMER_INODE_WOULDBLOCK;
if (error == EWOULDBLOCK)
error = 0;
}
hammer_sync_inode_done(ip, error);
while (hmp->flusher.finalize_want)
tsleep(&hmp->flusher.finalize_want, 0, "hmrsxx", 0);
if (hammer_flusher_undo_exhausted(trans, 1)) {
hkprintf("Warning: UNDO area too small!\n");
hammer_flusher_finalize(trans, 1);
} else if (hammer_flusher_meta_limit(trans->hmp)) {
hammer_flusher_finalize(trans, 0);
}
return (0);
}
int
hammer_flusher_undo_exhausted(hammer_transaction_t trans, int quarter)
{
if (hammer_undo_space(trans) <
hammer_undo_max(trans->hmp) * quarter / 4) {
return(1);
} else {
return(0);
}
}
void
hammer_flusher_finalize(hammer_transaction_t trans, int final)
{
hammer_volume_t root_volume;
hammer_blockmap_t cundomap, dundomap;
hammer_mount_t hmp;
hammer_io_t io;
hammer_off_t save_undo_next_offset;
int count;
int i;
hmp = trans->hmp;
root_volume = trans->rootvol;
++hmp->flusher.finalize_want;
hammer_lock_ex(&hmp->flusher.finalize_lock);
if (final == 0 && !hammer_flusher_meta_limit(hmp))
goto done;
if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
goto done;
count = 0;
while ((io = RB_FIRST(hammer_mod_rb_tree, &hmp->data_root)) != NULL) {
if (io->ioerror)
break;
hammer_ref(&io->lock);
hammer_io_write_interlock(io);
KKASSERT(io->type != HAMMER_IOTYPE_VOLUME);
hammer_io_flush(io, 0);
hammer_io_done_interlock(io);
hammer_rel_buffer(HAMMER_ITOB(io), 0);
hammer_io_limit_backlog(hmp);
++count;
}
hammer_sync_lock_ex(trans);
if (final) {
cundomap = &hmp->blockmap[0];
dundomap = &root_volume->ondisk->vol0_blockmap[0];
if (root_volume->io.modified) {
hammer_modify_volume(trans, root_volume,
dundomap, sizeof(hmp->blockmap));
for (i = 0; i < HAMMER_MAX_ZONES; ++i) {
hammer_crc_set_blockmap(hmp->version,
&cundomap[i]);
}
bcopy(cundomap, dundomap, sizeof(hmp->blockmap));
hammer_modify_volume_done(root_volume);
}
}
cundomap = &hmp->blockmap[HAMMER_ZONE_UNDO_INDEX];
hammer_lock_ex(&hmp->undo_lock);
save_undo_next_offset = cundomap->next_offset;
hammer_unlock(&hmp->undo_lock);
hammer_flusher_flush_undos(hmp, HAMMER_FLUSH_UNDOS_FORCED);
if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
goto failed;
dundomap = &root_volume->ondisk->vol0_blockmap[HAMMER_ZONE_UNDO_INDEX];
cundomap = &hmp->blockmap[HAMMER_ZONE_UNDO_INDEX];
if (dundomap->first_offset != cundomap->first_offset ||
dundomap->next_offset != save_undo_next_offset) {
hammer_modify_volume_noundo(NULL, root_volume);
dundomap->first_offset = cundomap->first_offset;
dundomap->next_offset = save_undo_next_offset;
hammer_crc_set_blockmap(hmp->version, dundomap);
hammer_modify_volume_done(root_volume);
}
if (root_volume->io.modified) {
hammer_modify_volume_noundo(NULL, root_volume);
if (root_volume->ondisk->vol0_next_tid < trans->tid)
root_volume->ondisk->vol0_next_tid = trans->tid;
hammer_crc_set_volume(hmp->version, root_volume->ondisk);
hammer_modify_volume_done(root_volume);
hammer_io_write_interlock(&root_volume->io);
hammer_io_flush(&root_volume->io, 0);
hammer_io_done_interlock(&root_volume->io);
}
hammer_flusher_clean_loose_ios(hmp);
if (hmp->version < HAMMER_VOL_VERSION_FOUR)
hammer_io_wait_all(hmp, "hmrfl3", 1);
if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
goto failed;
count = 0;
while ((io = RB_FIRST(hammer_mod_rb_tree, &hmp->meta_root)) != NULL) {
if (io->ioerror)
break;
KKASSERT(io->modify_refs == 0);
hammer_ref(&io->lock);
KKASSERT(io->type != HAMMER_IOTYPE_VOLUME);
hammer_io_flush(io, 0);
hammer_rel_buffer(HAMMER_ITOB(io), 0);
hammer_io_limit_backlog(hmp);
++count;
}
if (final) {
cundomap = &hmp->blockmap[HAMMER_ZONE_UNDO_INDEX];
if (cundomap->first_offset != save_undo_next_offset) {
cundomap->first_offset = save_undo_next_offset;
hmp->hflags |= HMNT_UNDO_DIRTY;
} else if (cundomap->first_offset != cundomap->next_offset) {
hmp->hflags |= HMNT_UNDO_DIRTY;
} else {
hmp->hflags &= ~HMNT_UNDO_DIRTY;
}
hammer_clear_undo_history(hmp);
if (hmp->flush_tid1 != hmp->flush_tid2) {
hmp->flush_tid1 = hmp->flush_tid2;
wakeup(&hmp->flush_tid1);
}
hmp->flush_tid2 = trans->tid;
hmp->flags &= ~HAMMER_MOUNT_REDO_SYNC;
}
failed:
hammer_sync_unlock(trans);
if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR) {
hvkprintf(root_volume,
"Critical write error during flush, "
"refusing to sync UNDO FIFO\n");
}
done:
hammer_unlock(&hmp->flusher.finalize_lock);
if (--hmp->flusher.finalize_want == 0)
wakeup(&hmp->flusher.finalize_want);
hammer_stats_commits += final;
}
void
hammer_flusher_flush_undos(hammer_mount_t hmp, int mode)
{
hammer_io_t io;
int count;
count = 0;
while ((io = RB_FIRST(hammer_mod_rb_tree, &hmp->undo_root)) != NULL) {
if (io->ioerror)
break;
hammer_ref(&io->lock);
KKASSERT(io->type != HAMMER_IOTYPE_VOLUME);
hammer_io_write_interlock(io);
hammer_io_flush(io, hammer_undo_reclaim(io));
hammer_io_done_interlock(io);
hammer_rel_buffer(HAMMER_ITOB(io), 0);
hammer_io_limit_backlog(hmp);
++count;
}
hammer_flusher_clean_loose_ios(hmp);
if (mode == HAMMER_FLUSH_UNDOS_FORCED ||
(mode == HAMMER_FLUSH_UNDOS_AUTO && count)) {
hammer_io_wait_all(hmp, "hmrfl1", 1);
} else {
hammer_io_wait_all(hmp, "hmrfl2", 0);
}
}
int
hammer_flusher_meta_limit(hammer_mount_t hmp)
{
if (hmp->locked_dirty_space + hmp->io_running_space >
hammer_limit_dirtybufspace) {
return(1);
}
return(0);
}
int
hammer_flusher_meta_halflimit(hammer_mount_t hmp)
{
if (hmp->locked_dirty_space + hmp->io_running_space >
hammer_limit_dirtybufspace / 2) {
return(1);
}
return(0);
}
int
hammer_flusher_haswork(hammer_mount_t hmp)
{
if (hmp->ronly)
return(0);
if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
return(0);
if (TAILQ_FIRST(&hmp->flush_group_list) ||
RB_ROOT(&hmp->volu_root) ||
RB_ROOT(&hmp->undo_root) ||
RB_ROOT(&hmp->data_root) ||
RB_ROOT(&hmp->meta_root) ||
(hmp->hflags & HMNT_UNDO_DIRTY)) {
return(1);
}
return(0);
}
int
hammer_flush_dirty(hammer_mount_t hmp, int max_count)
{
int count = 0;
int dummy;
while (hammer_flusher_haswork(hmp)) {
hammer_flusher_sync(hmp);
++count;
if (count >= 5) {
if (count == 5)
hkprintf("flushing.");
else
kprintf(".");
tsleep(&dummy, 0, "hmrufl", hz);
}
if (max_count != -1 && count == max_count) {
kprintf("giving up");
break;
}
}
if (count >= 5)
kprintf("\n");
if (count >= max_count)
return(-1);
return(0);
}