From a279333c0b4bdc99993b4ada12293ae396135a40 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 29 Dec 2015 15:20:45 +0100 Subject: [PATCH 01/34] Alternate scrub next block computation --- cmdline/scrub.c | 78 ++++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/cmdline/scrub.c b/cmdline/scrub.c index 3e9073a7..735d8459 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -39,6 +39,7 @@ struct snapraid_rehash { * Scrub plan to use. */ struct snapraid_plan { + struct snapraid_state* state; int plan; /**< One of the SCRUB_*. */ time_t timelimit; /**< Time limit. Valid only with SCRUB_AUTO. */ block_off_t lastlimit; /**< Number of blocks allowed with time exactly at ::timelimit. */ @@ -48,13 +49,13 @@ struct snapraid_plan { /** * Check if we have to process the specified block index ::i. */ -static int block_is_enabled(struct snapraid_state* state, block_off_t i, struct snapraid_plan* plan) +static int block_is_enabled(struct snapraid_plan* plan, block_off_t i) { time_t blocktime; snapraid_info info; /* don't scrub unused blocks in all plans */ - info = info_get(&state->infoarr, i); + info = info_get(&plan->state->infoarr, i); if (info == 0) return 0; @@ -105,7 +106,8 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par void* rehandle_alloc; struct snapraid_rehash* rehandle; unsigned diskmax; - block_off_t i; + block_off_t blockcur; + block_off_t blocknext; unsigned j; void* buffer_alloc; void** buffer; @@ -142,8 +144,8 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* first count the number of blocks to process */ countmax = 0; plan->countlast = 0; - for (i = blockstart; i < blockmax; ++i) { - if (!block_is_enabled(state, i, plan)) + for (blockcur = blockstart; blockcur < blockmax; ++blockcur) { + if (!block_is_enabled(plan, blockcur)) continue; ++countmax; @@ -163,7 +165,9 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par countpos = 0; plan->countlast = 0; state_progress_begin(state, blockstart, blockmax, countmax); - for (i = blockstart; i < blockmax; ++i) { + blocknext = blockstart; + + while (1) { snapraid_info info; int error_on_this_block; int silent_error_on_this_block; @@ -171,9 +175,16 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par int block_is_unsynced; int rehash; - if (!block_is_enabled(state, i, plan)) - continue; + /* go to the next block */ + blockcur = blocknext; + if (blockcur >= blockmax) + break; + /* find the next block */ + ++blocknext; + while (blocknext < blockmax && !block_is_enabled(plan, blocknext)) + ++blocknext; + /* one more block processed for autosave */ ++autosavedone; --autosavemissing; @@ -188,7 +199,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par block_is_unsynced = 0; /* get block specific info */ - info = info_get(&state->infoarr, i); + info = info_get(&state->infoarr, blockcur); /* if we have to use the old hash */ rehash = info_get_rehash(info); @@ -218,7 +229,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par } /* if the block is not used */ - block = fs_par2block_get(disk, i); + block = fs_par2block_get(disk, blockcur); if (!block_has_file(block)) { /* use an empty block */ memset(buffer[j], 0, state->block_size); @@ -226,7 +237,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par } /* get the file of this block */ - file = fs_par2file_get(disk, i, &file_pos); + file = fs_par2file_get(disk, blockcur, &file_pos); /* if the block is unsynced, errors are expected */ if (block_has_invalid_parity(block)) { @@ -249,18 +260,18 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* This one is really an unexpected error, because we are only reading */ /* and closing a descriptor should never fail */ if (errno == EIO) { - log_tag("error:%u:%s:%s: Close EIO error. %s\n", i, disk->name, esc(report->sub), strerror(errno)); + log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to scrub.\n"); log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++io_error; goto bail; } - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(report->sub), strerror(errno)); + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to scrub.\n"); log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++error; goto bail; /* LCOV_EXCL_STOP */ @@ -271,16 +282,16 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par if (ret == -1) { if (errno == EIO) { /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Open EIO error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); + log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno)); log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to scrub.\n"); log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++io_error; goto bail; /* LCOV_EXCL_STOP */ } - log_tag("error:%u:%s:%s: Open error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); + log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno)); ++error; error_on_this_block = 1; continue; @@ -305,12 +316,12 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par read_size = handle_read(&handle[j], file_pos, buffer[j], state->block_size, log_error, 0); if (read_size == -1) { if (errno == EIO) { - log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno)); + log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(file->sub), file_pos, strerror(errno)); if (io_error >= state->opt.io_error_limit) { /* LCOV_EXCL_START */ log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n"); log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++io_error; goto bail; /* LCOV_EXCL_STOP */ @@ -322,7 +333,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par continue; } - log_tag("error:%u:%s:%s: Read error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno)); + log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(file->sub), file_pos, strerror(errno)); ++error; error_on_this_block = 1; continue; @@ -349,7 +360,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par if (memcmp(hash, block->hash, HASH_SIZE) != 0) { unsigned diff = memdiff(hash, block->hash, HASH_SIZE); - log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", i, disk->name, esc(file->sub), file_pos, diff); + log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", blockcur, disk->name, esc(file->sub), file_pos, diff); /* it's a silent error only if we are dealing with synced files */ if (file_is_unsynced) { @@ -380,29 +391,29 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* read the parity */ for (l = 0; l < state->level; ++l) { - ret = parity_read(parity[l], i, buffer_recov[l], state->block_size, log_error); + ret = parity_read(parity[l], blockcur, buffer_recov[l], state->block_size, log_error); if (ret == -1) { buffer_recov[l] = 0; if (errno == EIO) { - log_tag("parity_error:%u:%s: Read EIO error. %s\n", i, lev_config_name(l), strerror(errno)); + log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(l), strerror(errno)); if (io_error >= state->opt.io_error_limit) { /* LCOV_EXCL_START */ log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(l)); log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++io_error; goto bail; /* LCOV_EXCL_STOP */ } - log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), i); + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), blockcur); ++io_error; io_error_on_this_block = 1; continue; } - log_tag("parity_error:%u:%s: Read error. %s\n", i, lev_config_name(l), strerror(errno)); + log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(l), strerror(errno)); ++error; error_on_this_block = 1; continue; @@ -420,14 +431,14 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par if (buffer_recov[l] && memcmp(buffer[diskmax + l], buffer_recov[l], state->block_size) != 0) { unsigned diff = memdiff(buffer[diskmax + l], buffer_recov[l], state->block_size); - log_tag("parity_error:%u:%s: Data error, diff bits %u\n", i, lev_config_name(l), diff); + log_tag("parity_error:%u:%s: Data error, diff bits %u\n", blockcur, lev_config_name(l), diff); /* it's a silent error only if we are dealing with synced blocks */ if (block_is_unsynced) { ++error; error_on_this_block = 1; } else { - log_fatal("Data error in parity '%s' at position '%u', diff bits %u\n", lev_config_name(l), i, diff); + log_fatal("Data error in parity '%s' at position '%u', diff bits %u\n", lev_config_name(l), blockcur, diff); ++silent_error; silent_error_on_this_block = 1; } @@ -437,7 +448,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par if (silent_error_on_this_block || io_error_on_this_block) { /* set the error status keeping other info */ - info_set(&state->infoarr, i, info_set_bad(info)); + info_set(&state->infoarr, blockcur, info_set_bad(info)); } else if (error_on_this_block) { /* do nothing, as this is a generic error */ /* likely caused by a not synced array */ @@ -453,7 +464,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* update the time info of the block */ /* and clear any other flag */ - info_set(&state->infoarr, i, info_make(now, 0, 0, 0)); + info_set(&state->infoarr, blockcur, info_make(now, 0, 0, 0)); } /* mark the state as needing write */ @@ -463,7 +474,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par ++countpos; /* progress */ - if (state_progress(state, i, countpos, countmax, countsize)) { + if (state_progress(state, blockcur, countpos, countmax, countsize)) { /* LCOV_EXCL_START */ break; /* LCOV_EXCL_STOP */ @@ -533,7 +544,7 @@ bail: ret = handle_close(&handle[j]); if (ret == -1) { /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno)); log_fatal("DANGER! Unexpected close error in a data disk.\n"); ++error; /* continue, as we are already exiting */ @@ -605,6 +616,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan) countlimit = 0; recentlimit = 0; + ps.state = state; if (state->opt.force_scrub_even) { ps.plan = SCRUB_EVEN; } else if (plan == SCRUB_FULL) { From 3c00248aa6a385b7f173cf9b871c467d48ef527d Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Wed, 30 Dec 2015 14:22:43 +0100 Subject: [PATCH 02/34] Add level knownledge to parity handle --- cmdline/check.c | 4 ++-- cmdline/dry.c | 2 +- cmdline/parity.c | 6 ++++-- cmdline/parity.h | 5 +++-- cmdline/scrub.c | 2 +- cmdline/sync.c | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/cmdline/check.c b/cmdline/check.c index 55a35eb5..89c0fbe1 100644 --- a/cmdline/check.c +++ b/cmdline/check.c @@ -1942,7 +1942,7 @@ int state_check(struct snapraid_state* state, int fix, block_off_t blockstart, b } parity_ptr[l] = &parity[l]; - ret = parity_create(parity_ptr[l], state->parity[l].path, &out_size, state->file_mode); + ret = parity_create(parity_ptr[l], l, state->parity[l].path, &out_size, state->file_mode); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("WARNING! Without an accessible %s file, it isn't possible to fix any error.\n", lev_name(l)); @@ -1963,7 +1963,7 @@ int state_check(struct snapraid_state* state, int fix, block_off_t blockstart, b /* it may fail if the file doesn't exist, in this case we continue to check the files */ for (l = 0; l < state->level; ++l) { parity_ptr[l] = &parity[l]; - ret = parity_open(parity_ptr[l], state->parity[l].path, state->file_mode); + ret = parity_open(parity_ptr[l], l, state->parity[l].path, state->file_mode); if (ret == -1) { msg_status("No accessible %s file, only files will be checked.\n", lev_name(l)); /* continue anyway */ diff --git a/cmdline/dry.c b/cmdline/dry.c index c9a460d2..812e9af0 100644 --- a/cmdline/dry.c +++ b/cmdline/dry.c @@ -219,7 +219,7 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t /* it may fail if the file doesn't exist, in this case we continue to dry the files */ for (l = 0; l < state->level; ++l) { parity_ptr[l] = &parity[l]; - ret = parity_open(parity_ptr[l], state->parity[l].path, state->file_mode); + ret = parity_open(parity_ptr[l], l, state->parity[l].path, state->file_mode); if (ret == -1) { msg_status("No accessible %s file.\n", lev_name(l)); /* continue anyway */ diff --git a/cmdline/parity.c b/cmdline/parity.c index f62a3f41..400f0601 100644 --- a/cmdline/parity.c +++ b/cmdline/parity.c @@ -149,11 +149,12 @@ void parity_overflow(struct snapraid_state* state, data_off_t size) } } -int parity_create(struct snapraid_parity_handle* parity, const char* path, data_off_t* out_size, int mode) +int parity_create(struct snapraid_parity_handle* parity, unsigned level, const char* path, data_off_t* out_size, int mode) { int ret; int flags; + parity->level = level; pathcpy(parity->path, sizeof(parity->path), path); /* opening in sequential mode in Windows */ @@ -323,11 +324,12 @@ bail: /* LCOV_EXCL_STOP */ } -int parity_open(struct snapraid_parity_handle* parity, const char* path, int mode) +int parity_open(struct snapraid_parity_handle* parity, unsigned level, const char* path, int mode) { int ret; int flags; + parity->level = level; pathcpy(parity->path, sizeof(parity->path), path); /* open for read */ diff --git a/cmdline/parity.h b/cmdline/parity.h index 066ff3d7..f63376bb 100644 --- a/cmdline/parity.h +++ b/cmdline/parity.h @@ -25,6 +25,7 @@ struct snapraid_parity_handle { char path[PATH_MAX]; /**< Path of the file. */ + unsigned level; /**< Level of the parity. */ int f; /**< Handle of the file. */ struct stat st; /**< Stat info of the opened file. */ data_off_t valid_size; /**< Size of the valid data. */ @@ -74,7 +75,7 @@ void parity_overflow(struct snapraid_state* state, data_off_t size); * Create the parity file. * \param out_size Return the size of the parity file. */ -int parity_create(struct snapraid_parity_handle* parity, const char* path, data_off_t* out_size, int mode); +int parity_create(struct snapraid_parity_handle* parity, unsigned level, const char* path, data_off_t* out_size, int mode); /** * Change the parity size. @@ -85,7 +86,7 @@ int parity_chsize(struct snapraid_parity_handle* parity, data_off_t size, data_o /** * Open an already existing parity file. */ -int parity_open(struct snapraid_parity_handle* parity, const char* path, int mode); +int parity_open(struct snapraid_parity_handle* parity, unsigned level, const char* path, int mode); /** * Flush the parity file in the disk. diff --git a/cmdline/scrub.c b/cmdline/scrub.c index 735d8459..fe10cdd2 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -723,7 +723,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan) /* open the file for reading */ for (l = 0; l < state->level; ++l) { parity_ptr[l] = &parity[l]; - ret = parity_open(parity_ptr[l], state->parity[l].path, state->file_mode); + ret = parity_open(parity_ptr[l], l, state->parity[l].path, state->file_mode); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("WARNING! Without an accessible %s file, it isn't possible to scrub.\n", lev_name(l)); diff --git a/cmdline/sync.c b/cmdline/sync.c index e0a2aafe..d6712732 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -1258,7 +1258,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t /* create the file and open for writing */ parity_ptr[l] = &parity[l]; - ret = parity_create(parity_ptr[l], state->parity[l].path, &out_size, state->file_mode); + ret = parity_create(parity_ptr[l], l, state->parity[l].path, &out_size, state->file_mode); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("WARNING! Without an accessible %s file, it isn't possible to sync.\n", lev_name(l)); From 51af8931b715f61b1cbbb570fd3a8d49fd1c0138 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Wed, 30 Dec 2015 11:08:20 +0100 Subject: [PATCH 03/34] Add support for multithread data and parity reading in scrub --- Makefile.am | 2 + cmdline/io.c | 396 +++++++++++++++++++++++++++++++++++++++++ cmdline/io.h | 281 +++++++++++++++++++++++++++++ cmdline/scrub.c | 460 ++++++++++++++++++++++++++++++------------------ 4 files changed, 967 insertions(+), 172 deletions(-) create mode 100644 cmdline/io.c create mode 100644 cmdline/io.h diff --git a/Makefile.am b/Makefile.am index 389b54cc..d5f253b5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,6 +16,7 @@ snapraid_SOURCES = \ raid/tag.c \ tommyds/tommy.c \ cmdline/snapraid.c \ + cmdline/io.c \ cmdline/util.c \ cmdline/stream.c \ cmdline/support.c \ @@ -70,6 +71,7 @@ noinst_HEADERS = \ tommyds/tommytree.c \ cmdline/portable.h \ cmdline/snapraid.h \ + cmdline/io.h \ cmdline/util.h \ cmdline/stream.h \ cmdline/support.h \ diff --git a/cmdline/io.c b/cmdline/io.c new file mode 100644 index 00000000..c29b58c7 --- /dev/null +++ b/cmdline/io.c @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2016 Andrea Mazzoleni + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "portable.h" + +#include "io.h" + +void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buffer_max, + void (*data_reader)(struct snapraid_worker*, struct snapraid_task*), + struct snapraid_handle* handle_map, unsigned handle_max, + void (*parity_reader)(struct snapraid_worker*, struct snapraid_task*), + struct snapraid_parity_handle* parity_handle_map, unsigned parity_handle_max) +{ + unsigned i; + + pthread_mutex_init(&io->mutex, 0); + pthread_cond_init(&io->not_empty, 0); + pthread_cond_init(&io->not_full, 0); + + io->state = state; + + io->buffer_max = buffer_max; + for (i = 0; i < IO_MAX; ++i) { + io->buffer_map[i] = malloc_nofail_vector_align(handle_max, buffer_max, state->block_size, &io->buffer_alloc_map[i]); + if (!state->opt.skip_self) + mtest_vector(io->buffer_max, state->block_size, io->buffer_map[i]); + } + + io->worker_max = handle_max + parity_handle_max; + io->worker_map = malloc_nofail(sizeof(struct snapraid_worker) * io->worker_max); + io->worker_list = malloc_nofail(io->worker_max + 1); + + io->data_base = 0; + io->data_count = handle_max; + io->parity_base = handle_max; + io->parity_count = parity_handle_max; + + for (i = 0; i < io->worker_max; ++i) { + struct snapraid_worker* worker = &io->worker_map[i]; + + worker->io = io; + worker->fs_last = 0; + + if (i < handle_max) { + worker->handle = &handle_map[i]; + worker->parity_handle = 0; + worker->func = data_reader; + + /* data read is put in lower buffer index */ + worker->buffer_skew = 0; + } else { + worker->handle = 0; + worker->parity_handle = &parity_handle_map[i - handle_max]; + worker->func = parity_reader; + + /* parity read is put after data and computed parity */ + worker->buffer_skew = parity_handle_max; + } + } +} + +void io_done(struct snapraid_io* io) +{ + unsigned i; + + for (i = 0; i < IO_MAX; ++i) { + free(io->buffer_map[i]); + free(io->buffer_alloc_map[i]); + } + + free(io->worker_map); + free(io->worker_list); + + pthread_mutex_destroy(&io->mutex); + pthread_cond_destroy(&io->not_empty); + pthread_cond_destroy(&io->not_full); +} + +/** + * Get the next block position to process. + */ +static block_off_t io_position_next(struct snapraid_io* io) +{ + block_off_t blockcur; + + /* get the next position */ + while (io->block_next < io->block_max && !io->block_is_enabled(io->block_arg, io->block_next)) + ++io->block_next; + + blockcur = io->block_next; + + /* next block for the next call */ + ++io->block_next; + + return blockcur; +} + +/** + * Setup the next pending task for all workers. + */ +static void io_pending_next(struct snapraid_io* io, int index, block_off_t blockcur) +{ + unsigned i; + + for (i = 0; i < io->worker_max; ++i) { + struct snapraid_worker* worker = &io->worker_map[i]; + struct snapraid_task* task = &worker->task_map[index]; + + /* setup the new pending task */ + if (blockcur < io->block_max) + task->state = TASK_STATE_READY; + else + task->state = TASK_STATE_EMPTY; + + task->path[0] = 0; + if (worker->handle) + task->disk = worker->handle->disk; + else + task->disk = 0; + task->buffer = io->buffer_map[index][worker->buffer_skew + i]; + task->position = blockcur; + task->block = 0; + task->file = 0; + task->file_pos = 0; + task->read_size = 0; + task->is_timestamp_different = 0; + } +} + +/** + * Get the next task to work on for a worker. + * + * This is the synchronization point for workers with the io. + */ +static struct snapraid_task* io_worker_next(struct snapraid_worker* worker) +{ + struct snapraid_io* io = worker->io; + struct snapraid_task* task = 0; + + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + while (1) { + unsigned index; + + /* check if the worker has to exit */ + if (io->done) + break; + + /* get the next pending task */ + index = (worker->index + 1) % IO_MAX; + + /* if the queue of pending tasks is not empty */ + if (index != io->index) { + /* get the new working task */ + worker->index = index; + task = &worker->task_map[worker->index]; + + /* notify the io that we started working */ + pthread_cond_signal(&io->not_empty); + + /* return the new task */ + break; + } + + /* otherwise wait for a not_full event */ + pthread_cond_wait(&io->not_full, &io->mutex); + } + + pthread_mutex_unlock(&io->mutex); + + return task; +} + +/** + * Get the next block position to operate on. + * + * This is the synchronization point for workers with the io. + */ +block_off_t io_next(struct snapraid_io* io, void*** buffer) +{ + block_off_t blockcur_schedule; + block_off_t blockcur_caller; + unsigned i; + + blockcur_schedule = io_position_next(io); + + /* setup the list of workers to process */ + for (i = 0; i <= io->worker_max; ++i) + io->worker_list[i] = i; + + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + /* reset the task just used to be the next pending task */ + io_pending_next(io, io->index, blockcur_schedule); + + /* get the next task to return to the caller */ + io->index = (io->index + 1) % IO_MAX; + + /* get the position to operate at high level from one task */ + blockcur_caller = io->worker_map[0].task_map[io->index].position; + + /* set the buffer to use */ + *buffer = io->buffer_map[io->index]; + + /* signal all the workers that there is a new pending task */ + pthread_cond_broadcast(&io->not_full); + + pthread_mutex_unlock(&io->mutex); + + return blockcur_caller; +} + +static struct snapraid_task* io_task_next(struct snapraid_io* io, unsigned base, unsigned count, unsigned* pos) +{ + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + while (1) { + unsigned char* let; + + /* search for a worker that has already finished */ + let = &io->worker_list[0]; + while (1) { + unsigned i = *let; + + /* if we are at the end */ + if (i == io->worker_max) + break; + + /* if it's in range */ + if (base <= i && i < base + count) { + struct snapraid_worker* worker; + + worker = &io->worker_map[i]; + + /* if the worker has finished this index */ + if (io->index != worker->index) { + struct snapraid_task* task; + + task = &worker->task_map[io->index]; + + pthread_mutex_unlock(&io->mutex); + + /* mark the worker as processed */ + /* setting the previous one to point at the next one */ + *let = io->worker_list[i + 1]; + + /* return the position */ + *pos = i - base; + + return task; + } + } + + /* next position to check */ + let = &io->worker_list[i + 1]; + } + + /* if not worker is ready, wait for an event */ + pthread_cond_wait(&io->not_empty, &io->mutex); + } +} + +struct snapraid_task* io_data_next(struct snapraid_io* io, unsigned* pos) +{ + return io_task_next(io, io->data_base, io->data_count, pos); +} + +struct snapraid_task* io_parity_next(struct snapraid_io* io, unsigned* pos) +{ + return io_task_next(io, io->parity_base, io->parity_count, pos); +} + +static void io_worker(struct snapraid_worker* worker, struct snapraid_task* task) +{ + /* if we reached the end */ + if (task->position >= worker->io->block_max) { + task->state = TASK_STATE_EMPTY; + } else { + worker->func(worker, task); + } +} + +static void* io_thread(void* arg) +{ + struct snapraid_worker* worker = arg; + + /* force completion of the first task */ + io_worker(worker, &worker->task_map[0]); + + while (1) { + struct snapraid_task* task; + + /* get the new task */ + task = io_worker_next(worker); + + /* if no task, it means to exit */ + if (!task) + break; + + /* nothing more to do */ + if (task->state == TASK_STATE_EMPTY) + continue; + + assert(task->state == TASK_STATE_READY); + + /* work on the assigned task */ + io_worker(worker, task); + } + + return 0; +} + +void io_start(struct snapraid_io* io, + block_off_t blockstart, block_off_t blockmax, + int (*block_is_enabled)(void* arg,block_off_t), void* blockarg) +{ + unsigned i; + + io->block_start = blockstart; + io->block_max = blockmax; + io->block_is_enabled = block_is_enabled; + io->block_arg = blockarg; + io->block_next = blockstart; + + io->done = 0; + io->index = IO_MAX - 1; + + /* setup the initial pending tasks, except the latest one, */ + /* the latest will be initialized at the fist io_next() call */ + for (i = 0; i < IO_MAX - 1; ++i) { + block_off_t blockcur = io_position_next(io); + + io_pending_next(io, i, blockcur); + } + + /* start the worker threads */ + for (i = 0; i < io->worker_max; ++i) { + struct snapraid_worker* worker = &io->worker_map[i]; + + worker->index = 0; + + if (pthread_create(&worker->thread, 0, io_thread, worker) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to create thread.\n"); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } +} + +void io_stop(struct snapraid_io* io) +{ + unsigned i; + + pthread_mutex_lock(&io->mutex); + + /* mark that we are stopping */ + io->done = 1; + + /* signal all the threads to recognize the new state */ + pthread_cond_broadcast(&io->not_full); + + pthread_mutex_unlock(&io->mutex); + + /* wait for all threads to terminate */ + for (i = 0; i < io->worker_max; ++i) { + struct snapraid_worker* worker = &io->worker_map[i]; + void* retval; + + /* wait for thread termination */ + if (pthread_join(worker->thread, &retval) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to join thread.\n"); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } +} + diff --git a/cmdline/io.h b/cmdline/io.h new file mode 100644 index 00000000..335bad9e --- /dev/null +++ b/cmdline/io.h @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2016 Andrea Mazzoleni + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __IO_H +#define __IO_H + +#include "state.h" +#include "support.h" +#include "handle.h" +#include "parity.h" + +/** + * Number of read-ahead buffers. + */ +#define IO_MAX 4 + +/** + * State of the task. + */ +#define TASK_STATE_IOERROR_CONTINUE -4 /**< IO error. Continuation requested. */ +#define TASK_STATE_ERROR_CONTINUE -3 /**< Generic error. Continuation requested. */ +#define TASK_STATE_IOERROR -2 /**< IO error. Failure requested. */ +#define TASK_STATE_ERROR -1 /**< Generic error. Failure requested. */ +#define TASK_STATE_EMPTY 0 /**< Nothing to do. */ +#define TASK_STATE_READY 1 /**< Ready to start. */ +#define TASK_STATE_DONE 2 /**< Task completed. */ + +/** + * Task of work. + * + * This represents the minimal element of work that worker threads are + * going to be asked to do. + * + * It consists in reading a block of data from a disk. + * + * Note that the disk to use is defined implicitly in the worker thread. + */ +struct snapraid_task { + int state; /**< State of the task. One of the TASK_STATE_*. */ + char path[PATH_MAX]; /**< Path of the file. */ + struct snapraid_disk* disk; /**< Disk of the file. */ + unsigned char* buffer; /**< Where to read the data. */ + block_off_t position; /**< Parity position to read. */ + + /** + * Result of the task. + */ + struct snapraid_block* block; + struct snapraid_file* file; + block_off_t file_pos; + int read_size; /**< Size of the data read. */ + int is_timestamp_different; /**< Report if file has a changed timestamp. */ +}; + +/** + * Worker for tasks. + * + * This represents a worker thread designated to read data + * from a specific disk. + */ +struct snapraid_worker { + pthread_t thread; + + struct snapraid_io* io; /**< Parent pointer. */ + + void (*func)(struct snapraid_worker*, struct snapraid_task*); + + /** + * Handle to data or parity. + * + * Only one of the two is valid, the other is 0. + */ + struct snapraid_handle* handle; /**< Handle at the file on the disk. */ + struct snapraid_parity_handle* parity_handle; /**< Handle at the parity on the disk. */ + + /** + * Vector of tasks. + * + * It's a ring of tasks reused cycle after cycle. + */ + struct snapraid_task task_map[IO_MAX]; + + /** + * The task in progress by the worker thread. + * + * It's an index inside in the ::task_map vector. + */ + unsigned index; + + /** + * Which buffer base index should be used for destination. + */ + unsigned buffer_skew; + + /** + * Last chunk we accessed. + * + * It's used to optimize access of sequential blocks. + * We cannot use the one in ::disk because it's used + * by the main thread. + */ + struct snapraid_chunk* fs_last; +}; + +/** + * Reader. + * + * This represents the pool of worker threads dedicated to read + * data from the disks. + */ +struct snapraid_io { + /** + * Mutex used to protect the synchronization + * between the io and the workers. + */ + pthread_mutex_t mutex; + + /** + * Condition for (not) empty queue of tasks. + * + * The io waits on this condition when the queue is empty, + * and it's signaled by workers. + */ + pthread_cond_t not_empty; + + /** + * Condition for (not) full queue of tasks. + * + * The workers wait on this condition when the queue is full, + * and it's signaled by the io. + */ + pthread_cond_t not_full; + + struct snapraid_state* state; + + /** + * Base position for workers. + * + * It's the index in the ::worker_map[]. + */ + unsigned data_base; + unsigned data_count; + unsigned parity_base; + unsigned parity_count; + + /** + * Callbacks for workers. + */ + void (*data_reader)(struct snapraid_worker*, struct snapraid_task*); + void (*parity_reader)(struct snapraid_worker*, struct snapraid_task*); + + /** + * Blocks mapping. + * + * This info is used to obtain the sequence of block + * positions to process. + */ + block_off_t block_start; + block_off_t block_max; + block_off_t block_next; + int (*block_is_enabled)(void* arg,block_off_t); + void* block_arg; + + /** + * Buffers for data. + * + * A pool of buffers used to store the data read. + */ + unsigned buffer_max; /**< Number of buffers. */ + void* buffer_alloc_map[IO_MAX]; /**< Allocation map for buffers. */ + void** buffer_map[IO_MAX]; /**< Buffers for data. */ + + /** + * Workers. + * + * A vector of workers, each one representing a different thread. + */ + unsigned worker_max; /**< Number of workers. */ + struct snapraid_worker* worker_map; /**< Vector of workers. */ + + /** + * List of not yet processed workers. + * + * The list has ::worker_max + 1 elements. Each element contains + * the number of the worker to process. + * + * At initialization the list is filled with [0..worker_max]. + * To get the next element to process we use i = list[i + 1]. + * The end is when i == worker_max. + */ + unsigned char* worker_list; + + /** + * Exit condition for all threads. + */ + int done; + + /** + * The task currently used by the caller. + * + * It's a rolling counter, when reaching IO_MAX + * it goes again to 0. + */ + unsigned index; +}; + +/** + * Initialize the InputOutput workers. + * + * \param buffer_max The number of data/parity buffers to allocate. + */ +void io_init(struct snapraid_io* io, struct snapraid_state* state, + unsigned buffer_max, + void (*data_reader)(struct snapraid_worker*, struct snapraid_task*), + struct snapraid_handle* handle_map, unsigned handle_max, + void (*parity_reader)(struct snapraid_worker*, struct snapraid_task*), + struct snapraid_parity_handle* parity_handle_map, unsigned parity_handle_max); + +/** + * Deinitialize te InputOutput workers. + */ +void io_done(struct snapraid_io* io); + +/** + * Start all the worker threads. + */ +void io_start(struct snapraid_io* io, + block_off_t blockstart, block_off_t blockmax, + int (*block_is_enabled)(void* arg,block_off_t), void* blockarg); + +/** + * Stop all the worker threads. + */ +void io_stop(struct snapraid_io* io); + +/** + * Get the next parity position to process. + * + * \param io InputOutput context. + * \param buffer The data buffers to use for this position. + * \return The parity position. + */ +block_off_t io_next(struct snapraid_io* io, void*** buffer); + +/** + * Get the next data block to process. + * + * It must be called exactly ::handle_max times. + * + * \param io InputOutput context. + * \param pos The position of the data block in the ::handle_map vector. + * \return The completed task. + */ +struct snapraid_task* io_data_next(struct snapraid_io* io, unsigned* pos); + +/** + * Get the next parity block to process. + * + * It must be called exactly ::parity_handle_max times. + * + * \param io InputOutput context. + * \param pos The position of the parity block in the ::parity_handle_map vector. + * \return The completed task. + */ +struct snapraid_task* io_parity_next(struct snapraid_io* io, unsigned* pos); + +#endif diff --git a/cmdline/scrub.c b/cmdline/scrub.c index fe10cdd2..0e06d5f2 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -22,6 +22,7 @@ #include "state.h" #include "parity.h" #include "handle.h" +#include "io.h" #include "raid/raid.h" /****************************************************************************/ @@ -49,8 +50,9 @@ struct snapraid_plan { /** * Check if we have to process the specified block index ::i. */ -static int block_is_enabled(struct snapraid_plan* plan, block_off_t i) +static int block_is_enabled(void* void_plan, block_off_t i) { + struct snapraid_plan* plan = void_plan; time_t blocktime; snapraid_info info; @@ -100,17 +102,179 @@ static int block_is_enabled(struct snapraid_plan* plan, block_off_t i) return 1; } -static int state_scrub_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax, struct snapraid_plan* plan, time_t now) +static void scrub_data_reader(struct snapraid_worker* worker, struct snapraid_task* task) { + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_handle* handle = worker->handle; + struct snapraid_disk* disk = handle->disk; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* if the disk position is not used */ + if (!disk) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the block */ + task->block = fs_par2block_get_ts(disk, &worker->fs_last, blockcur); + + /* if the block is not used */ + if (!block_has_file(task->block)) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the file of this block */ + task->file = fs_par2file_get_ts(disk, &worker->fs_last, blockcur, &task->file_pos); + + /* if the file is different than the current one, close it */ + if (handle->file != 0 && handle->file != task->file) { + /* keep a pointer at the file we are going to close for error reporting */ + struct snapraid_file* report = handle->file; + ret = handle_close(handle); + if (ret == -1) { + /* LCOV_EXCL_START */ + /* This one is really an unexpected error, because we are only reading */ + /* and closing a descriptor should never fail */ + if (errno == EIO) { + log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + } + + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that file '%s' can be accessed.\n", handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + } + + ret = handle_open(handle, task->file, state->file_mode, log_error, 0); + if (ret == -1) { + if (errno == EIO) { + /* LCOV_EXCL_START */ + log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + /* LCOV_EXCL_STOP */ + } + + log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + /* check if the file is changed */ + if (handle->st.st_size != task->file->size + || handle->st.st_mtime != task->file->mtime_sec + || STAT_NSEC(&handle->st) != task->file->mtime_nsec + /* don't check the inode to support filesystem without persistent inodes */ + ) { + /* report that the block and the file are not synced */ + task->is_timestamp_different = 1; + /* follow */ + } + + /* note that we intentionally don't abort if the file has different attributes */ + /* from the last sync, as we are expected to return errors if running */ + /* in an unsynced array. This is just like the check command. */ + + task->read_size = handle_read(handle, task->file_pos, buffer, state->block_size, log_error, 0); + if (task->read_size == -1) { + if (errno == EIO) { + log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); +#if 0 + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + /* LCOV_EXCL_STOP */ + } +#endif + log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + /* store the path of the opened file */ + pathcpy(task->path, sizeof(task->path), handle->path); + + task->state = TASK_STATE_DONE; +} + +static void scrub_parity_reader(struct snapraid_worker* worker, struct snapraid_task* task) +{ + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_parity_handle* parity_handle = worker->parity_handle; + unsigned level = parity_handle->level; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* read the parity */ + ret = parity_read(parity_handle, blockcur, buffer, state->block_size, log_error); + if (ret == -1) { + if (errno == EIO) { + log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno)); +#if 0 + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(level)); + log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(level)); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + /* LCOV_EXCL_STOP */ + } +#endif + + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + task->state = TASK_STATE_DONE; +} + +static int state_scrub_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax, struct snapraid_plan* plan, time_t now) +{ + struct snapraid_io io; struct snapraid_handle* handle; void* rehandle_alloc; struct snapraid_rehash* rehandle; unsigned diskmax; block_off_t blockcur; - block_off_t blocknext; unsigned j; - void* buffer_alloc; - void** buffer; unsigned buffermax; data_off_t countsize; block_off_t countpos; @@ -133,9 +297,8 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* we need disk + 2 for each parity level buffers */ buffermax = diskmax + state->level * 2; - buffer = malloc_nofail_vector_align(diskmax, buffermax, state->block_size, &buffer_alloc); - if (!state->opt.skip_self) - mtest_vector(buffermax, state->block_size, buffer); + /* initialize the io threads */ + io_init(&io, state, buffermax, scrub_data_reader, handle, diskmax, scrub_parity_reader, parity_handle, state->level); error = 0; silent_error = 0; @@ -147,7 +310,6 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par for (blockcur = blockstart; blockcur < blockmax; ++blockcur) { if (!block_is_enabled(plan, blockcur)) continue; - ++countmax; } @@ -164,27 +326,26 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par countsize = 0; countpos = 0; plan->countlast = 0; - state_progress_begin(state, blockstart, blockmax, countmax); - blocknext = blockstart; + /* start all the worker threads */ + io_start(&io, blockstart, blockmax, &block_is_enabled, plan); + + state_progress_begin(state, blockstart, blockmax, countmax); while (1) { + unsigned char* buffer_recov[LEV_MAX]; snapraid_info info; int error_on_this_block; int silent_error_on_this_block; int io_error_on_this_block; int block_is_unsynced; int rehash; + void** buffer; /* go to the next block */ - blockcur = blocknext; + blockcur = io_next(&io, &buffer); if (blockcur >= blockmax) break; - /* find the next block */ - ++blocknext; - while (blocknext < blockmax && !block_is_enabled(plan, blocknext)) - ++blocknext; - /* one more block processed for autosave */ ++autosavedone; --autosavemissing; @@ -206,38 +367,42 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* for each disk, process the block */ for (j = 0; j < diskmax; ++j) { + struct snapraid_task* task; int read_size; unsigned char hash[HASH_SIZE]; struct snapraid_block* block; int file_is_unsynced; - struct snapraid_disk* disk = handle[j].disk; + struct snapraid_disk* disk; struct snapraid_file* file; block_off_t file_pos; + unsigned diskcur; /* if the file on this disk is synced */ /* if not, silent errors are assumed as expected error */ file_is_unsynced = 0; + /* until now is CPU */ + state_usage_cpu(state); + + /* get the next task */ + task = io_data_next(&io, &diskcur); + + /* get the task results */ + disk = task->disk; + block = task->block; + file = task->file; + file_pos = task->file_pos; + read_size = task->read_size; + /* by default no rehash in case of "continue" */ - rehandle[j].block = 0; + rehandle[diskcur].block = 0; /* if the disk position is not used */ - if (!disk) { - /* use an empty block */ - memset(buffer[j], 0, state->block_size); + if (!disk) continue; - } - /* if the block is not used */ - block = fs_par2block_get(disk, blockcur); - if (!block_has_file(block)) { - /* use an empty block */ - memset(buffer[j], 0, state->block_size); - continue; - } - - /* get the file of this block */ - file = fs_par2file_get(disk, blockcur, &file_pos); + /* until now is disk */ + state_usage_disk(state, disk); /* if the block is unsynced, errors are expected */ if (block_has_invalid_parity(block)) { @@ -247,112 +412,55 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* follow */ } - /* until now is CPU */ - state_usage_cpu(state); - - /* if the file is different than the current one, close it */ - if (handle[j].file != 0 && handle[j].file != file) { - /* keep a pointer at the file we are going to close for error reporting */ - struct snapraid_file* report = handle[j].file; - ret = handle_close(&handle[j]); - if (ret == -1) { - /* LCOV_EXCL_START */ - /* This one is really an unexpected error, because we are only reading */ - /* and closing a descriptor should never fail */ - if (errno == EIO) { - log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); - log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to scrub.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", blockcur); - ++io_error; - goto bail; - } - - log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); - log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to scrub.\n"); - log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path); - log_fatal("Stopping at block %u\n", blockcur); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } - } - - ret = handle_open(&handle[j], file, state->file_mode, log_error, 0); - if (ret == -1) { - if (errno == EIO) { - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno)); - log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to scrub.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", blockcur); - ++io_error; - goto bail; - /* LCOV_EXCL_STOP */ - } - - log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno)); - ++error; - error_on_this_block = 1; + /* if the block is not used */ + if (!block_has_file(block)) continue; - } - /* check if the file is changed */ - if (handle[j].st.st_size != file->size - || handle[j].st.st_mtime != file->mtime_sec - || STAT_NSEC(&handle[j].st) != file->mtime_nsec - /* don't check the inode to support file-system without persistent inodes */ - ) { + /* if the block is unsynced, errors are expected */ + if (task->is_timestamp_different) { /* report that the block and the file are not synced */ block_is_unsynced = 1; file_is_unsynced = 1; /* follow */ } - /* note that we intentionally don't abort if the file has different attributes */ - /* from the last sync, as we are expected to return errors if running */ - /* in an unsynced array. This is just like the check command. */ - - read_size = handle_read(&handle[j], file_pos, buffer[j], state->block_size, log_error, 0); - if (read_size == -1) { - if (errno == EIO) { - log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(file->sub), file_pos, strerror(errno)); - if (io_error >= state->opt.io_error_limit) { - /* LCOV_EXCL_START */ - log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", blockcur); - ++io_error; - goto bail; - /* LCOV_EXCL_STOP */ - } - - log_error("Input/Output error in file '%s' at position '%u'\n", handle[j].path, file_pos); - ++io_error; - io_error_on_this_block = 1; - continue; - } - - log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(file->sub), file_pos, strerror(errno)); + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; + } + if (task->state == TASK_STATE_ERROR) { + ++error; + goto bail; + } + if (task->state == TASK_STATE_ERROR_CONTINUE) { ++error; error_on_this_block = 1; continue; } - - /* until now is disk */ - state_usage_disk(state, disk); + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + io_error_on_this_block = 1; + continue; + } + if (task->state != TASK_STATE_DONE) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in task state\n"); + os_abort(); + /* LCOV_EXCL_STOP */ + } countsize += read_size; /* now compute the hash */ if (rehash) { - memhash(state->prevhash, state->prevhashseed, hash, buffer[j], read_size); + memhash(state->prevhash, state->prevhashseed, hash, buffer[diskcur], read_size); /* compute the new hash, and store it */ - rehandle[j].block = block; - memhash(state->hash, state->hashseed, rehandle[j].hash, buffer[j], read_size); + rehandle[diskcur].block = block; + memhash(state->hash, state->hashseed, rehandle[diskcur].hash, buffer[diskcur], read_size); } else { - memhash(state->hash, state->hashseed, hash, buffer[j], read_size); + memhash(state->hash, state->hashseed, hash, buffer[diskcur], read_size); } if (block_has_updated_hash(block)) { @@ -367,7 +475,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par ++error; error_on_this_block = 1; } else { - log_error("Data error in file '%s' at position '%u', diff bits %u\n", handle[j].path, file_pos, diff); + log_error("Data error in file '%s' at position '%u', diff bits %u\n", task->path, file_pos, diff); ++silent_error; silent_error_on_this_block = 1; } @@ -376,52 +484,60 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par } } + /* buffers for parity read and not computed */ + for (l = 0; l < state->level; ++l) + buffer_recov[l] = buffer[diskmax + state->level + l]; + for (; l < LEV_MAX; ++l) + buffer_recov[l] = 0; + + /* until now is CPU */ + state_usage_cpu(state); + + /* read the parity */ + for (l = 0; l < state->level; ++l) { + struct snapraid_task* task; + unsigned levcur; + + task = io_parity_next(&io, &levcur); + + /* until now is parity */ + state_usage_parity(state, levcur); + + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; + } + if (task->state == TASK_STATE_ERROR) { + ++error; + goto bail; + } + if (task->state == TASK_STATE_ERROR_CONTINUE) { + ++error; + error_on_this_block = 1; + + /* if continuing on error, clear the missing buffer */ + buffer_recov[levcur] = 0; + continue; + } + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + io_error_on_this_block = 1; + + /* if continuing on error, clear the missing buffer */ + buffer_recov[levcur] = 0; + continue; + } + if (task->state != TASK_STATE_DONE) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in task state\n"); + os_abort(); + /* LCOV_EXCL_STOP */ + } + } + /* if we have read all the data required and it's correct, proceed with the parity check */ if (!error_on_this_block && !silent_error_on_this_block && !io_error_on_this_block) { - unsigned char* buffer_recov[LEV_MAX]; - - /* until now is CPU */ - state_usage_cpu(state); - - /* buffers for parity read and not computed */ - for (l = 0; l < state->level; ++l) - buffer_recov[l] = buffer[diskmax + state->level + l]; - for (; l < LEV_MAX; ++l) - buffer_recov[l] = 0; - - /* read the parity */ - for (l = 0; l < state->level; ++l) { - ret = parity_read(parity[l], blockcur, buffer_recov[l], state->block_size, log_error); - if (ret == -1) { - buffer_recov[l] = 0; - - if (errno == EIO) { - log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(l), strerror(errno)); - if (io_error >= state->opt.io_error_limit) { - /* LCOV_EXCL_START */ - log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(l)); - log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", blockcur); - ++io_error; - goto bail; - /* LCOV_EXCL_STOP */ - } - - log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), blockcur); - ++io_error; - io_error_on_this_block = 1; - continue; - } - - log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(l), strerror(errno)); - ++error; - error_on_this_block = 1; - continue; - } - - /* until now is parity */ - state_usage_parity(state, l); - } /* compute the parity */ raid_gen(diskmax, state->level, state->block_size, buffer); @@ -538,6 +654,9 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par log_flush(); bail: + /* stop all the worker threads */ + io_stop(&io); + for (j = 0; j < diskmax; ++j) { struct snapraid_file* file = handle[j].file; struct snapraid_disk* disk = handle[j].disk; @@ -553,9 +672,8 @@ bail: } free(handle); - free(buffer_alloc); - free(buffer); free(rehandle_alloc); + io_done(&io); if (state->opt.expect_recoverable) { if (error + silent_error + io_error == 0) @@ -589,8 +707,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan) block_off_t count; time_t recentlimit; int ret; - struct snapraid_parity_handle parity[LEV_MAX]; - struct snapraid_parity_handle* parity_ptr[LEV_MAX]; + struct snapraid_parity_handle parity_handle[LEV_MAX]; struct snapraid_plan ps; time_t* timemap; unsigned error; @@ -722,8 +839,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan) /* open the file for reading */ for (l = 0; l < state->level; ++l) { - parity_ptr[l] = &parity[l]; - ret = parity_open(parity_ptr[l], l, state->parity[l].path, state->file_mode); + ret = parity_open(&parity_handle[l], l, state->parity[l].path, state->file_mode); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("WARNING! Without an accessible %s file, it isn't possible to scrub.\n", lev_name(l)); @@ -736,14 +852,14 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan) error = 0; - ret = state_scrub_process(state, parity_ptr, 0, blockmax, &ps, now); + ret = state_scrub_process(state, parity_handle, 0, blockmax, &ps, now); if (ret == -1) { ++error; /* continue, as we are already exiting */ } for (l = 0; l < state->level; ++l) { - ret = parity_close(parity_ptr[l]); + ret = parity_close(&parity_handle[l]); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("DANGER! Unexpected close error in %s disk.\n", lev_name(l)); From 74204a42d75da9a355ec61de176d44ea0ba36f0b Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Wed, 30 Dec 2015 23:45:11 +0100 Subject: [PATCH 04/34] Add support for multithread data reading in sync --- cmdline/sync.c | 493 ++++++++++++++++++++++++++++++------------------- 1 file changed, 299 insertions(+), 194 deletions(-) diff --git a/cmdline/sync.c b/cmdline/sync.c index d6712732..b4840ee2 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -22,11 +22,20 @@ #include "state.h" #include "parity.h" #include "handle.h" +#include "io.h" #include "raid/raid.h" /****************************************************************************/ /* hash */ +/** + * Sync plan to use. + */ +struct snapraid_plan { + unsigned handle_max; + struct snapraid_handle* handle_map; +}; + static int state_hash_process(struct snapraid_state* state, block_off_t blockstart, block_off_t blockmax, int* skip_sync) { struct snapraid_handle* handle; @@ -393,6 +402,21 @@ struct failed_struct { struct snapraid_block* block; /**< The failed block, or BLOCK_DELETED for a deleted block */ }; +/** + * Comparison function for sorting by index. + */ +int failed_compare_by_index(const void* void_a, const void* void_b) +{ + const struct failed_struct* a = void_a; + const struct failed_struct* b = void_b; + + if (a->index < b->index) + return -1; + if (a->index > b->index) + return 1; + return 0; +} + /** * Buffer for storing the new hashes. */ @@ -404,8 +428,9 @@ struct snapraid_rehash { /** * Check if we have to process the specified block index ::i. */ -static int block_is_enabled(block_off_t i, struct snapraid_handle* handle, unsigned diskmax) +static int block_is_enabled(void* void_plan, block_off_t i) { + struct snapraid_plan* plan = void_plan; unsigned j; int one_invalid; int one_valid; @@ -413,14 +438,15 @@ static int block_is_enabled(block_off_t i, struct snapraid_handle* handle, unsig /* for each disk */ one_invalid = 0; one_valid = 0; - for (j = 0; j < diskmax; ++j) { + for (j = 0; j < plan->handle_max; ++j) { struct snapraid_block* block; + struct snapraid_disk* disk = plan->handle_map[j].disk; /* if no disk, nothing to check */ - if (!handle[j].disk) + if (!disk) continue; - block = fs_par2block_get(handle[j].disk, i); + block = fs_par2block_get(disk, i); if (block_has_file(block)) one_valid = 1; @@ -436,16 +462,180 @@ static int block_is_enabled(block_off_t i, struct snapraid_handle* handle, unsig return 1; } +static void sync_data_reader(struct snapraid_worker* worker, struct snapraid_task* task) +{ + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_handle* handle = worker->handle; + struct snapraid_disk* disk = handle->disk; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* if the disk position is not used */ + if (!disk) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the block */ + task->block = fs_par2block_get_ts(disk, &worker->fs_last, blockcur); + + /* if the block has no file, meanining that it's EMPTY or DELETED, */ + /* it doesn't partecipate in the new parity computation */ + if (!block_has_file(task->block)) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the file of this block */ + task->file = fs_par2file_get_ts(disk, &worker->fs_last, blockcur, &task->file_pos); + + /* if the file is different than the current one, close it */ + if (handle->file != 0 && handle->file != task->file) { + /* keep a pointer at the file we are going to close for error reporting */ + struct snapraid_file* report = handle->file; + ret = handle_close(handle); + if (ret == -1) { + /* LCOV_EXCL_START */ + /* This one is really an unexpected error, because we are only reading */ + /* and closing a descriptor should never fail */ + if (errno == EIO) { + log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + } + + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that file '%s' can be accessed.\n", handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + } + + ret = handle_open(handle, task->file, state->file_mode, log_error, 0); + if (ret == -1) { + if (errno == EIO) { + /* LCOV_EXCL_START */ + log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + /* LCOV_EXCL_STOP */ + } + + if (errno == ENOENT) { + log_tag("error:%u:%s:%s: Open ENOENT error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_error("Missing file '%s'.\n", handle->path); + log_error("WARNING! You cannot modify data disk during a sync.\n"); + log_error("Rerun the sync command when finished.\n"); + /* if the file is missing, it means that it was removed during sync */ + /* this isn't a serious error, so we skip this block, and continue with others */ + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + if (errno == EACCES) { + log_tag("error:%u:%s:%s: Open EACCES error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_error("No access at file '%s'.\n", handle->path); + log_error("WARNING! Please fix the access permission in the data disk.\n"); + log_error("Rerun the sync command when finished.\n"); + /* this isn't a serious error, so we skip this block, and continue with others */ + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + /* LCOV_EXCL_START */ + log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_fatal("WARNING! Unexpected open error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that file '%s' can be accessed.\n", handle->path); + log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", task->file->sub); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + + /* check if the file is changed */ + if (handle->st.st_size != task->file->size + || handle->st.st_mtime != task->file->mtime_sec + || STAT_NSEC(&handle->st) != task->file->mtime_nsec + || handle->st.st_ino != task->file->inode + ) { + log_tag("error:%u:%s:%s: Unexpected attribute change\n", blockcur, disk->name, esc(task->file->sub)); + if (handle->st.st_size != task->file->size) { + log_error("Unexpected size change at file '%s' from %" PRIu64 " to %" PRIu64 ".\n", handle->path, task->file->size, handle->st.st_size); + } else if (handle->st.st_mtime != task->file->mtime_sec + || STAT_NSEC(&handle->st) != task->file->mtime_nsec) { + log_error("Unexpected time change at file '%s' from %" PRIu64 ".%d to %" PRIu64 ".%d.\n", handle->path, task->file->mtime_sec, task->file->mtime_nsec, (uint64_t)handle->st.st_mtime, (uint32_t)STAT_NSEC(&handle->st)); + } else { + log_error("Unexpected inode change from %" PRIu64 " to %" PRIu64 " at file '%s'.\n", task->file->inode, (uint64_t)handle->st.st_ino, handle->path); + } + log_error("WARNING! You cannot modify files during a sync.\n"); + log_error("Rerun the sync command when finished.\n"); + /* if the file is changed, it means that it was modified during sync */ + /* this isn't a serious error, so we skip this block, and continue with others */ + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + task->read_size = handle_read(handle, task->file_pos, buffer, state->block_size, log_error, 0); + if (task->read_size == -1) { + /* LCOV_EXCL_START */ + if (errno == EIO) { + log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); +#if 0 + if (io_error >= state->opt.io_error_limit) { + log_fatal("DANGER! Unexpected input/output read error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be read.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + } +#endif + log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + log_fatal("WARNING! Unexpected read error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that file '%s' can be read.\n", handle->path); + log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", task->file->sub); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + + /* store the path of the opened file */ + pathcpy(task->path, sizeof(task->path), handle->path); + + task->state = TASK_STATE_DONE; +} + static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax) { + struct snapraid_io io; + struct snapraid_plan plan; struct snapraid_handle* handle; void* rehandle_alloc; struct snapraid_rehash* rehandle; unsigned diskmax; - block_off_t i; + block_off_t blockcur; unsigned j; - void* buffer_alloc; - void** buffer; + void* zero_alloc; + void** zero; unsigned buffermax; data_off_t countsize; block_off_t countpos; @@ -475,16 +665,16 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* rehash buffers */ rehandle = malloc_nofail_align(diskmax * sizeof(struct snapraid_rehash), &rehandle_alloc); - /* we need 2 * data + 1 * parity + 1 * zero */ - buffermax = 2 * diskmax + state->level + 1; + /* we need 2 * data + 1 * parity */ + buffermax = 2 * diskmax + state->level; - buffer = malloc_nofail_vector_align(diskmax, buffermax, state->block_size, &buffer_alloc); - if (!state->opt.skip_self) - mtest_vector(buffermax, state->block_size, buffer); + /* initialize the io threads */ + io_init(&io, state, buffermax, sync_data_reader, handle, diskmax, 0, 0, 0); /* fill up the zero buffer */ - memset(buffer[buffermax - 1], 0, state->block_size); - raid_zero(buffer[buffermax - 1]); + zero = malloc_nofail_align(state->block_size, &zero_alloc); + memset(zero, 0, state->block_size); + raid_zero(zero); failed = malloc_nofail(diskmax * sizeof(struct failed_struct)); failed_map = malloc_nofail(diskmax * sizeof(unsigned)); @@ -495,8 +685,10 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* first count the number of blocks to process */ countmax = 0; - for (i = blockstart; i < blockmax; ++i) { - if (!block_is_enabled(i, handle, diskmax)) + plan.handle_max = diskmax; + plan.handle_map = handle; + for (blockcur = blockstart; blockcur < blockmax; ++blockcur) { + if (!block_is_enabled(&plan, blockcur)) continue; ++countmax; } @@ -513,10 +705,14 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari countsize = 0; countpos = 0; + + /* start all the worker threads */ + io_start(&io, blockstart, blockmax, &block_is_enabled, &plan); + if (!state_progress_begin(state, blockstart, blockmax, countmax)) goto end; - for (i = blockstart; i < blockmax; ++i) { + while (1) { unsigned failed_count; int error_on_this_block; int silent_error_on_this_block; @@ -525,15 +721,18 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari int parity_needs_to_be_updated; snapraid_info info; int rehash; + void** buffer; - if (!block_is_enabled(i, handle, diskmax)) - continue; + /* go to the next block */ + blockcur = io_next(&io, &buffer); + if (blockcur >= blockmax) + break; /* one more block processed for autosave */ ++autosavedone; --autosavemissing; - /* by default process the block, and skip it if something go wrong */ + /* by default process the block, and skip it if something goes wrong */ error_on_this_block = 0; silent_error_on_this_block = 0; io_error_on_this_block = 0; @@ -543,7 +742,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari failed_count = 0; /* get block specific info */ - info = info_get(&state->infoarr, i); + info = info_get(&state->infoarr, blockcur); /* if we have to use the old hash */ rehash = info_get_rehash(info); @@ -563,26 +762,37 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* for each disk, process the block */ for (j = 0; j < diskmax; ++j) { + struct snapraid_task* task; int read_size; unsigned char hash[HASH_SIZE]; struct snapraid_block* block; unsigned block_state; - struct snapraid_disk* disk = handle[j].disk; + struct snapraid_disk* disk; struct snapraid_file* file; block_off_t file_pos; + unsigned diskcur; + + /* until now is CPU */ + state_usage_cpu(state); + + task = io_data_next(&io, &diskcur); + + /* get the results */ + disk = task->disk; + block = task->block; + file = task->file; + file_pos = task->file_pos; + read_size = task->read_size; /* by default no rehash in case of "continue" */ - rehandle[j].block = 0; + rehandle[diskcur].block = 0; /* if the disk position is not used */ - if (!disk) { - /* use an empty block */ - memset(buffer[j], 0, state->block_size); + if (!disk) continue; - } - /* get the block */ - block = fs_par2block_get(disk, i); + /* until now is disk */ + state_usage_disk(state, disk); /* get the state of the block */ block_state = block_state_get(block); @@ -592,7 +802,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari if (block_has_invalid_parity(block)) { /* store it in the failed set, because */ /* the parity may be still computed with the previous content */ - failed[failed_count].index = j; + failed[failed_count].index = diskcur; failed[failed_count].size = state->block_size; failed[failed_count].block = block; ++failed_count; @@ -611,160 +821,47 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* follow */ } - /* if the block has no file, meanining that it's EMPTY or DELETED, */ - /* it doesn't partecipate in the new parity computation */ - if (!block_has_file(block)) { - /* use an empty block */ - memset(buffer[j], 0, state->block_size); + /* if the block is not used */ + if (!block_has_file(block)) continue; + + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; } - - /* get the file of this block */ - file = fs_par2file_get(disk, i, &file_pos); - - /* until now is CPU */ - state_usage_cpu(state); - - /* if the file is different than the current one, close it */ - if (handle[j].file != 0 && handle[j].file != file) { - /* keep a pointer at the file we are going to close for error reporting */ - struct snapraid_file* report = handle[j].file; - ret = handle_close(&handle[j]); - if (ret == -1) { - /* LCOV_EXCL_START */ - /* This one is really an unexpected error, because we are only reading */ - /* and closing a descriptor should never fail */ - if (errno == EIO) { - log_tag("error:%u:%s:%s: Close EIO error. %s\n", i, disk->name, esc(report->sub), strerror(errno)); - log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - } - - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(report->sub), strerror(errno)); - log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } - } - - ret = handle_open(&handle[j], file, state->file_mode, log_error, 0); - if (ret == -1) { - if (errno == EIO) { - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Open EIO error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - /* LCOV_EXCL_STOP */ - } - - if (errno == ENOENT) { - log_tag("error:%u:%s:%s: Open ENOENT error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_error("Missing file '%s'.\n", handle[j].path); - log_error("WARNING! You cannot modify data disk during a sync.\n"); - log_error("Rerun the sync command when finished.\n"); - ++error; - /* if the file is missing, it means that it was removed during sync */ - /* this isn't a serious error, so we skip this block, and continue with others */ - error_on_this_block = 1; - continue; - } - - if (errno == EACCES) { - log_tag("error:%u:%s:%s: Open EACCES error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_error("No access at file '%s'.\n", handle[j].path); - log_error("WARNING! Please fix the access permission in the data disk.\n"); - log_error("Rerun the sync command when finished.\n"); - ++error; - /* this isn't a serious error, so we skip this block, and continue with others */ - error_on_this_block = 1; - continue; - } - - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Open error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_fatal("WARNING! Unexpected open error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path); - log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", file->sub); + if (task->state == TASK_STATE_ERROR) { ++error; goto bail; - /* LCOV_EXCL_STOP */ } - - /* check if the file is changed */ - if (handle[j].st.st_size != file->size - || handle[j].st.st_mtime != file->mtime_sec - || STAT_NSEC(&handle[j].st) != file->mtime_nsec - || handle[j].st.st_ino != file->inode - ) { - log_tag("error:%u:%s:%s: Unexpected attribute change\n", i, disk->name, esc(file->sub)); - if (handle[j].st.st_size != file->size) { - log_error("Unexpected size change at file '%s' from %" PRIu64 " to %" PRIu64 ".\n", handle[j].path, file->size, handle[j].st.st_size); - } else if (handle[j].st.st_mtime != file->mtime_sec - || STAT_NSEC(&handle[j].st) != file->mtime_nsec) { - log_error("Unexpected time change at file '%s' from %" PRIu64 ".%d to %" PRIu64 ".%d.\n", handle[j].path, file->mtime_sec, file->mtime_nsec, (uint64_t)handle[j].st.st_mtime, (uint32_t)STAT_NSEC(&handle[j].st)); - } else { - log_error("Unexpected inode change from %" PRIu64 " to %" PRIu64 " at file '%s'.\n", file->inode, (uint64_t)handle[j].st.st_ino, handle[j].path); - } - log_error("WARNING! You cannot modify files during a sync.\n"); - log_error("Rerun the sync command when finished.\n"); + if (task->state == TASK_STATE_ERROR_CONTINUE) { ++error; - /* if the file is changed, it means that it was modified during sync */ - /* this isn't a serious error, so we skip this block, and continue with others */ error_on_this_block = 1; continue; } - - read_size = handle_read(&handle[j], file_pos, buffer[j], state->block_size, log_error, 0); - if (read_size == -1) { + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + io_error_on_this_block = 1; + continue; + } + if (task->state != TASK_STATE_DONE) { /* LCOV_EXCL_START */ - if (errno == EIO) { - log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno)); - if (io_error >= state->opt.io_error_limit) { - log_fatal("DANGER! Unexpected input/output read error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be read.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - } - - log_error("Input/Output error in file '%s' at position '%u'\n", handle[j].path, file_pos); - ++io_error; - io_error_on_this_block = 1; - continue; - } - - log_tag("error:%u:%s:%s: Read error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno)); - log_fatal("WARNING! Unexpected read error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that file '%s' can be read.\n", handle[j].path); - log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", file->sub); - ++error; - goto bail; + log_fatal("Internal inconsistency in task state\n"); + os_abort(); /* LCOV_EXCL_STOP */ } - /* until now is disk */ - state_usage_disk(state, disk); - countsize += read_size; /* now compute the hash */ if (rehash) { - memhash(state->prevhash, state->prevhashseed, hash, buffer[j], read_size); + memhash(state->prevhash, state->prevhashseed, hash, buffer[diskcur], read_size); /* compute the new hash, and store it */ - rehandle[j].block = block; - memhash(state->hash, state->hashseed, rehandle[j].hash, buffer[j], read_size); + rehandle[diskcur].block = block; + memhash(state->hash, state->hashseed, rehandle[diskcur].hash, buffer[diskcur], read_size); } else { - memhash(state->hash, state->hashseed, hash, buffer[j], read_size); + memhash(state->hash, state->hashseed, hash, buffer[diskcur], read_size); } if (block_has_updated_hash(block)) { @@ -772,8 +869,8 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari if (memcmp(hash, block->hash, HASH_SIZE) != 0) { /* if the file has invalid parity, it's a REP changed during the sync */ if (block_has_invalid_parity(block)) { - log_tag("error:%u:%s:%s: Unexpected data change\n", i, disk->name, esc(file->sub)); - log_error("Data change at file '%s' at position '%u'\n", handle[j].path, file_pos); + log_tag("error:%u:%s:%s: Unexpected data change\n", blockcur, disk->name, esc(file->sub)); + log_error("Data change at file '%s' at position '%u'\n", task->path, file_pos); log_error("WARNING! Unexpected data modification of a file without parity!\n"); if (file_flag_has(file, FILE_IS_COPY)) { @@ -793,11 +890,11 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari continue; } else { /* otherwise it's a BLK with silent error */ unsigned diff = memdiff(hash, block->hash, HASH_SIZE); - log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", i, disk->name, esc(file->sub), file_pos, diff); - log_error("Data error in file '%s' at position '%u', diff bits %u\n", handle[j].path, file_pos, diff); + log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", blockcur, disk->name, esc(file->sub), file_pos, diff); + log_error("Data error in file '%s' at position '%u', diff bits %u\n", task->path, file_pos, diff); /* save the failed block for the fix */ - failed[failed_count].index = j; + failed[failed_count].index = diskcur; failed[failed_count].size = read_size; failed[failed_count].block = block; ++failed_count; @@ -847,6 +944,11 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari unsigned failed_mac; int something_to_recover = 0; + /* sort the failed vector */ + /* because with threads it may be in any order */ + /* but RAID requires the indexes to be sorted */ + qsort(failed, failed_count, sizeof(failed[0]), failed_compare_by_index); + /* setup the blocks to recover */ failed_mac = 0; for (j = 0; j < failed_count; ++j) { @@ -887,29 +989,29 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* we are sure that parity exists because */ /* we have at least one BLK block */ for (l = 0; l < state->level; ++l) { - ret = parity_read(parity[l], i, buffer[diskmax + l], state->block_size, log_error); + ret = parity_read(parity[l], blockcur, buffer[diskmax + l], state->block_size, log_error); if (ret == -1) { /* LCOV_EXCL_START */ if (errno == EIO) { - log_tag("parity_error:%u:%s: Read EIO error. %s\n", i, lev_config_name(l), strerror(errno)); + log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(l), strerror(errno)); if (io_error >= state->opt.io_error_limit) { log_fatal("DANGER! Unexpected input/output read error in the %s disk, it isn't possible to sync.\n", lev_name(l)); log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++io_error; goto bail; } - log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), i); + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), blockcur); ++io_error; io_error_on_this_block = 1; continue; } - log_tag("parity_error:%u:%s: Read error. %s\n", i, lev_config_name(l), strerror(errno)); + log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(l), strerror(errno)); log_fatal("WARNING! Unexpected read error in the %s disk, it isn't possible to sync.\n", lev_name(l)); log_fatal("Ensure that disk '%s' can be read.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++error; goto bail; /* LCOV_EXCL_STOP */ @@ -982,29 +1084,29 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* write the parity */ for (l = 0; l < state->level; ++l) { - ret = parity_write(parity[l], i, buffer[diskmax + l], state->block_size); + ret = parity_write(parity[l], blockcur, buffer[diskmax + l], state->block_size); if (ret == -1) { /* LCOV_EXCL_START */ if (errno == EIO) { - log_tag("parity_error:%u:%s: Write EIO error. %s\n", i, lev_config_name(l), strerror(errno)); + log_tag("parity_error:%u:%s: Write EIO error. %s\n", blockcur, lev_config_name(l), strerror(errno)); if (io_error >= state->opt.io_error_limit) { log_fatal("DANGER! Unexpected input/output write error in the %s disk, it isn't possible to sync.\n", lev_name(l)); log_fatal("Ensure that disk '%s' is sane and can be written.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++io_error; goto bail; } - log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), i); + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), blockcur); ++io_error; io_error_on_this_block = 1; continue; } - log_tag("parity_error:%u:%s: Write error. %s\n", i, lev_config_name(l), strerror(errno)); + log_tag("parity_error:%u:%s: Write error. %s\n", blockcur, lev_config_name(l), strerror(errno)); log_fatal("WARNING! Unexpected write error in the %s disk, it isn't possible to sync.\n", lev_name(l)); log_fatal("Ensure that disk '%s' has some free space available.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++error; goto bail; /* LCOV_EXCL_STOP */ @@ -1024,7 +1126,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari if (!handle[j].disk) continue; - block = fs_par2block_get(handle[j].disk, i); + block = fs_par2block_get(handle[j].disk, blockcur); if (block == BLOCK_EMPTY) { /* nothing to do */ @@ -1034,7 +1136,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* if it's a deleted block */ if (block_state_get(block) == BLOCK_STATE_DELETED) { /* the parity is now updated without this block, so it's now empty */ - fs_deallocate(handle[j].disk, i); + fs_deallocate(handle[j].disk, blockcur); continue; } @@ -1063,7 +1165,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* update the time info of the block */ /* we are also clearing any previous bad and rehash flag */ - info_set(&state->infoarr, i, info_make(now, 0, 0, 1)); + info_set(&state->infoarr, blockcur, info_make(now, 0, 0, 1)); } } @@ -1072,7 +1174,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* because our correction is in memory only and not yet written */ if (silent_error_on_this_block || io_error_on_this_block) { /* set the error status keeping the other info */ - info_set(&state->infoarr, i, info_set_bad(info)); + info_set(&state->infoarr, blockcur, info_set_bad(info)); } /* mark the state as needing write */ @@ -1082,7 +1184,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari ++countpos; /* progress */ - if (state_progress(state, i, countpos, countmax, countsize)) { + if (state_progress(state, blockcur, countpos, countmax, countsize)) { /* LCOV_EXCL_START */ break; /* LCOV_EXCL_STOP */ @@ -1093,7 +1195,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari && autosavedone >= autosavelimit /* if we have reached the limit */ && autosavemissing >= autosavelimit) /* if we have at least a full step to do */ /* or if we have a forced autosave at the specified block */ - || (state->opt.force_autosave_at != 0 && state->opt.force_autosave_at == i) + || (state->opt.force_autosave_at != 0 && state->opt.force_autosave_at == blockcur) ) { autosavedone = 0; /* restart the counter */ @@ -1110,10 +1212,10 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari ret = parity_sync(parity[l]); if (ret == -1) { /* LCOV_EXCL_START */ - log_tag("parity_error:%u:%s: Sync error\n", i, lev_config_name(l)); + log_tag("parity_error:%u:%s: Sync error\n", blockcur, lev_config_name(l)); log_fatal("DANGER! Unexpected sync error in %s disk.\n", lev_name(l)); log_fatal("Ensure that disk '%s' is sane.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++error; goto bail; /* LCOV_EXCL_STOP */ @@ -1141,10 +1243,10 @@ end: ret = parity_sync(parity[l]); if (ret == -1) { /* LCOV_EXCL_START */ - log_tag("parity_error:%u:%s: Sync error\n", i, lev_config_name(l)); + log_tag("parity_error:%u:%s: Sync error\n", blockcur, lev_config_name(l)); log_fatal("DANGER! Unexpected sync error in %s disk.\n", lev_name(l)); log_fatal("Ensure that disk '%s' is sane.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++error; goto bail; /* LCOV_EXCL_STOP */ @@ -1183,13 +1285,16 @@ end: log_flush(); bail: + /* stop all the worker threads */ + io_stop(&io); + for (j = 0; j < diskmax; ++j) { struct snapraid_file* file = handle[j].file; struct snapraid_disk* disk = handle[j].disk; ret = handle_close(&handle[j]); if (ret == -1) { /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno)); log_fatal("DANGER! Unexpected close error in a data disk.\n"); ++error; /* continue, as we are already exiting */ @@ -1198,11 +1303,11 @@ bail: } free(handle); - free(buffer_alloc); - free(buffer); + free(zero_alloc); free(rehandle_alloc); free(failed); free(failed_map); + io_done(&io); if (state->opt.expect_recoverable) { if (error + silent_error + io_error == 0) From cbf2040b89cdf6bfff4cd608ed31ff16d5476665 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 1 Jan 2016 10:00:18 +0100 Subject: [PATCH 05/34] Add mutex protection at the filesystem structure In sync we need to modify the filesystem, like to call fs_deallocate(). We need then proper protection. This also removes the need of multiple fs_last cached value. --- cmdline/elem.c | 115 +++++++++++++++++++++++++++++++++++------------ cmdline/elem.h | 47 +++++++------------ cmdline/io.c | 1 - cmdline/io.h | 9 ---- cmdline/parity.c | 4 +- cmdline/scrub.c | 4 +- cmdline/state.c | 24 ++++------ cmdline/sync.c | 4 +- 8 files changed, 117 insertions(+), 91 deletions(-) diff --git a/cmdline/elem.c b/cmdline/elem.c index 2f8caf61..21187320 100644 --- a/cmdline/elem.c +++ b/cmdline/elem.c @@ -737,6 +737,8 @@ struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev /* ensure that the dir terminate with "/" if it isn't empty */ pathslash(disk->dir, sizeof(disk->dir)); + pthread_mutex_init(&disk->fs_mutex, 0); + disk->smartctl[0] = 0; disk->device = dev; disk->tick = 0; @@ -778,9 +780,22 @@ void disk_free(struct snapraid_disk* disk) tommy_hashdyn_done(&disk->linkset); tommy_list_foreach(&disk->dirlist, (tommy_foreach_func*)dir_free); tommy_hashdyn_done(&disk->dirset); + + pthread_mutex_destroy(&disk->fs_mutex); + free(disk); } +static inline void fs_lock(struct snapraid_disk* disk) +{ + pthread_mutex_lock(&disk->fs_mutex); +} + +static inline void fs_unlock(struct snapraid_disk* disk) +{ + pthread_mutex_unlock(&disk->fs_mutex); +} + struct chunk_disk_empty { block_off_t blockmax; }; @@ -788,7 +803,7 @@ struct chunk_disk_empty { /** * Compare the chunk if inside the specified blockmax. */ -static int chunk_disk_empty_compare(const void* void_a, const void* void_b) +static int chunk_disk_empty_compare_unlock(const void* void_a, const void* void_b) { const struct chunk_disk_empty* arg_a = void_a; const struct snapraid_chunk* arg_b = void_b; @@ -801,7 +816,7 @@ static int chunk_disk_empty_compare(const void* void_a, const void* void_b) return -1; } -int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax) +int fs_is_empty(struct snapraid_disk* disk, block_off_t blockmax) { struct chunk_disk_empty arg = { blockmax }; @@ -814,11 +829,16 @@ int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax) if (!tommy_list_empty(&disk->dirlist)) return 0; + fs_lock(disk); + /* search for any chunk inside blockmax */ - if (tommy_tree_search_compare(&disk->fs_parity, chunk_disk_empty_compare, &arg) != 0) + if (tommy_tree_search_compare(&disk->fs_parity, chunk_disk_empty_compare_unlock, &arg) != 0) { + fs_unlock(disk); return 0; + } /* finally, it's empty */ + fs_unlock(disk); return 1; } @@ -831,7 +851,7 @@ struct chunk_disk_size { * * The maximum parity position is stored as size. */ -static int chunk_disk_size_compare(const void* void_a, const void* void_b) +static int chunk_disk_size_compare_unlock(const void* void_a, const void* void_b) { struct chunk_disk_size* arg_a = (void*)void_a; const struct snapraid_chunk* arg_b = void_b; @@ -844,10 +864,16 @@ static int chunk_disk_size_compare(const void* void_a, const void* void_b) return 1; } -block_off_t disk_size(struct snapraid_disk* disk) +block_off_t fs_size(struct snapraid_disk* disk) { struct chunk_disk_size arg = { 0 }; - tommy_tree_search_compare(&disk->fs_parity, chunk_disk_size_compare, &arg); + + fs_lock(disk); + + tommy_tree_search_compare(&disk->fs_parity, chunk_disk_size_compare_unlock, &arg); + + fs_unlock(disk); + return arg.size; } @@ -856,7 +882,7 @@ struct chunk_check { int result; }; -void chunk_parity_check_foreach(void* void_arg, void* void_obj) +static void chunk_parity_check_foreach_unlock(void* void_arg, void* void_obj) { struct chunk_check* arg = void_arg; const struct snapraid_chunk* obj = void_obj; @@ -906,7 +932,7 @@ void chunk_parity_check_foreach(void* void_arg, void* void_obj) } } -void chunk_file_check_foreach(void* void_arg, void* void_obj) +static void chunk_file_check_foreach_unlock(void* void_arg, void* void_obj) { struct chunk_check* arg = void_arg; const struct snapraid_chunk* obj = void_obj; @@ -1022,13 +1048,17 @@ int fs_check(struct snapraid_disk* disk) /* error count starts from 0 */ arg.result = 0; + fs_lock(disk); + /* check parity sequence */ arg.prev = 0; - tommy_tree_foreach_arg(&disk->fs_parity, chunk_parity_check_foreach, &arg); + tommy_tree_foreach_arg(&disk->fs_parity, chunk_parity_check_foreach_unlock, &arg); /* check file sequence */ arg.prev = 0; - tommy_tree_foreach_arg(&disk->fs_file, chunk_file_check_foreach, &arg); + tommy_tree_foreach_arg(&disk->fs_file, chunk_file_check_foreach_unlock, &arg); + + fs_unlock(disk); if (arg.result != 0) return -1; @@ -1043,7 +1073,7 @@ struct chunk_parity_inside { /** * Compare the chunk if containing the specified parity position. */ -static int chunk_parity_inside_compare(const void* void_a, const void* void_b) +static int chunk_parity_inside_compare_unlock(const void* void_a, const void* void_b) { const struct chunk_parity_inside* arg_a = void_a; const struct snapraid_chunk* arg_b = void_b; @@ -1061,7 +1091,7 @@ static int chunk_parity_inside_compare(const void* void_a, const void* void_b) * The search is optimized for sequential accesses. * \return If not found return 0 */ -static struct snapraid_chunk* fs_par2chunk_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos) +static struct snapraid_chunk* fs_par2chunk_get_unlock(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos) { struct snapraid_chunk* chunk; @@ -1073,7 +1103,7 @@ static struct snapraid_chunk* fs_par2chunk_get_ts(struct snapraid_disk* disk, st chunk = *fs_last; } else { struct chunk_parity_inside arg = { parity_pos }; - chunk = tommy_tree_search_compare(&disk->fs_parity, chunk_parity_inside_compare, &arg); + chunk = tommy_tree_search_compare(&disk->fs_parity, chunk_parity_inside_compare_unlock, &arg); } if (!chunk) @@ -1093,7 +1123,7 @@ struct chunk_file_inside { /** * Compare the chunk if containing the specified file position. */ -static int chunk_file_inside_compare(const void* void_a, const void* void_b) +static int chunk_file_inside_compare_unlock(const void* void_a, const void* void_b) { const struct chunk_file_inside* arg_a = void_a; const struct snapraid_chunk* arg_b = void_b; @@ -1116,7 +1146,7 @@ static int chunk_file_inside_compare(const void* void_a, const void* void_b) * The search is optimized for sequential accesses. * \return If not found return 0 */ -static struct snapraid_chunk* fs_file2chunk_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos) +static struct snapraid_chunk* fs_file2chunk_get_unlock(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos) { struct snapraid_chunk* chunk; @@ -1129,7 +1159,7 @@ static struct snapraid_chunk* fs_file2chunk_get_ts(struct snapraid_disk* disk, s chunk = *fs_last; } else { struct chunk_file_inside arg = { file, file_pos }; - chunk = tommy_tree_search_compare(&disk->fs_file, chunk_file_inside_compare, &arg); + chunk = tommy_tree_search_compare(&disk->fs_file, chunk_file_inside_compare_unlock, &arg); } if (!chunk) @@ -1141,25 +1171,37 @@ static struct snapraid_chunk* fs_file2chunk_get_ts(struct snapraid_disk* disk, s return chunk; } -struct snapraid_file* fs_par2file_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos, block_off_t* file_pos) +struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos) { struct snapraid_chunk* chunk; + struct snapraid_file* file; - chunk = fs_par2chunk_get_ts(disk, fs_last, parity_pos); - if (!chunk) + fs_lock(disk); + + chunk = fs_par2chunk_get_unlock(disk, &disk->fs_last, parity_pos); + + if (!chunk) { + fs_unlock(disk); return 0; + } if (file_pos) *file_pos = chunk->file_pos + (parity_pos - chunk->parity_pos); - return chunk->file; + file = chunk->file; + + fs_unlock(disk); + return file; } -block_off_t fs_file2par_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos) +block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos) { struct snapraid_chunk* chunk; + block_off_t ret; - chunk = fs_file2chunk_get_ts(disk, fs_last, file, file_pos); + fs_lock(disk); + + chunk = fs_file2chunk_get_unlock(disk, &disk->fs_last, file, file_pos); if (!chunk) { /* LCOV_EXCL_START */ log_fatal("Internal inconsistency when resolving file '%s' at position '%u/%u' in disk '%s'\n", file->sub, file_pos, file->blockmax, disk->name); @@ -1167,7 +1209,10 @@ block_off_t fs_file2par_get_ts(struct snapraid_disk* disk, struct snapraid_chunk /* LCOV_EXCL_STOP */ } - return chunk->parity_pos + (file_pos - chunk->file_pos); + ret = chunk->parity_pos + (file_pos - chunk->file_pos); + + fs_unlock(disk); + return ret; } void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos) @@ -1176,9 +1221,11 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap struct snapraid_chunk* parity_chunk; struct snapraid_chunk* file_chunk; + fs_lock(disk); + if (file_pos > 0) { /* search an existing chunk for the previous file_pos */ - chunk = fs_file2chunk_get_ts(disk, &disk->fs_last, file, file_pos - 1); + chunk = fs_file2chunk_get_unlock(disk, &disk->fs_last, file, file_pos - 1); if (chunk != 0 && parity_pos == chunk->parity_pos + chunk->count) { /* ensure that we are extending the chunk at the end */ @@ -1192,6 +1239,7 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap /* extend the existing chunk */ ++chunk->count; + fs_unlock(disk); return; } } @@ -1212,6 +1260,8 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap /* store the last accessed chunk */ disk->fs_last = chunk; + + fs_unlock(disk); } void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) @@ -1222,7 +1272,9 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) struct snapraid_chunk* file_chunk; block_off_t first_count, second_count; - chunk = fs_par2chunk_get_ts(disk, &disk->fs_last, parity_pos); + fs_lock(disk); + + chunk = fs_par2chunk_get_unlock(disk, &disk->fs_last, parity_pos); if (!chunk) { /* LCOV_EXCL_START */ log_fatal("Internal inconsistency when deallocating parity position '%u' for not existing chunk in disk '%s'\n", parity_pos, disk->name); @@ -1241,6 +1293,8 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) /* clear the last accessed chunk */ disk->fs_last = 0; + + fs_unlock(disk); return; } @@ -1249,12 +1303,16 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) ++chunk->parity_pos; ++chunk->file_pos; --chunk->count; + + fs_unlock(disk); return; } /* if it's at the end of the chunk, shrink the chunk */ if (parity_pos == chunk->parity_pos + chunk->count - 1) { --chunk->count; + + fs_unlock(disk); return; } @@ -1281,6 +1339,8 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) /* store the last accessed chunk */ disk->fs_last = second_chunk; + + fs_unlock(disk); } struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos) @@ -1295,13 +1355,12 @@ struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t return &file->blockvec[file_pos]; } -struct snapraid_block* fs_par2block_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos) +struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos) { struct snapraid_file* file; block_off_t file_pos; - file = fs_par2file_get_ts(disk, fs_last, parity_pos, &file_pos); - + file = fs_par2file_get(disk, parity_pos, &file_pos); if (!file) return BLOCK_EMPTY; diff --git a/cmdline/elem.h b/cmdline/elem.h index 9264ba9b..e1a11a2c 100644 --- a/cmdline/elem.h +++ b/cmdline/elem.h @@ -347,6 +347,17 @@ struct snapraid_disk { int mapping_idx; /**< Index in the mapping vector. Used only as buffer when writing the content file. */ int skip_access; /**< If the disk is inaccessible and it should be skipped. */ + /** + * Mutex for protecting the filesystem structure. + * + * Specifically, this protects ::fs_parity, ::fs_file, and ::fs_last, + * meaning that it protectes only chunks. + * + * Files, links and dirs are not protected as they are not expected to + * change during multithread processing. + */ + pthread_mutex_t fs_mutex; + /** * Mapping of chunks in the parity. * Sorted by and by @@ -878,7 +889,7 @@ void disk_free(struct snapraid_disk* disk); /** * Get the size of the disk in blocks. */ -block_off_t disk_size(struct snapraid_disk* disk); +block_off_t fs_size(struct snapraid_disk* disk); /** * Check if a disk is totally empty and can be discarded from the content file. @@ -886,7 +897,7 @@ block_off_t disk_size(struct snapraid_disk* disk); * and without any DELETED block. * The blockmax is used to limit the search of DELETED block up to blockmax. */ -int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax); +int fs_is_empty(struct snapraid_disk* disk, block_off_t blockmax); /** * Check the file-system for errors. @@ -919,49 +930,23 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t pos); */ struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos); -/** - * Like fs_file2par_get() but thread-safe. - */ -struct snapraid_file* fs_par2file_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos, block_off_t* file_pos); - /** * Get the file position from the parity position. * Return 0 if no file is using it. - * \note This function is NOT thread-safe as it uses the the disk cache. */ -static inline struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos) -{ - return fs_par2file_get_ts(disk, &disk->fs_last, parity_pos, file_pos); -} - -/** - * Like fs_file2par_get() but thread-safe. - */ -block_off_t fs_file2par_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos); +struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos); /** * Get the parity position from the file position. - * \note This function is NOT thread-safe as it uses the the disk cache. */ -static inline block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos) -{ - return fs_file2par_get_ts(disk, &disk->fs_last, file, file_pos); -} - -/** - * Like fs_par2block_get() but thread-safe. - */ -struct snapraid_block* fs_par2block_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos); +block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos); /** * Get the block from the parity position. * Return BLOCK_EMPTY==0 if the block is over the end of the disk or not used. * \note This function is NOT thread-safe as it uses the the disk cache. */ -static inline struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos) -{ - return fs_par2block_get_ts(disk, &disk->fs_last, parity_pos); -} +struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos); /** * Allocate a disk mapping. diff --git a/cmdline/io.c b/cmdline/io.c index c29b58c7..f8a7d422 100644 --- a/cmdline/io.c +++ b/cmdline/io.c @@ -53,7 +53,6 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff struct snapraid_worker* worker = &io->worker_map[i]; worker->io = io; - worker->fs_last = 0; if (i < handle_max) { worker->handle = &handle_map[i]; diff --git a/cmdline/io.h b/cmdline/io.h index 335bad9e..a15c1d53 100644 --- a/cmdline/io.h +++ b/cmdline/io.h @@ -105,15 +105,6 @@ struct snapraid_worker { * Which buffer base index should be used for destination. */ unsigned buffer_skew; - - /** - * Last chunk we accessed. - * - * It's used to optimize access of sequential blocks. - * We cannot use the one in ::disk because it's used - * by the main thread. - */ - struct snapraid_chunk* fs_last; }; /** diff --git a/cmdline/parity.c b/cmdline/parity.c index 400f0601..92d2db8c 100644 --- a/cmdline/parity.c +++ b/cmdline/parity.c @@ -37,7 +37,7 @@ block_off_t parity_allocated_size(struct snapraid_state* state) struct snapraid_disk* disk = i->data; /* start from the declared size */ - block_off_t block = disk_size(disk); + block_off_t block = fs_size(disk); /* decrease the block until an allocated one, but part of a file */ /* we don't stop at deleted blocks, because we want to have them cleared */ @@ -64,7 +64,7 @@ block_off_t parity_used_size(struct snapraid_state* state) struct snapraid_disk* disk = i->data; /* start from the declared size */ - block_off_t block = disk_size(disk); + block_off_t block = fs_size(disk); /* decrease the block until an used one */ while (block > parity_block && !block_has_file_and_valid_parity(fs_par2block_get(disk, block - 1))) diff --git a/cmdline/scrub.c b/cmdline/scrub.c index 0e06d5f2..135b773f 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -121,7 +121,7 @@ static void scrub_data_reader(struct snapraid_worker* worker, struct snapraid_ta } /* get the block */ - task->block = fs_par2block_get_ts(disk, &worker->fs_last, blockcur); + task->block = fs_par2block_get(disk, blockcur); /* if the block is not used */ if (!block_has_file(task->block)) { @@ -132,7 +132,7 @@ static void scrub_data_reader(struct snapraid_worker* worker, struct snapraid_ta } /* get the file of this block */ - task->file = fs_par2file_get_ts(disk, &worker->fs_last, blockcur, &task->file_pos); + task->file = fs_par2file_get(disk, blockcur, &task->file_pos); /* if the file is different than the current one, close it */ if (handle->file != 0 && handle->file != task->file) { diff --git a/cmdline/state.c b/cmdline/state.c index a140d122..465f9f89 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -1599,9 +1599,9 @@ static void fs_position_clear_deleted(struct snapraid_state* state, block_off_t /** * Check if a block position in a disk is deleted. */ -static int fs_is_block_deleted_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t pos) +static int fs_is_block_deleted(struct snapraid_disk* disk, block_off_t pos) { - struct snapraid_block* block = fs_par2block_get_ts(disk, fs_last, pos); + struct snapraid_block* block = fs_par2block_get(disk, pos); return block_state_get(block) == BLOCK_STATE_DELETED; } @@ -2735,14 +2735,6 @@ static void* state_write_thread(void* arg) for (i = state->disklist; i != 0; i = i->next) { tommy_node* j; struct snapraid_disk* disk = i->data; - struct snapraid_chunk* fs_last; - - /** - * This is the last accessed chunk in the tree operations. - * Here we are inside a thread, and we cannot use the - * ::disk member to store this information. - */ - fs_last = 0; /* if the disk is not mapped, skip it */ if (disk->mapping_idx < 0) @@ -2783,7 +2775,7 @@ static void* state_write_thread(void* arg) begin = 0; while (begin < file->blockmax) { unsigned v_state = block_state_get(fs_file2block_get(file, begin)); - block_off_t v_pos = fs_file2par_get_ts(disk, &fs_last, file, begin); + block_off_t v_pos = fs_file2par_get(disk, file, begin); uint32_t v_count; block_off_t end; @@ -2793,7 +2785,7 @@ static void* state_write_thread(void* arg) while (end < file->blockmax) { if (v_state != block_state_get(fs_file2block_get(file, end))) break; - if (v_pos + (end - begin) != fs_file2par_get_ts(disk, &fs_last, file, end)) + if (v_pos + (end - begin) != fs_file2par_get(disk,file, end)) break; ++end; } @@ -2898,12 +2890,12 @@ static void* state_write_thread(void* arg) int is_deleted; block_off_t end; - is_deleted = fs_is_block_deleted_ts(disk, &fs_last, begin); + is_deleted = fs_is_block_deleted(disk, begin); /* find the end of run of blocks */ end = begin + 1; while (end < blockmax - && is_deleted == fs_is_block_deleted_ts(disk, &fs_last, end) + && is_deleted == fs_is_block_deleted(disk, end) ) { ++end; } @@ -2916,7 +2908,7 @@ static void* state_write_thread(void* arg) /* write all the hash */ while (begin < end) { - struct snapraid_block* block = fs_par2block_get_ts(disk, &fs_last, begin); + struct snapraid_block* block = fs_par2block_get(disk, begin); swrite(block->hash, HASH_SIZE, f); @@ -3108,7 +3100,7 @@ static void state_write_content(struct snapraid_state* state, uint32_t* out_crc) } /* save the mapping only for not empty disks */ - if (!disk_is_empty(disk, blockmax)) { + if (!fs_is_empty(disk, blockmax)) { /* assign the mapping index used to identify disks */ disk->mapping_idx = mapping_idx; ++mapping_idx; diff --git a/cmdline/sync.c b/cmdline/sync.c index b4840ee2..0d13c9c8 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -481,7 +481,7 @@ static void sync_data_reader(struct snapraid_worker* worker, struct snapraid_tas } /* get the block */ - task->block = fs_par2block_get_ts(disk, &worker->fs_last, blockcur); + task->block = fs_par2block_get(disk, blockcur); /* if the block has no file, meanining that it's EMPTY or DELETED, */ /* it doesn't partecipate in the new parity computation */ @@ -493,7 +493,7 @@ static void sync_data_reader(struct snapraid_worker* worker, struct snapraid_tas } /* get the file of this block */ - task->file = fs_par2file_get_ts(disk, &worker->fs_last, blockcur, &task->file_pos); + task->file = fs_par2file_get(disk, blockcur, &task->file_pos); /* if the file is different than the current one, close it */ if (handle->file != 0 && handle->file != task->file) { From afd29347bbabe9a365d6482bdaab76c1d40ede44 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sat, 2 Jan 2016 17:13:19 +0100 Subject: [PATCH 06/34] Restore the io error limit feature for multithread support --- cmdline/scrub.c | 43 ++++++++++++++++++++----------------------- cmdline/sync.c | 17 ++++++++--------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/cmdline/scrub.c b/cmdline/scrub.c index 135b773f..fccedd69 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -199,17 +199,6 @@ static void scrub_data_reader(struct snapraid_worker* worker, struct snapraid_ta if (task->read_size == -1) { if (errno == EIO) { log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); -#if 0 - if (io_error >= state->opt.io_error_limit) { - /* LCOV_EXCL_START */ - log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); - log_fatal("Stopping at block %u\n", blockcur); - task->state = TASK_STATE_IOERROR; - return; - /* LCOV_EXCL_STOP */ - } -#endif log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos); task->state = TASK_STATE_IOERROR_CONTINUE; return; @@ -241,18 +230,6 @@ static void scrub_parity_reader(struct snapraid_worker* worker, struct snapraid_ if (ret == -1) { if (errno == EIO) { log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno)); -#if 0 - if (io_error >= state->opt.io_error_limit) { - /* LCOV_EXCL_START */ - log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(level)); - log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(level)); - log_fatal("Stopping at block %u\n", blockcur); - task->state = TASK_STATE_IOERROR; - return; - /* LCOV_EXCL_STOP */ - } -#endif - log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur); task->state = TASK_STATE_IOERROR_CONTINUE; return; @@ -440,6 +417,16 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par } if (task->state == TASK_STATE_IOERROR_CONTINUE) { ++io_error; + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, task->path); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + /* LCOV_EXCL_STOP */ + } + + /* otherwise continue */ io_error_on_this_block = 1; continue; } @@ -522,6 +509,16 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par } if (task->state == TASK_STATE_IOERROR_CONTINUE) { ++io_error; + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(levcur)); + log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(levcur)); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + /* LCOV_EXCL_STOP */ + } + + /* otherwise continue */ io_error_on_this_block = 1; /* if continuing on error, clear the missing buffer */ diff --git a/cmdline/sync.c b/cmdline/sync.c index 0d13c9c8..d38d2432 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -595,15 +595,6 @@ static void sync_data_reader(struct snapraid_worker* worker, struct snapraid_tas /* LCOV_EXCL_START */ if (errno == EIO) { log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); -#if 0 - if (io_error >= state->opt.io_error_limit) { - log_fatal("DANGER! Unexpected input/output read error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be read.\n", disk->dir, handle->path); - log_fatal("Stopping at block %u\n", blockcur); - task->state = TASK_STATE_IOERROR; - return; - } -#endif log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos); task->state = TASK_STATE_IOERROR_CONTINUE; return; @@ -841,6 +832,14 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari } if (task->state == TASK_STATE_IOERROR_CONTINUE) { ++io_error; + if (io_error >= state->opt.io_error_limit) { + log_fatal("DANGER! Unexpected input/output read error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be read.\n", disk->dir, task->path); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + } + + /* otherwise continue */ io_error_on_this_block = 1; continue; } From 55ac2b0d25212b986799c3a9b8999d1d5308e4b8 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sun, 3 Jan 2016 18:38:07 +0100 Subject: [PATCH 07/34] Use a lot more read-ahead buffers This improves the performance even more. --- cmdline/io.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cmdline/io.h b/cmdline/io.h index a15c1d53..9514ada0 100644 --- a/cmdline/io.h +++ b/cmdline/io.h @@ -25,8 +25,21 @@ /** * Number of read-ahead buffers. + * + * More buffers always result in better performance. + * + * This is the scrub performance on my machine with different buffers: + * + * 1 - 380 MiB/s, CPU 26%, speed 100% [SnapRAID 9.2] + * 2 - 426 MiB/s, CPU 46%, speed 112% + * 4 - 452 MiB/s, CPU 54%, speed 118% + * 8 - 487 MiB/s, CPU 60%, speed 128% + * 16 - 505 MiB/s, CPU 63%, speed 132% + * 32 - 520 MiB/s, CPU 64%, speed 136% + * 64 - 524 MiB/s, CPU 65%, speed 137% + * 128 - 525 MiB/s, CPU 66%, speed 138% */ -#define IO_MAX 4 +#define IO_MAX 32 /** * State of the task. From b619b9bee133b7e4cba6dfde97a59aea2ee345a0 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sun, 3 Jan 2016 18:57:58 +0100 Subject: [PATCH 08/34] Use a faster memory test We have now many buffers to test, and we need a faster test. --- cmdline/util.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/cmdline/util.c b/cmdline/util.c index de789666..4a52fa8b 100644 --- a/cmdline/util.c +++ b/cmdline/util.c @@ -68,9 +68,39 @@ void* malloc_nofail_test(size_t size) return ptr; } +/** + * Fast memory test. + * + * It's similat at raid_mtest_vector() but faster because + * we have a lot of buffers to verify. + */ +static int fast_mtest_vector(int n, size_t size, void** vv) +{ + int i, j; + + /* test with all the bits */ + for (i = 0; i < 8; ++i) { + unsigned char value = 1 << i; + + /* fill first */ + memset(vv[0], value, size); + + /* copy to the next */ + for (j = 1; j < n; ++j) + memcpy(vv[j], vv[j-1], size); + + /* compare with the previous */ + for (j = 1; j <= n; ++j) + if (memcmp(vv[j % n], vv[j-1], size) != 0) + return -1; + } + + return 0; +} + void mtest_vector(int n, size_t size, void** vv) { - if (raid_mtest_vector(n, size, vv) != 0) { + if (fast_mtest_vector(n, size, vv) != 0) { /* LCOV_EXCL_START */ log_fatal("DANGER! Your RAM memory is broken! DO NOT PROCEED UNTIL FIXED!\n"); log_fatal("Try running a memory test like http://www.memtest86.com/\n"); From 0849bfc261bb97ebf848799791cef522cce13535 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sun, 3 Jan 2016 20:15:28 +0100 Subject: [PATCH 09/34] Use a thread-safe pread/pwrite implementation for Windows This may be useful in future for concurrent calls to the same file. --- cmdline/mingw.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cmdline/mingw.c b/cmdline/mingw.c index 98db7915..f9d651d6 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -1760,7 +1760,7 @@ off_t windows_lseek(int fd, off_t offset, int whence) ssize_t windows_pread(int fd, void* buffer, size_t size, off_t offset) { HANDLE h; - LARGE_INTEGER pos; + OVERLAPPED overlapped; DWORD count; if (fd == -1) { @@ -1775,13 +1775,13 @@ ssize_t windows_pread(int fd, void* buffer, size_t size, off_t offset) } retry: - pos.QuadPart = offset; - if (!SetFilePointerEx(h, pos, 0, FILE_BEGIN)) { - windows_errno(GetLastError()); - return -1; - } + overlapped.Internal = 0; + overlapped.InternalHigh = 0; + overlapped.Offset = offset & 0xFFFFFFFF; + overlapped.OffsetHigh = offset >> 32; + overlapped.hEvent = 0; - if (!ReadFile(h, buffer, size, &count, 0)) { + if (!ReadFile(h, buffer, size, &count, &overlapped)) { DWORD err = GetLastError(); /* * If Windows is not able to allocate memory from the PagedPool for the disk cache @@ -1818,7 +1818,7 @@ retry: ssize_t windows_pwrite(int fd, const void* buffer, size_t size, off_t offset) { HANDLE h; - LARGE_INTEGER pos; + OVERLAPPED overlapped; DWORD count; if (fd == -1) { @@ -1833,13 +1833,13 @@ ssize_t windows_pwrite(int fd, const void* buffer, size_t size, off_t offset) } retry: - pos.QuadPart = offset; - if (!SetFilePointerEx(h, pos, 0, FILE_BEGIN)) { - windows_errno(GetLastError()); - return -1; - } + overlapped.Internal = 0; + overlapped.InternalHigh = 0; + overlapped.Offset = offset & 0xFFFFFFFF; + overlapped.OffsetHigh = offset >> 32; + overlapped.hEvent = 0; - if (!WriteFile(h, buffer, size, &count, 0)) { + if (!WriteFile(h, buffer, size, &count, &overlapped)) { DWORD err = GetLastError(); /* See windows_pread() for comments on this error management */ if (err == ERROR_NO_SYSTEM_RESOURCES) { From d9f7657107c64ec6b6a110250586a0eb5c996a8e Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Mon, 4 Jan 2016 11:32:02 +0100 Subject: [PATCH 10/34] Remove the unnecessary parity_ptr variable in sync --- cmdline/sync.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/cmdline/sync.c b/cmdline/sync.c index d38d2432..e5da297d 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -615,7 +615,7 @@ static void sync_data_reader(struct snapraid_worker* worker, struct snapraid_tas task->state = TASK_STATE_DONE; } -static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax) +static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax) { struct snapraid_io io; struct snapraid_plan plan; @@ -988,7 +988,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* we are sure that parity exists because */ /* we have at least one BLK block */ for (l = 0; l < state->level; ++l) { - ret = parity_read(parity[l], blockcur, buffer[diskmax + l], state->block_size, log_error); + ret = parity_read(&parity_handle[l], blockcur, buffer[diskmax + l], state->block_size, log_error); if (ret == -1) { /* LCOV_EXCL_START */ if (errno == EIO) { @@ -1083,7 +1083,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* write the parity */ for (l = 0; l < state->level; ++l) { - ret = parity_write(parity[l], blockcur, buffer[diskmax + l], state->block_size); + ret = parity_write(&parity_handle[l], blockcur, buffer[diskmax + l], state->block_size); if (ret == -1) { /* LCOV_EXCL_START */ if (errno == EIO) { @@ -1208,7 +1208,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* before writing the new content file we ensure that */ /* the parity is really written flushing the disk cache */ for (l = 0; l < state->level; ++l) { - ret = parity_sync(parity[l]); + ret = parity_sync(&parity_handle[l]); if (ret == -1) { /* LCOV_EXCL_START */ log_tag("parity_error:%u:%s: Sync error\n", blockcur, lev_config_name(l)); @@ -1239,7 +1239,7 @@ end: /* before returning we ensure that */ /* the parity is really written flushing the disk cache */ for (l = 0; l < state->level; ++l) { - ret = parity_sync(parity[l]); + ret = parity_sync(&parity_handle[l]); if (ret == -1) { /* LCOV_EXCL_START */ log_tag("parity_error:%u:%s: Sync error\n", blockcur, lev_config_name(l)); @@ -1326,10 +1326,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t data_off_t size; data_off_t out_size; int ret; - struct snapraid_parity_handle parity[LEV_MAX]; - /* the following initialization is to avoid clang warnings about */ - /* potential state->level change, that never happens */ - struct snapraid_parity_handle* parity_ptr[LEV_MAX] = { 0 }; + struct snapraid_parity_handle parity_handle[LEV_MAX]; unsigned unrecoverable_error; unsigned l; int skip_sync = 0; @@ -1361,8 +1358,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t block_off_t parityblocks; /* create the file and open for writing */ - parity_ptr[l] = &parity[l]; - ret = parity_create(parity_ptr[l], l, state->parity[l].path, &out_size, state->file_mode); + ret = parity_create(&parity_handle[l], l, state->parity[l].path, &out_size, state->file_mode); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("WARNING! Without an accessible %s file, it isn't possible to sync.\n", lev_name(l)); @@ -1408,7 +1404,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t /* change the size of the parity file, truncating or extending it */ /* from this point all the DELETED blocks after the end of the parity are invalid */ /* and they are automatically removed when we save the new content file */ - ret = parity_chsize(parity_ptr[l], size, &out_size, state->opt.skip_fallocate); + ret = parity_chsize(&parity_handle[l], size, &out_size, state->opt.skip_fallocate); if (ret == -1) { /* LCOV_EXCL_START */ parity_overflow(state, out_size); @@ -1444,7 +1440,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t /* skip degenerated cases of empty parity, or skipping all */ if (blockstart < blockmax) { - ret = state_sync_process(state, parity_ptr, blockstart, blockmax); + ret = state_sync_process(state, parity_handle, blockstart, blockmax); if (ret == -1) { /* LCOV_EXCL_START */ ++unrecoverable_error; @@ -1457,7 +1453,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t } for (l = 0; l < state->level; ++l) { - ret = parity_close(parity_ptr[l]); + ret = parity_close(&parity_handle[l]); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("DANGER! Unexpected close error in %s disk.\n", lev_name(l)); From 62b7587c3a568bb444d20030c0f1f4f03fd913a4 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Mon, 4 Jan 2016 11:24:04 +0100 Subject: [PATCH 11/34] IO functions renaming to prepare for parity write support No functional change. --- cmdline/io.c | 143 +++++++++++++++++++++++++++--------------------- cmdline/io.h | 54 ++++++++++-------- cmdline/scrub.c | 6 +- cmdline/sync.c | 4 +- 4 files changed, 116 insertions(+), 91 deletions(-) diff --git a/cmdline/io.c b/cmdline/io.c index f8a7d422..79ce0e2e 100644 --- a/cmdline/io.c +++ b/cmdline/io.c @@ -28,8 +28,8 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff unsigned i; pthread_mutex_init(&io->mutex, 0); - pthread_cond_init(&io->not_empty, 0); - pthread_cond_init(&io->not_full, 0); + pthread_cond_init(&io->read_done, 0); + pthread_cond_init(&io->read_sched, 0); io->state = state; @@ -40,21 +40,22 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff mtest_vector(io->buffer_max, state->block_size, io->buffer_map[i]); } - io->worker_max = handle_max + parity_handle_max; - io->worker_map = malloc_nofail(sizeof(struct snapraid_worker) * io->worker_max); - io->worker_list = malloc_nofail(io->worker_max + 1); + io->reader_max = handle_max + parity_handle_max; + io->reader_map = malloc_nofail(sizeof(struct snapraid_worker) * io->reader_max); + io->reader_list = malloc_nofail(io->reader_max + 1); io->data_base = 0; io->data_count = handle_max; io->parity_base = handle_max; io->parity_count = parity_handle_max; - for (i = 0; i < io->worker_max; ++i) { - struct snapraid_worker* worker = &io->worker_map[i]; + for (i = 0; i < io->reader_max; ++i) { + struct snapraid_worker* worker = &io->reader_map[i]; worker->io = io; if (i < handle_max) { + /* it's a data read */ worker->handle = &handle_map[i]; worker->parity_handle = 0; worker->func = data_reader; @@ -62,6 +63,7 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff /* data read is put in lower buffer index */ worker->buffer_skew = 0; } else { + /* it's a parity read */ worker->handle = 0; worker->parity_handle = &parity_handle_map[i - handle_max]; worker->func = parity_reader; @@ -81,12 +83,12 @@ void io_done(struct snapraid_io* io) free(io->buffer_alloc_map[i]); } - free(io->worker_map); - free(io->worker_list); + free(io->reader_map); + free(io->reader_list); pthread_mutex_destroy(&io->mutex); - pthread_cond_destroy(&io->not_empty); - pthread_cond_destroy(&io->not_full); + pthread_cond_destroy(&io->read_done); + pthread_cond_destroy(&io->read_sched); } /** @@ -109,14 +111,14 @@ static block_off_t io_position_next(struct snapraid_io* io) } /** - * Setup the next pending task for all workers. + * Setup the next pending task for all readers. */ -static void io_pending_next(struct snapraid_io* io, int index, block_off_t blockcur) +static void io_reader_sched(struct snapraid_io* io, int index, block_off_t blockcur) { unsigned i; - for (i = 0; i < io->worker_max; ++i) { - struct snapraid_worker* worker = &io->worker_map[i]; + for (i = 0; i < io->reader_max; ++i) { + struct snapraid_worker* worker = &io->reader_map[i]; struct snapraid_task* task = &worker->task_map[index]; /* setup the new pending task */ @@ -141,11 +143,11 @@ static void io_pending_next(struct snapraid_io* io, int index, block_off_t block } /** - * Get the next task to work on for a worker. + * Get the next task to work on for a reader. * * This is the synchronization point for workers with the io. */ -static struct snapraid_task* io_worker_next(struct snapraid_worker* worker) +static struct snapraid_task* io_reader_step(struct snapraid_worker* worker) { struct snapraid_io* io = worker->io; struct snapraid_task* task = 0; @@ -157,6 +159,7 @@ static struct snapraid_task* io_worker_next(struct snapraid_worker* worker) unsigned index; /* check if the worker has to exit */ + /* even if there is work to do */ if (io->done) break; @@ -164,20 +167,20 @@ static struct snapraid_task* io_worker_next(struct snapraid_worker* worker) index = (worker->index + 1) % IO_MAX; /* if the queue of pending tasks is not empty */ - if (index != io->index) { + if (index != io->reader_index) { /* get the new working task */ worker->index = index; task = &worker->task_map[worker->index]; - /* notify the io that we started working */ - pthread_cond_signal(&io->not_empty); + /* notify the io that a new read is complete */ + pthread_cond_signal(&io->read_done); /* return the new task */ break; } - /* otherwise wait for a not_full event */ - pthread_cond_wait(&io->not_full, &io->mutex); + /* otherwise wait for a read_sched event */ + pthread_cond_wait(&io->read_sched, &io->mutex); } pthread_mutex_unlock(&io->mutex); @@ -190,75 +193,85 @@ static struct snapraid_task* io_worker_next(struct snapraid_worker* worker) * * This is the synchronization point for workers with the io. */ -block_off_t io_next(struct snapraid_io* io, void*** buffer) +block_off_t io_read_next(struct snapraid_io* io, void*** buffer) { block_off_t blockcur_schedule; block_off_t blockcur_caller; unsigned i; + /* get the next parity position to process */ blockcur_schedule = io_position_next(io); + /* ensure that all data/parity was read */ + assert(io->reader_list[0] == io->reader_max); + /* setup the list of workers to process */ - for (i = 0; i <= io->worker_max; ++i) - io->worker_list[i] = i; + for (i = 0; i <= io->reader_max; ++i) + io->reader_list[i] = i; /* the synchronization is protected by the io mutex */ pthread_mutex_lock(&io->mutex); - /* reset the task just used to be the next pending task */ - io_pending_next(io, io->index, blockcur_schedule); + /* schedule the next read */ + io_reader_sched(io, io->reader_index, blockcur_schedule); /* get the next task to return to the caller */ - io->index = (io->index + 1) % IO_MAX; + io->reader_index = (io->reader_index + 1) % IO_MAX; /* get the position to operate at high level from one task */ - blockcur_caller = io->worker_map[0].task_map[io->index].position; + blockcur_caller = io->reader_map[0].task_map[io->reader_index].position; /* set the buffer to use */ - *buffer = io->buffer_map[io->index]; + *buffer = io->buffer_map[io->reader_index]; /* signal all the workers that there is a new pending task */ - pthread_cond_broadcast(&io->not_full); + pthread_cond_broadcast(&io->read_sched); pthread_mutex_unlock(&io->mutex); return blockcur_caller; } -static struct snapraid_task* io_task_next(struct snapraid_io* io, unsigned base, unsigned count, unsigned* pos) +static struct snapraid_task* io_task_read(struct snapraid_io* io, unsigned base, unsigned count, unsigned* pos) { /* the synchronization is protected by the io mutex */ pthread_mutex_lock(&io->mutex); while (1) { unsigned char* let; + unsigned index; + + /* get the index the caller is using */ + /* we must ensure that this index has not a read in progress */ + /* to avoid a concurrent access */ + index = io->reader_index; /* search for a worker that has already finished */ - let = &io->worker_list[0]; + let = &io->reader_list[0]; while (1) { unsigned i = *let; /* if we are at the end */ - if (i == io->worker_max) + if (i == io->reader_max) break; /* if it's in range */ if (base <= i && i < base + count) { struct snapraid_worker* worker; - worker = &io->worker_map[i]; + worker = &io->reader_map[i]; /* if the worker has finished this index */ - if (io->index != worker->index) { + if (index != worker->index) { struct snapraid_task* task; - task = &worker->task_map[io->index]; + task = &worker->task_map[io->reader_index]; pthread_mutex_unlock(&io->mutex); /* mark the worker as processed */ /* setting the previous one to point at the next one */ - *let = io->worker_list[i + 1]; + *let = io->reader_list[i + 1]; /* return the position */ *pos = i - base; @@ -268,46 +281,47 @@ static struct snapraid_task* io_task_next(struct snapraid_io* io, unsigned base, } /* next position to check */ - let = &io->worker_list[i + 1]; + let = &io->reader_list[i + 1]; } /* if not worker is ready, wait for an event */ - pthread_cond_wait(&io->not_empty, &io->mutex); + pthread_cond_wait(&io->read_done, &io->mutex); } } -struct snapraid_task* io_data_next(struct snapraid_io* io, unsigned* pos) +struct snapraid_task* io_data_read(struct snapraid_io* io, unsigned* pos) { - return io_task_next(io, io->data_base, io->data_count, pos); + return io_task_read(io, io->data_base, io->data_count, pos); } -struct snapraid_task* io_parity_next(struct snapraid_io* io, unsigned* pos) +struct snapraid_task* io_parity_read(struct snapraid_io* io, unsigned* pos) { - return io_task_next(io, io->parity_base, io->parity_count, pos); + return io_task_read(io, io->parity_base, io->parity_count, pos); } -static void io_worker(struct snapraid_worker* worker, struct snapraid_task* task) +static void io_reader_worker(struct snapraid_worker* worker, struct snapraid_task* task) { /* if we reached the end */ if (task->position >= worker->io->block_max) { + /* complete a dummy task */ task->state = TASK_STATE_EMPTY; } else { worker->func(worker, task); } } -static void* io_thread(void* arg) +static void* io_reader_thread(void* arg) { struct snapraid_worker* worker = arg; /* force completion of the first task */ - io_worker(worker, &worker->task_map[0]); + io_reader_worker(worker, &worker->task_map[0]); while (1) { struct snapraid_task* task; /* get the new task */ - task = io_worker_next(worker); + task = io_reader_step(worker); /* if no task, it means to exit */ if (!task) @@ -320,7 +334,7 @@ static void* io_thread(void* arg) assert(task->state == TASK_STATE_READY); /* work on the assigned task */ - io_worker(worker, task); + io_reader_worker(worker, task); } return 0; @@ -339,25 +353,28 @@ void io_start(struct snapraid_io* io, io->block_next = blockstart; io->done = 0; - io->index = IO_MAX - 1; + io->reader_index = IO_MAX - 1; - /* setup the initial pending tasks, except the latest one, */ - /* the latest will be initialized at the fist io_next() call */ + /* setup the initial read pending tasks, except the latest one, */ + /* the latest will be initialized at the fist io_read_next() call */ for (i = 0; i < IO_MAX - 1; ++i) { block_off_t blockcur = io_position_next(io); - io_pending_next(io, i, blockcur); + io_reader_sched(io, i, blockcur); } - /* start the worker threads */ - for (i = 0; i < io->worker_max; ++i) { - struct snapraid_worker* worker = &io->worker_map[i]; + /* setup the lists of workers to process */ + io->reader_list[0] = io->reader_max; + + /* start the reader threads */ + for (i = 0; i < io->reader_max; ++i) { + struct snapraid_worker* worker = &io->reader_map[i]; worker->index = 0; - if (pthread_create(&worker->thread, 0, io_thread, worker) != 0) { + if (pthread_create(&worker->thread, 0, io_reader_thread, worker) != 0) { /* LCOV_EXCL_START */ - log_fatal("Failed to create thread.\n"); + log_fatal("Failed to create reader thread.\n"); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } @@ -374,19 +391,19 @@ void io_stop(struct snapraid_io* io) io->done = 1; /* signal all the threads to recognize the new state */ - pthread_cond_broadcast(&io->not_full); + pthread_cond_broadcast(&io->read_sched); pthread_mutex_unlock(&io->mutex); - /* wait for all threads to terminate */ - for (i = 0; i < io->worker_max; ++i) { - struct snapraid_worker* worker = &io->worker_map[i]; + /* wait for all readers to terminate */ + for (i = 0; i < io->reader_max; ++i) { + struct snapraid_worker* worker = &io->reader_map[i]; void* retval; /* wait for thread termination */ if (pthread_join(worker->thread, &retval) != 0) { /* LCOV_EXCL_START */ - log_fatal("Failed to join thread.\n"); + log_fatal("Failed to join reader thread.\n"); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } diff --git a/cmdline/io.h b/cmdline/io.h index 9514ada0..94ba58cd 100644 --- a/cmdline/io.h +++ b/cmdline/io.h @@ -134,20 +134,22 @@ struct snapraid_io { pthread_mutex_t mutex; /** - * Condition for (not) empty queue of tasks. + * Condition for a new read is completed. * - * The io waits on this condition when the queue is empty, - * and it's signaled by workers. + * The workers signal this condition when a new read is completed. + * The IO waits on this condition when it's waiting for + * a new read to be completed. */ - pthread_cond_t not_empty; + pthread_cond_t read_done; /** - * Condition for (not) full queue of tasks. + * Condition for a new read scheduled. * - * The workers wait on this condition when the queue is full, - * and it's signaled by the io. + * The workers wait on this condition when they are waiting for a new + * read to process. + * The IO signals this condition when new reads are scheduled. */ - pthread_cond_t not_full; + pthread_cond_t read_sched; struct snapraid_state* state; @@ -191,22 +193,22 @@ struct snapraid_io { /** * Workers. * - * A vector of workers, each one representing a different thread. + * A vector of readers, each one representing a different thread. */ - unsigned worker_max; /**< Number of workers. */ - struct snapraid_worker* worker_map; /**< Vector of workers. */ + unsigned reader_max; /**< Number of workers. */ + struct snapraid_worker* reader_map; /**< Vector of workers. */ /** * List of not yet processed workers. * - * The list has ::worker_max + 1 elements. Each element contains - * the number of the worker to process. + * The list has ::reader_max + 1 elements. Each element contains + * the number of the reader to process. * - * At initialization the list is filled with [0..worker_max]. + * At initialization the list is filled with [0..reader_max]. * To get the next element to process we use i = list[i + 1]. - * The end is when i == worker_max. + * The end is when i == reader_max. */ - unsigned char* worker_list; + unsigned char* reader_list; /** * Exit condition for all threads. @@ -218,8 +220,11 @@ struct snapraid_io { * * It's a rolling counter, when reaching IO_MAX * it goes again to 0. + * + * When the caller finish with the current index, + * it's incremented, and a read_sched() signal is sent. */ - unsigned index; + unsigned reader_index; }; /** @@ -252,16 +257,19 @@ void io_start(struct snapraid_io* io, void io_stop(struct snapraid_io* io); /** - * Get the next parity position to process. + * Next read position. + * + * This call starts the reading process. + * It must be called before io_data_read() and io_parity_read(). * * \param io InputOutput context. * \param buffer The data buffers to use for this position. * \return The parity position. */ -block_off_t io_next(struct snapraid_io* io, void*** buffer); +block_off_t io_read_next(struct snapraid_io* io, void*** buffer); /** - * Get the next data block to process. + * Read a data block. * * It must be called exactly ::handle_max times. * @@ -269,10 +277,10 @@ block_off_t io_next(struct snapraid_io* io, void*** buffer); * \param pos The position of the data block in the ::handle_map vector. * \return The completed task. */ -struct snapraid_task* io_data_next(struct snapraid_io* io, unsigned* pos); +struct snapraid_task* io_data_read(struct snapraid_io* io, unsigned* diskcur); /** - * Get the next parity block to process. + * Read a parity block. * * It must be called exactly ::parity_handle_max times. * @@ -280,6 +288,6 @@ struct snapraid_task* io_data_next(struct snapraid_io* io, unsigned* pos); * \param pos The position of the parity block in the ::parity_handle_map vector. * \return The completed task. */ -struct snapraid_task* io_parity_next(struct snapraid_io* io, unsigned* pos); +struct snapraid_task* io_parity_read(struct snapraid_io* io, unsigned* levcur); #endif diff --git a/cmdline/scrub.c b/cmdline/scrub.c index fccedd69..4b5da24d 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -319,7 +319,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par void** buffer; /* go to the next block */ - blockcur = io_next(&io, &buffer); + blockcur = io_read_next(&io, &buffer); if (blockcur >= blockmax) break; @@ -362,7 +362,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par state_usage_cpu(state); /* get the next task */ - task = io_data_next(&io, &diskcur); + task = io_data_read(&io, &diskcur); /* get the task results */ disk = task->disk; @@ -485,7 +485,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par struct snapraid_task* task; unsigned levcur; - task = io_parity_next(&io, &levcur); + task = io_parity_read(&io, &levcur); /* until now is parity */ state_usage_parity(state, levcur); diff --git a/cmdline/sync.c b/cmdline/sync.c index e5da297d..e8785314 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -715,7 +715,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari void** buffer; /* go to the next block */ - blockcur = io_next(&io, &buffer); + blockcur = io_read_next(&io, &buffer); if (blockcur >= blockmax) break; @@ -766,7 +766,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* until now is CPU */ state_usage_cpu(state); - task = io_data_next(&io, &diskcur); + task = io_data_read(&io, &diskcur); /* get the results */ disk = task->disk; From 18cc80bd7eccd21c6445c146e9aedb8cd595c712 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Mon, 4 Jan 2016 17:53:04 +0100 Subject: [PATCH 12/34] Now pread and pwrite use is required for multithread We need to support concurrent access at the same file handle from multiple threads, and they cannot share the same file pointer. --- cmdline/handle.c | 26 -------------------------- cmdline/mingw.h | 2 -- cmdline/parity.c | 29 ----------------------------- configure.ac | 2 +- 4 files changed, 1 insertion(+), 58 deletions(-) diff --git a/cmdline/handle.c b/cmdline/handle.c index 531ba1e7..69e9f386 100644 --- a/cmdline/handle.c +++ b/cmdline/handle.c @@ -272,24 +272,9 @@ int handle_read(struct snapraid_handle* handle, block_off_t file_pos, unsigned c read_size = file_block_size(handle->file, file_pos, block_size); -#if !HAVE_PREAD - if (lseek(handle->f, offset, SEEK_SET) != offset) { - /* LCOV_EXCL_START */ - out("Error seeking file '%s' at offset %" PRIu64 ". %s.\n", handle->path, offset, strerror(errno)); - return -1; - /* LCOV_EXCL_STOP */ - } -#endif - count = 0; do { - -#if HAVE_PREAD read_ret = pread(handle->f, block_buffer + count, read_size - count, offset + count); -#else - read_ret = read(handle->f, block_buffer + count, read_size - count); -#endif - if (read_ret < 0) { /* LCOV_EXCL_START */ out("Error reading file '%s' at offset %" PRIu64 " for size %u. %s.\n", handle->path, offset + count, read_size - count, strerror(errno)); @@ -333,18 +318,7 @@ int handle_write(struct snapraid_handle* handle, block_off_t file_pos, unsigned write_size = file_block_size(handle->file, file_pos, block_size); -#if HAVE_PWRITE write_ret = pwrite(handle->f, block_buffer, write_size, offset); -#else - if (lseek(handle->f, offset, SEEK_SET) != offset) { - /* LCOV_EXCL_START */ - log_fatal("Error seeking file '%s'. %s.\n", handle->path, strerror(errno)); - return -1; - /* LCOV_EXCL_STOP */ - } - - write_ret = write(handle->f, block_buffer, write_size); -#endif if (write_ret != (ssize_t)write_size) { /* conversion is safe because block_size is always small */ /* LCOV_EXCL_START */ log_fatal("Error writing file '%s'. %s.\n", handle->path, strerror(errno)); diff --git a/cmdline/mingw.h b/cmdline/mingw.h index 21f309e1..aeb34def 100644 --- a/cmdline/mingw.h +++ b/cmdline/mingw.h @@ -109,8 +109,6 @@ #define write windows_write #undef lseek #define lseek windows_lseek -#define HAVE_PREAD 1 -#define HAVE_PWRITE 1 #undef pread #define pread windows_pread #undef pwrite diff --git a/cmdline/parity.c b/cmdline/parity.c index 92d2db8c..ab7ce9ac 100644 --- a/cmdline/parity.c +++ b/cmdline/parity.c @@ -433,22 +433,7 @@ int parity_write(struct snapraid_parity_handle* parity, block_off_t pos, unsigne offset = pos * (data_off_t)block_size; -#if HAVE_PWRITE write_ret = pwrite(parity->f, block_buffer, block_size, offset); -#else - if (lseek(parity->f, offset, SEEK_SET) != offset) { - /* LCOV_EXCL_START */ - if (errno == ENOSPC) { - log_fatal("Failed to grow parity file '%s' using lseek due lack of space.\n", parity->path); - } else { - log_fatal("Error seeking file '%s'. %s.\n", parity->path, strerror(errno)); - } - return -1; - /* LCOV_EXCL_STOP */ - } - - write_ret = write(parity->f, block_buffer, block_size); -#endif if (write_ret != (ssize_t)block_size) { /* conversion is safe because block_size is always small */ /* LCOV_EXCL_START */ if (errno == ENOSPC) { @@ -485,23 +470,9 @@ int parity_read(struct snapraid_parity_handle* parity, block_off_t pos, unsigned return -1; } -#if !HAVE_PREAD - if (lseek(parity->f, offset, SEEK_SET) != offset) { - /* LCOV_EXCL_START */ - out("Error seeking file '%s' at offset %" PRIu64 ". %s.\n", parity->path, offset, strerror(errno)); - return -1; - /* LCOV_EXCL_STOP */ - } -#endif - count = 0; do { - -#if HAVE_PREAD read_ret = pread(parity->f, block_buffer + count, block_size - count, offset + count); -#else - read_ret = read(parity->f, block_buffer + count, block_size - count); -#endif if (read_ret < 0) { /* LCOV_EXCL_START */ out("Error reading file '%s' at offset %" PRIu64 " for size %u. %s.\n", parity->path, offset + count, block_size - count, strerror(errno)); diff --git a/configure.ac b/configure.ac index 03244d1b..b5adb42f 100644 --- a/configure.ac +++ b/configure.ac @@ -82,7 +82,7 @@ dnl Checks for library functions. AC_CHECK_FUNCS([memset strchr strerror strrchr mkdir gettimeofday strtoul]) AC_CHECK_FUNCS([getopt getopt_long snprintf vsnprintf sigaction]) AC_CHECK_FUNCS([ftruncate fallocate fsync access posix_fallocate posix_fadvise]) -AC_CHECK_FUNCS([pread pwrite getc_unlocked ferror_unlocked fnmatch]) +AC_CHECK_FUNCS([getc_unlocked ferror_unlocked fnmatch]) AC_CHECK_FUNCS([futimes futimens futimesat localtime_r]) AC_CHECK_FUNCS([fstatat flock statfs]) AC_CHECK_FUNCS([mach_absolute_time]) From e69e8640ce523c3ee332bb10a70ea47ef143b04b Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Mon, 4 Jan 2016 17:57:51 +0100 Subject: [PATCH 13/34] Use pread also in import and search --- cmdline/import.c | 9 +-------- cmdline/search.c | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/cmdline/import.c b/cmdline/import.c index 0741dd63..c878fb84 100644 --- a/cmdline/import.c +++ b/cmdline/import.c @@ -185,14 +185,7 @@ int state_import_fetch(struct snapraid_state* state, int rehash, struct snapraid /* LCOV_EXCL_STOP */ } - if (lseek(f, block->offset, SEEK_SET) != block->offset) { - /* LCOV_EXCL_START */ - log_fatal("Error seeking file '%s'. %s.\n", path, strerror(errno)); - exit(EXIT_FAILURE); - /* LCOV_EXCL_STOP */ - } - - ret = read(f, buffer, read_size); + ret = pread(f, buffer, read_size, block->offset); if (ret < 0 || (unsigned)ret != read_size) { /* LCOV_EXCL_START */ log_fatal("Error reading file '%s'. %s.\n", path, strerror(errno)); diff --git a/cmdline/search.c b/cmdline/search.c index 09a26283..357e0498 100644 --- a/cmdline/search.c +++ b/cmdline/search.c @@ -89,14 +89,7 @@ int search_file_compare(const void* void_arg, const void* void_data) /* LCOV_EXCL_STOP */ } - if (lseek(f, arg->offset, SEEK_SET) != arg->offset) { - /* LCOV_EXCL_START */ - log_fatal("Error seeking file '%s'. %s.\n", path, strerror(errno)); - exit(EXIT_FAILURE); - /* LCOV_EXCL_STOP */ - } - - ret = read(f, arg->buffer, arg->read_size); + ret = pread(f, arg->buffer, arg->read_size, arg->offset); if (ret < 0 || (unsigned)ret != arg->read_size) { /* LCOV_EXCL_START */ log_fatal("Error reading file '%s'. %s.\n", path, strerror(errno)); From a3121c31c58b60fb41ebe4b05abdfbaf0be457a1 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Mon, 4 Jan 2016 11:28:34 +0100 Subject: [PATCH 14/34] Add support for multithread parity writing in sync --- cmdline/io.c | 275 +++++++++++++++++++++++++++++++++++++++++++++++- cmdline/io.h | 56 ++++++++++ cmdline/scrub.c | 2 +- cmdline/sync.c | 143 ++++++++++++++----------- 4 files changed, 412 insertions(+), 64 deletions(-) diff --git a/cmdline/io.c b/cmdline/io.c index 79ce0e2e..4e1c7b77 100644 --- a/cmdline/io.c +++ b/cmdline/io.c @@ -23,6 +23,7 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff void (*data_reader)(struct snapraid_worker*, struct snapraid_task*), struct snapraid_handle* handle_map, unsigned handle_max, void (*parity_reader)(struct snapraid_worker*, struct snapraid_task*), + void (*parity_writer)(struct snapraid_worker*, struct snapraid_task*), struct snapraid_parity_handle* parity_handle_map, unsigned parity_handle_max) { unsigned i; @@ -30,6 +31,8 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff pthread_mutex_init(&io->mutex, 0); pthread_cond_init(&io->read_done, 0); pthread_cond_init(&io->read_sched, 0); + pthread_cond_init(&io->write_done, 0); + pthread_cond_init(&io->write_sched, 0); io->state = state; @@ -40,9 +43,18 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff mtest_vector(io->buffer_max, state->block_size, io->buffer_map[i]); } - io->reader_max = handle_max + parity_handle_max; + if (parity_writer) { + io->reader_max = handle_max; + io->writer_max = parity_handle_max; + } else { + io->reader_max = handle_max + parity_handle_max; + io->writer_max = 0; + } + io->reader_map = malloc_nofail(sizeof(struct snapraid_worker) * io->reader_max); io->reader_list = malloc_nofail(io->reader_max + 1); + io->writer_map = malloc_nofail(sizeof(struct snapraid_worker) * io->writer_max); + io->writer_list = malloc_nofail(io->writer_max + 1); io->data_base = 0; io->data_count = handle_max; @@ -72,6 +84,20 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff worker->buffer_skew = parity_handle_max; } } + + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + + worker->io = io; + + /* it's a parity write */ + worker->handle = 0; + worker->parity_handle = &parity_handle_map[i]; + worker->func = parity_writer; + + /* parity to write is put after data */ + worker->buffer_skew = handle_max; + } } void io_done(struct snapraid_io* io) @@ -85,10 +111,14 @@ void io_done(struct snapraid_io* io) free(io->reader_map); free(io->reader_list); + free(io->writer_map); + free(io->writer_list); pthread_mutex_destroy(&io->mutex); pthread_cond_destroy(&io->read_done); pthread_cond_destroy(&io->read_sched); + pthread_cond_destroy(&io->write_done); + pthread_cond_destroy(&io->write_sched); } /** @@ -142,6 +172,57 @@ static void io_reader_sched(struct snapraid_io* io, int index, block_off_t block } } +/** + * Setup the next pending task for all writers. + */ +static void io_writer_sched(struct snapraid_io* io, int index, block_off_t blockcur) +{ + unsigned i; + + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + struct snapraid_task* task = &worker->task_map[index]; + + /* setup the new pending task */ + task->state = TASK_STATE_READY; + task->path[0] = 0; + task->disk = 0; + task->buffer = io->buffer_map[index][worker->buffer_skew + i]; + task->position = blockcur; + task->block = 0; + task->file = 0; + task->file_pos = 0; + task->read_size = 0; + task->is_timestamp_different = 0; + } +} + +/** + * Setup an empty next pending task for all writers. + */ +static void io_writer_sched_empty(struct snapraid_io* io, int index, block_off_t blockcur) +{ + unsigned i; + + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + struct snapraid_task* task = &worker->task_map[index]; + + /* setup the new pending task */ + task->state = TASK_STATE_EMPTY; + task->path[0] = 0; + task->disk = 0; + task->buffer = 0; + task->position = blockcur; + task->block = 0; + task->file = 0; + task->file_pos = 0; + task->read_size = 0; + task->is_timestamp_different = 0; + } +} + + /** * Get the next task to work on for a reader. * @@ -188,6 +269,52 @@ static struct snapraid_task* io_reader_step(struct snapraid_worker* worker) return task; } +/** + * Get the next task to work on for a writer. + * + * This is the synchronization point for workers with the io. + */ +static struct snapraid_task* io_writer_step(struct snapraid_worker* worker) +{ + struct snapraid_io* io = worker->io; + struct snapraid_task* task = 0; + + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + while (1) { + unsigned index; + + /* get the next pending task */ + index = (worker->index + 1) % IO_MAX; + + /* if the queue of pending tasks is not empty */ + if (index != io->writer_index) { + /* get the new working task */ + worker->index = index; + task = &worker->task_map[worker->index]; + + /* notify the io that a new writer is complete */ + pthread_cond_signal(&io->write_done); + + /* return the new task */ + break; + } + + /* check if the worker has to exit */ + /* but only if there is no work to do */ + if (io->done) + break; + + /* otherwise wait for a write_sched event */ + pthread_cond_wait(&io->write_sched, &io->mutex); + } + + pthread_mutex_unlock(&io->mutex); + + return task; +} + /** * Get the next block position to operate on. * @@ -232,6 +359,39 @@ block_off_t io_read_next(struct snapraid_io* io, void*** buffer) return blockcur_caller; } +void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip) +{ + unsigned i; + + /* ensure that all parity was written */ + assert(io->writer_list[0] == io->writer_max); + + /* setup the list of workers to process */ + for (i = 0; i <= io->writer_max; ++i) + io->writer_list[i] = i; + + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + if (skip) { + /* skip the next write */ + io_writer_sched_empty(io, io->writer_index, blockcur); + } else { + /* schedule the next write */ + io_writer_sched(io, io->writer_index, blockcur); + } + + assert(io->writer_index == io->reader_index); + + /* get the next task to return to the caller */ + io->writer_index = (io->writer_index + 1) % IO_MAX; + + /* signal all the workers that there is a new pending task */ + pthread_cond_broadcast(&io->write_sched); + + pthread_mutex_unlock(&io->mutex); +} + static struct snapraid_task* io_task_read(struct snapraid_io* io, unsigned base, unsigned count, unsigned* pos) { /* the synchronization is protected by the io mutex */ @@ -299,6 +459,60 @@ struct snapraid_task* io_parity_read(struct snapraid_io* io, unsigned* pos) return io_task_read(io, io->parity_base, io->parity_count, pos); } +void io_parity_write(struct snapraid_io* io, unsigned* pos) +{ + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + while (1) { + unsigned char* let; + unsigned index; + + /* get the next index the caller is going to use */ + /* we must ensure that this index has not a write in progress */ + /* to avoid a concurrent access */ + /* note that we are already sure that a write is not in progress */ + /* at the index the caller is using at now */ + index = (io->writer_index + 1) % IO_MAX; + + /* search for a worker that has already finished */ + let = &io->writer_list[0]; + while (1) { + unsigned i = *let; + struct snapraid_worker* worker; + + /* if we are at the end */ + if (i == io->writer_max) + break; + + worker = &io->writer_map[i]; + + /* the two indexes cannot be equal */ + assert(io->writer_index != worker->index); + + /* if the worker has finished this index */ + if (index != worker->index) { + pthread_mutex_unlock(&io->mutex); + + /* mark the worker as processed */ + /* setting the previous one to point at the next one */ + *let = io->writer_list[i + 1]; + + /* return the position */ + *pos = i; + + return; + } + + /* next position to check */ + let = &io->writer_list[i + 1]; + } + + /* if not worker is ready, wait for an event */ + pthread_cond_wait(&io->write_done, &io->mutex); + } +} + static void io_reader_worker(struct snapraid_worker* worker, struct snapraid_task* task) { /* if we reached the end */ @@ -340,6 +554,33 @@ static void* io_reader_thread(void* arg) return 0; } +static void* io_writer_thread(void* arg) +{ + struct snapraid_worker* worker = arg; + + while (1) { + struct snapraid_task* task; + + /* get the new task */ + task = io_writer_step(worker); + + /* if no task, it means to exit */ + if (!task) + break; + + /* nothing more to do */ + if (task->state == TASK_STATE_EMPTY) + continue; + + assert(task->state == TASK_STATE_READY); + + /* work on the assigned task */ + worker->func(worker, task); + } + + return 0; +} + void io_start(struct snapraid_io* io, block_off_t blockstart, block_off_t blockmax, int (*block_is_enabled)(void* arg,block_off_t), void* blockarg) @@ -354,6 +595,7 @@ void io_start(struct snapraid_io* io, io->done = 0; io->reader_index = IO_MAX - 1; + io->writer_index = 0; /* setup the initial read pending tasks, except the latest one, */ /* the latest will be initialized at the fist io_read_next() call */ @@ -365,6 +607,8 @@ void io_start(struct snapraid_io* io, /* setup the lists of workers to process */ io->reader_list[0] = io->reader_max; + for (i = 0; i <= io->writer_max; ++i) + io->writer_list[i] = i; /* start the reader threads */ for (i = 0; i < io->reader_max; ++i) { @@ -379,6 +623,20 @@ void io_start(struct snapraid_io* io, /* LCOV_EXCL_STOP */ } } + + /* start the writer threads */ + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + + worker->index = IO_MAX - 1; + + if (pthread_create(&worker->thread, 0, io_writer_thread, worker) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to create writer thread.\n"); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } } void io_stop(struct snapraid_io* io) @@ -392,6 +650,7 @@ void io_stop(struct snapraid_io* io) /* signal all the threads to recognize the new state */ pthread_cond_broadcast(&io->read_sched); + pthread_cond_broadcast(&io->write_sched); pthread_mutex_unlock(&io->mutex); @@ -408,5 +667,19 @@ void io_stop(struct snapraid_io* io) /* LCOV_EXCL_STOP */ } } + + /* wait for all writers to terminate */ + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + void* retval; + + /* wait for thread termination */ + if (pthread_join(worker->thread, &retval) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to join writer thread.\n"); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } } diff --git a/cmdline/io.h b/cmdline/io.h index 94ba58cd..da240a87 100644 --- a/cmdline/io.h +++ b/cmdline/io.h @@ -151,6 +151,24 @@ struct snapraid_io { */ pthread_cond_t read_sched; + /** + * Condition for a new write is completed. + * + * The workers signal this condition when a new write is completed. + * The IO waits on this condition when it's waiting for + * a new write to be completed. + */ + pthread_cond_t write_done; + + /** + * Condition for a new write scheduled. + * + * The workers wait on this condition when they are waiting for a new + * write to process. + * The IO signals this condition when new writes are scheduled. + */ + pthread_cond_t write_sched; + struct snapraid_state* state; /** @@ -168,6 +186,7 @@ struct snapraid_io { */ void (*data_reader)(struct snapraid_worker*, struct snapraid_task*); void (*parity_reader)(struct snapraid_worker*, struct snapraid_task*); + void (*parity_writer)(struct snapraid_worker*, struct snapraid_task*); /** * Blocks mapping. @@ -197,6 +216,8 @@ struct snapraid_io { */ unsigned reader_max; /**< Number of workers. */ struct snapraid_worker* reader_map; /**< Vector of workers. */ + unsigned writer_max; /**< Number of workers. */ + struct snapraid_worker* writer_map; /**< Vector of workers. */ /** * List of not yet processed workers. @@ -209,6 +230,7 @@ struct snapraid_io { * The end is when i == reader_max. */ unsigned char* reader_list; + unsigned char* writer_list; /** * Exit condition for all threads. @@ -225,6 +247,17 @@ struct snapraid_io { * it's incremented, and a read_sched() signal is sent. */ unsigned reader_index; + + /** + * The task currently used by the caller. + * + * It's a rolling counter, when reaching IO_MAX + * it goes again to 0. + * + * When the caller finish with the current index, + * it's incremented, and a write_sched() signal is sent. + */ + unsigned writer_index; }; /** @@ -237,6 +270,7 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, void (*data_reader)(struct snapraid_worker*, struct snapraid_task*), struct snapraid_handle* handle_map, unsigned handle_max, void (*parity_reader)(struct snapraid_worker*, struct snapraid_task*), + void (*parity_writer)(struct snapraid_worker*, struct snapraid_task*), struct snapraid_parity_handle* parity_handle_map, unsigned parity_handle_max); /** @@ -290,4 +324,26 @@ struct snapraid_task* io_data_read(struct snapraid_io* io, unsigned* diskcur); */ struct snapraid_task* io_parity_read(struct snapraid_io* io, unsigned* levcur); +/** + * Write of a parity block. + * + * It must be called exactly ::parity_handle_max times. + * + * \param io InputOutput context. + * \param pos The position of the parity block in the ::parity_handle_map vector. + */ +void io_parity_write(struct snapraid_io* io, unsigned* levcur); + +/** + * Next write position. + * + * This call ends the write process. + * It must be called after io_parity_write(). + * + * \param io InputOutput context. + * \param blockcur The parity position to write. + * \param skip Skip the writes, in case parity doesn't need to be updated. + */ +void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip); + #endif diff --git a/cmdline/scrub.c b/cmdline/scrub.c index 4b5da24d..800a3c1a 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -275,7 +275,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par buffermax = diskmax + state->level * 2; /* initialize the io threads */ - io_init(&io, state, buffermax, scrub_data_reader, handle, diskmax, scrub_parity_reader, parity_handle, state->level); + io_init(&io, state, buffermax, scrub_data_reader, handle, diskmax, scrub_parity_reader, 0, parity_handle, state->level); error = 0; silent_error = 0; diff --git a/cmdline/sync.c b/cmdline/sync.c index e8785314..6f4b7c79 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -615,6 +615,39 @@ static void sync_data_reader(struct snapraid_worker* worker, struct snapraid_tas task->state = TASK_STATE_DONE; } +static void sync_parity_writer(struct snapraid_worker* worker, struct snapraid_task* task) +{ + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_parity_handle* parity_handle = worker->parity_handle; + unsigned level = parity_handle->level; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* write parity */ + ret = parity_write(parity_handle, blockcur, buffer, state->block_size); + if (ret == -1) { + /* LCOV_EXCL_START */ + if (errno == EIO) { + log_tag("parity_error:%u:%s: Write EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("parity_error:%u:%s: Write error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + log_fatal("WARNING! Unexpected write error in the %s disk, it isn't possible to sync.\n", lev_name(level)); + log_fatal("Ensure that disk '%s' has some free space available.\n", lev_config_name(level)); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + + task->state = TASK_STATE_DONE; +} + static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax) { struct snapraid_io io; @@ -660,7 +693,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari buffermax = 2 * diskmax + state->level; /* initialize the io threads */ - io_init(&io, state, buffermax, sync_data_reader, handle, diskmax, 0, 0, 0); + io_init(&io, state, buffermax, sync_data_reader, handle, diskmax, 0, sync_parity_writer, parity_handle, state->level); /* fill up the zero buffer */ zero = malloc_nofail_align(state->block_size, &zero_alloc); @@ -710,6 +743,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari int io_error_on_this_block; int fixed_error_on_this_block; int parity_needs_to_be_updated; + int parity_going_to_be_updated; snapraid_info info; int rehash; void** buffer; @@ -738,14 +772,18 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* if we have to use the old hash */ rehash = info_get_rehash(info); - /* it could happens that all the blocks are EMPTY/BLK and CHG but with the hash */ + /* if the parity requires to be updated */ + /* It could happens that all the blocks are EMPTY/BLK and CHG but with the hash */ /* still matching because the specific CHG block was not modified. */ + /* In such case, we can avoid to update parity, because it would be the same as before */ /* Note that CHG/DELETED blocks already present in the content file loaded */ /* have the hash cleared (::clear_past_hash flag), and then they won't never match the hash. */ /* We are treating only CHG blocks created at runtime. */ - /* In such case, we can avoid to update parity, because it would be the same as before */ parity_needs_to_be_updated = state->opt.force_parity_update; + /* if the parity is going to be updated */ + parity_going_to_be_updated = 0; + /* if the block is marked as bad, we force the parity update */ /* because the bad block may be the result of a wrong parity */ if (info_get_bad(info)) @@ -1078,70 +1116,33 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* compute the parity */ raid_gen(diskmax, state->level, state->block_size, buffer); - /* until now is CPU */ - state_usage_cpu(state); - - /* write the parity */ - for (l = 0; l < state->level; ++l) { - ret = parity_write(&parity_handle[l], blockcur, buffer[diskmax + l], state->block_size); - if (ret == -1) { - /* LCOV_EXCL_START */ - if (errno == EIO) { - log_tag("parity_error:%u:%s: Write EIO error. %s\n", blockcur, lev_config_name(l), strerror(errno)); - if (io_error >= state->opt.io_error_limit) { - log_fatal("DANGER! Unexpected input/output write error in the %s disk, it isn't possible to sync.\n", lev_name(l)); - log_fatal("Ensure that disk '%s' is sane and can be written.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", blockcur); - ++io_error; - goto bail; - } - - log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), blockcur); - ++io_error; - io_error_on_this_block = 1; - continue; - } - - log_tag("parity_error:%u:%s: Write error. %s\n", blockcur, lev_config_name(l), strerror(errno)); - log_fatal("WARNING! Unexpected write error in the %s disk, it isn't possible to sync.\n", lev_name(l)); - log_fatal("Ensure that disk '%s' has some free space available.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", blockcur); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } - - /* until now is parity */ - state_usage_parity(state, l); - } + /* mark that the parity is going to be written */ + parity_going_to_be_updated = 1; } - /* if no error in parity write */ - if (!io_error_on_this_block) { - /* for each disk, mark the blocks as processed */ - for (j = 0; j < diskmax; ++j) { - struct snapraid_block* block; + /* for each disk, mark the blocks as processed */ + for (j = 0; j < diskmax; ++j) { + struct snapraid_block* block; - if (!handle[j].disk) - continue; + if (!handle[j].disk) + continue; - block = fs_par2block_get(handle[j].disk, blockcur); + block = fs_par2block_get(handle[j].disk, blockcur); - if (block == BLOCK_EMPTY) { - /* nothing to do */ - continue; - } - - /* if it's a deleted block */ - if (block_state_get(block) == BLOCK_STATE_DELETED) { - /* the parity is now updated without this block, so it's now empty */ - fs_deallocate(handle[j].disk, blockcur); - continue; - } - - /* now all the blocks have the hash and the parity computed */ - block_state_set(block, BLOCK_STATE_BLK); + if (block == BLOCK_EMPTY) { + /* nothing to do */ + continue; } + + /* if it's a deleted block */ + if (block_state_get(block) == BLOCK_STATE_DELETED) { + /* the parity is now updated without this block, so it's now empty */ + fs_deallocate(handle[j].disk, blockcur); + continue; + } + + /* now all the blocks have the hash and the parity computed */ + block_state_set(block, BLOCK_STATE_BLK); } /* we update the info block only if we really have updated the parity */ @@ -1151,7 +1152,6 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* because has no sense to refresh the time for data that we know bad */ if (parity_needs_to_be_updated && !silent_error_on_this_block - && !io_error_on_this_block ) { /* if rehash is neeed */ if (rehash) { @@ -1176,6 +1176,25 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari info_set(&state->infoarr, blockcur, info_set_bad(info)); } + /* finally schedule parity write */ + /* Note that the calls to io_parity_write() are mandatory */ + /* even if the parity doesn't need to be updated */ + /* This because we want to keep track of the time usage */ + state_usage_cpu(state); + + /* write the parity */ + for (l = 0; l < state->level; ++l) { + unsigned levcur; + + io_parity_write(&io, &levcur); + + /* until now is parity */ + state_usage_parity(state, levcur); + } + + /* write finished */ + io_write_next(&io, blockcur, !parity_going_to_be_updated); + /* mark the state as needing write */ state->need_write = 1; From fa635f72b84386e21ac69998ea29f0ec9496511f Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 5 Jan 2016 10:42:46 +0100 Subject: [PATCH 15/34] In the check log write status:correct only in verbose mode --- cmdline/check.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmdline/check.c b/cmdline/check.c index 89c0fbe1..dd80763e 100644 --- a/cmdline/check.c +++ b/cmdline/check.c @@ -764,10 +764,11 @@ static int file_post(struct snapraid_state* state, int fix, unsigned i, struct s log_tag("status:recoverable:%s:%s\n", disk->name, esc(file->sub)); msg_info("recoverable %s\n", path); } else { - log_tag("status:correct:%s:%s\n", disk->name, esc(file->sub)); /* we don't use msg_verbose() because it also goes into the log */ - if (msg_level >= MSG_VERBOSE) + if (msg_level >= MSG_VERBOSE) { + log_tag("status:correct:%s:%s\n", disk->name, esc(file->sub)); msg_info("correct %s\n", path); + } } } From 4191989fa7b94f6777b555d1d2581801e9129220 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 5 Jan 2016 13:20:25 +0100 Subject: [PATCH 16/34] Restore error reporting for parity write --- cmdline/io.c | 31 +++++++++++++++++++++++++++---- cmdline/io.h | 14 +++++++++++++- cmdline/sync.c | 28 +++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/cmdline/io.c b/cmdline/io.c index 4e1c7b77..f4c085ca 100644 --- a/cmdline/io.c +++ b/cmdline/io.c @@ -274,14 +274,20 @@ static struct snapraid_task* io_reader_step(struct snapraid_worker* worker) * * This is the synchronization point for workers with the io. */ -static struct snapraid_task* io_writer_step(struct snapraid_worker* worker) +static struct snapraid_task* io_writer_step(struct snapraid_worker* worker, int state) { struct snapraid_io* io = worker->io; struct snapraid_task* task = 0; + int error_index; /* the synchronization is protected by the io mutex */ pthread_mutex_lock(&io->mutex); + /* counts the number of errors in the global state */ + error_index = state - IO_WRITER_ERROR_BASE; + if (error_index >= 0 && error_index < IO_WRITER_ERROR_MAX) + ++io->writer_error[error_index]; + while (1) { unsigned index; @@ -359,7 +365,7 @@ block_off_t io_read_next(struct snapraid_io* io, void*** buffer) return blockcur_caller; } -void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip) +void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip, int* writer_error) { unsigned i; @@ -373,6 +379,12 @@ void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip) /* the synchronization is protected by the io mutex */ pthread_mutex_lock(&io->mutex); + /* report errors */ + for (i = 0; i < IO_WRITER_ERROR_MAX; ++i) { + writer_error[i] = io->writer_error[i]; + io->writer_error[i] = 0; + } + if (skip) { /* skip the next write */ io_writer_sched_empty(io, io->writer_index, blockcur); @@ -554,28 +566,35 @@ static void* io_reader_thread(void* arg) return 0; } + static void* io_writer_thread(void* arg) { struct snapraid_worker* worker = arg; + int latest_state = TASK_STATE_DONE; while (1) { struct snapraid_task* task; /* get the new task */ - task = io_writer_step(worker); + task = io_writer_step(worker, latest_state); /* if no task, it means to exit */ if (!task) break; /* nothing more to do */ - if (task->state == TASK_STATE_EMPTY) + if (task->state == TASK_STATE_EMPTY) { + latest_state = TASK_STATE_DONE; continue; + } assert(task->state == TASK_STATE_READY); /* work on the assigned task */ worker->func(worker, task); + + /* save the resulting state */ + latest_state = task->state; } return 0; @@ -597,6 +616,10 @@ void io_start(struct snapraid_io* io, io->reader_index = IO_MAX - 1; io->writer_index = 0; + /* clear writer errors */ + for (i = 0; i < IO_WRITER_ERROR_MAX; ++i) + io->writer_error[i] = 0; + /* setup the initial read pending tasks, except the latest one, */ /* the latest will be initialized at the fist io_read_next() call */ for (i = 0; i < IO_MAX - 1; ++i) { diff --git a/cmdline/io.h b/cmdline/io.h index da240a87..6ecf7eff 100644 --- a/cmdline/io.h +++ b/cmdline/io.h @@ -120,6 +120,12 @@ struct snapraid_worker { unsigned buffer_skew; }; +/** + * Number of error kind for writers. + */ +#define IO_WRITER_ERROR_BASE TASK_STATE_IOERROR_CONTINUE +#define IO_WRITER_ERROR_MAX (-IO_WRITER_ERROR_BASE) + /** * Reader. * @@ -258,6 +264,11 @@ struct snapraid_io { * it's incremented, and a write_sched() signal is sent. */ unsigned writer_index; + + /** + * Counts the error happening in the writers. + */ + int writer_error[IO_WRITER_ERROR_MAX]; }; /** @@ -343,7 +354,8 @@ void io_parity_write(struct snapraid_io* io, unsigned* levcur); * \param io InputOutput context. * \param blockcur The parity position to write. * \param skip Skip the writes, in case parity doesn't need to be updated. + * \param writer_error Return the number of errors. Vector of IO_WRITER_ERROR_MAX elements. */ -void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip); +void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip, int* writer_error); #endif diff --git a/cmdline/sync.c b/cmdline/sync.c index 6f4b7c79..b3923cfd 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -747,6 +747,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari snapraid_info info; int rehash; void** buffer; + int writer_error[IO_WRITER_ERROR_MAX]; /* go to the next block */ blockcur = io_read_next(&io, &buffer); @@ -1193,7 +1194,32 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari } /* write finished */ - io_write_next(&io, blockcur, !parity_going_to_be_updated); + io_write_next(&io, blockcur, !parity_going_to_be_updated, writer_error); + + /* handle errors reported */ + for (j = 0; j < IO_WRITER_ERROR_MAX; ++j) { + if (writer_error[j]) { + switch (j + IO_WRITER_ERROR_BASE) { + case TASK_STATE_IOERROR_CONTINUE : + ++io_error; + if (io_error >= state->opt.io_error_limit) { + log_fatal("DANGER! Unexpected input/output write error in a parity disk, it isn't possible to sync.\n"); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + } + break; + case TASK_STATE_ERROR_CONTINUE : + ++error; + break; + case TASK_STATE_IOERROR : + ++io_error; + goto bail; + case TASK_STATE_ERROR : + ++error; + goto bail; + } + } + } /* mark the state as needing write */ state->need_write = 1; From fb404ef14959f81149960c6cfda994aac71c02f3 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 5 Jan 2016 13:58:08 +0100 Subject: [PATCH 17/34] Print the amount of memory used for MT --- cmdline/io.c | 5 +++++ cmdline/snapraid.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmdline/io.c b/cmdline/io.c index f4c085ca..02627787 100644 --- a/cmdline/io.c +++ b/cmdline/io.c @@ -27,6 +27,7 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff struct snapraid_parity_handle* parity_handle_map, unsigned parity_handle_max) { unsigned i; + size_t allocated; pthread_mutex_init(&io->mutex, 0); pthread_cond_init(&io->read_done, 0); @@ -37,12 +38,16 @@ void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buff io->state = state; io->buffer_max = buffer_max; + allocated = 0; for (i = 0; i < IO_MAX; ++i) { io->buffer_map[i] = malloc_nofail_vector_align(handle_max, buffer_max, state->block_size, &io->buffer_alloc_map[i]); if (!state->opt.skip_self) mtest_vector(io->buffer_max, state->block_size, io->buffer_map[i]); + allocated += state->block_size * buffer_max; } + msg_progress("Using %u MiB of memory for the MT buffers.\n", (unsigned)(allocated / (1024 * 1024))); + if (parity_writer) { io->reader_max = handle_max; io->writer_max = parity_handle_max; diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index 438c10d8..22674292 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -85,7 +85,7 @@ void memory(void) log_tag("memory:link:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_link))); log_tag("memory:dir:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_dir))); - msg_progress("Using %u MiB of memory.\n", (unsigned)(malloc_counter_get() / (1024 * 1024))); + msg_progress("Using %u MiB of memory for the FileSystem.\n", (unsigned)(malloc_counter_get() / (1024 * 1024))); } /****************************************************************************/ From da5be1c5a740e2c395c6960f2a1b844f874ed18a Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 5 Jan 2016 14:48:39 +0100 Subject: [PATCH 18/34] Reduce memory occupation for MT The copy buffer doesn't need a copy for each thread. --- cmdline/scrub.c | 4 ++-- cmdline/sync.c | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cmdline/scrub.c b/cmdline/scrub.c index 800a3c1a..99d27e01 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -271,8 +271,8 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* rehash buffers */ rehandle = malloc_nofail_align(diskmax * sizeof(struct snapraid_rehash), &rehandle_alloc); - /* we need disk + 2 for each parity level buffers */ - buffermax = diskmax + state->level * 2; + /* we need 1 * data + 2 * parity */ + buffermax = diskmax + 2 * state->level; /* initialize the io threads */ io_init(&io, state, buffermax, scrub_data_reader, handle, diskmax, scrub_parity_reader, 0, parity_handle, state->level); diff --git a/cmdline/sync.c b/cmdline/sync.c index b3923cfd..f3cb58fe 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -660,6 +660,8 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari unsigned j; void* zero_alloc; void** zero; + void* copy_alloc; + void** copy; unsigned buffermax; data_off_t countsize; block_off_t countpos; @@ -689,13 +691,16 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* rehash buffers */ rehandle = malloc_nofail_align(diskmax * sizeof(struct snapraid_rehash), &rehandle_alloc); - /* we need 2 * data + 1 * parity */ - buffermax = 2 * diskmax + state->level; + /* we need 1 * data + 1 * parity */ + buffermax = diskmax + state->level; /* initialize the io threads */ io_init(&io, state, buffermax, sync_data_reader, handle, diskmax, 0, sync_parity_writer, parity_handle, state->level); - /* fill up the zero buffer */ + /* allocate the copy buffer */ + copy = malloc_nofail_vector_align(diskmax, diskmax, state->block_size, ©_alloc); + + /* allocate and fill the zero buffer */ zero = malloc_nofail_align(state->block_size, &zero_alloc); memset(zero, 0, state->block_size); raid_zero(zero); @@ -991,7 +996,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari failed_mac = 0; for (j = 0; j < failed_count; ++j) { unsigned char* block_buffer = buffer[failed[j].index]; - unsigned char* block_copy = buffer[diskmax + state->level + failed[j].index]; + unsigned char* block_copy = copy[failed[j].index]; unsigned block_state = block_state_get(failed[j].block); /* we try to recover only if at least one BLK is present */ @@ -1071,7 +1076,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari for (j = 0; j < failed_count; ++j) { unsigned char hash[HASH_SIZE]; unsigned char* block_buffer = buffer[failed[j].index]; - unsigned char* block_copy = buffer[diskmax + state->level + failed[j].index]; + unsigned char* block_copy = copy[failed[j].index]; unsigned block_state = block_state_get(failed[j].block); if (block_state == BLOCK_STATE_BLK) { @@ -1348,6 +1353,8 @@ bail: free(handle); free(zero_alloc); + free(copy_alloc); + free(copy); free(rehandle_alloc); free(failed); free(failed_map); From e15b5b122c69fae2be91fb053810d4b9b7344d2c Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 5 Jan 2016 15:06:46 +0100 Subject: [PATCH 19/34] Remove the unnecessary parity_ptr variable in dry --- cmdline/dry.c | 53 ++++++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/cmdline/dry.c b/cmdline/dry.c index 812e9af0..004559da 100644 --- a/cmdline/dry.c +++ b/cmdline/dry.c @@ -26,7 +26,7 @@ /****************************************************************************/ /* dry */ -static int state_dry_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax) +static int state_dry_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax) { struct snapraid_handle* handle; unsigned diskmax; @@ -124,19 +124,17 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit /* read the parity */ for (l = 0; l < state->level; ++l) { - if (parity[l]) { - /* until now is CPU */ - state_usage_cpu(state); + /* until now is CPU */ + state_usage_cpu(state); - ret = parity_read(parity[l], i, buffer_aligned, state->block_size, log_error); - if (ret == -1) { - log_tag("parity_error:%u:%s: Read error\n", i, lev_config_name(l)); - ++error; - } - - /* until now is parity */ - state_usage_parity(state, l); + ret = parity_read(&parity_handle[l], i, buffer_aligned, state->block_size, log_error); + if (ret == -1) { + log_tag("parity_error:%u:%s: Read error\n", i, lev_config_name(l)); + ++error; } + + /* until now is parity */ + state_usage_parity(state, l); } /* count the number of processed block */ @@ -192,10 +190,7 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t { block_off_t blockmax; int ret; - struct snapraid_parity_handle parity[LEV_MAX]; - /* the following initialization is to avoid clang warnings about */ - /* potential state->level change, that never happens */ - struct snapraid_parity_handle* parity_ptr[LEV_MAX] = { 0 }; + struct snapraid_parity_handle parity_handle[LEV_MAX]; unsigned error; unsigned l; @@ -218,12 +213,12 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t /* open the file for reading */ /* it may fail if the file doesn't exist, in this case we continue to dry the files */ for (l = 0; l < state->level; ++l) { - parity_ptr[l] = &parity[l]; - ret = parity_open(parity_ptr[l], l, state->parity[l].path, state->file_mode); + ret = parity_open(&parity_handle[l], l, state->parity[l].path, state->file_mode); if (ret == -1) { - msg_status("No accessible %s file.\n", lev_name(l)); - /* continue anyway */ - parity_ptr[l] = 0; + /* LCOV_EXCL_START */ + log_fatal("WARNING! Without an accessible %s file, it isn't possible to dry.\n", lev_name(l)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ } } @@ -231,7 +226,7 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t /* skip degenerated cases of empty parity, or skipping all */ if (blockstart < blockmax) { - ret = state_dry_process(state, parity_ptr, blockstart, blockmax); + ret = state_dry_process(state, parity_handle, blockstart, blockmax); if (ret == -1) { /* LCOV_EXCL_START */ ++error; @@ -242,14 +237,12 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t /* try to close only if opened */ for (l = 0; l < state->level; ++l) { - if (parity_ptr[l]) { - ret = parity_close(parity_ptr[l]); - if (ret == -1) { - /* LCOV_EXCL_START */ - ++error; - /* continue, as we are already exiting */ - /* LCOV_EXCL_STOP */ - } + ret = parity_close(&parity_handle[l]); + if (ret == -1) { + /* LCOV_EXCL_START */ + ++error; + /* continue, as we are already exiting */ + /* LCOV_EXCL_STOP */ } } From ec5b383c1d4b4578350b2f0e4c24144d26519285 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 5 Jan 2016 15:35:57 +0100 Subject: [PATCH 20/34] Add support for multithread data and parity reading in dry --- cmdline/dry.c | 327 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 259 insertions(+), 68 deletions(-) diff --git a/cmdline/dry.c b/cmdline/dry.c index 004559da..fed8e4a9 100644 --- a/cmdline/dry.c +++ b/cmdline/dry.c @@ -22,30 +22,174 @@ #include "state.h" #include "parity.h" #include "handle.h" +#include "io.h" /****************************************************************************/ /* dry */ +/** + * Check if we have to process the specified block index ::i. + */ +static int block_is_enabled(void* void_plan, block_off_t i) +{ + (void)void_plan; + (void)i; + + return 1; +} + +static void dry_data_reader(struct snapraid_worker* worker, struct snapraid_task* task) +{ + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_handle* handle = worker->handle; + struct snapraid_disk* disk = handle->disk; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* if the disk position is not used */ + if (!disk) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the block */ + task->block = fs_par2block_get(disk, blockcur); + + /* if the block is not used */ + if (!block_has_file(task->block)) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the file of this block */ + task->file = fs_par2file_get(disk, blockcur, &task->file_pos); + + /* if the file is different than the current one, close it */ + if (handle->file != 0 && handle->file != task->file) { + /* keep a pointer at the file we are going to close for error reporting */ + struct snapraid_file* report = handle->file; + ret = handle_close(handle); + if (ret == -1) { + /* LCOV_EXCL_START */ + /* This one is really an unexpected error, because we are only reading */ + /* and closing a descriptor should never fail */ + if (errno == EIO) { + log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to dry.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + } + + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to dry.\n"); + log_fatal("Ensure that file '%s' can be accessed.\n", handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + } + + ret = handle_open(handle, task->file, state->file_mode, log_error, 0); + if (ret == -1) { + if (errno == EIO) { + /* LCOV_EXCL_START */ + log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to dry.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + /* LCOV_EXCL_STOP */ + } + + log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + task->read_size = handle_read(handle, task->file_pos, buffer, state->block_size, log_error, 0); + if (task->read_size == -1) { + if (errno == EIO) { + log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + /* store the path of the opened file */ + pathcpy(task->path, sizeof(task->path), handle->path); + + task->state = TASK_STATE_DONE; +} + +static void dry_parity_reader(struct snapraid_worker* worker, struct snapraid_task* task) +{ + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_parity_handle* parity_handle = worker->parity_handle; + unsigned level = parity_handle->level; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* read the parity */ + ret = parity_read(parity_handle, blockcur, buffer, state->block_size, log_error); + if (ret == -1) { + if (errno == EIO) { + log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + task->state = TASK_STATE_DONE; +} + static int state_dry_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax) { + struct snapraid_io io; struct snapraid_handle* handle; unsigned diskmax; - block_off_t i; + block_off_t blockcur; unsigned j; - void* buffer_alloc; - unsigned char* buffer_aligned; + unsigned buffermax; int ret; data_off_t countsize; block_off_t countpos; block_off_t countmax; unsigned error; + unsigned io_error; unsigned l; handle = handle_map(state, &diskmax); - buffer_aligned = malloc_nofail_align(state->block_size, &buffer_alloc); + /* we need 1 * data + 2 * parity */ + buffermax = diskmax + 2 * state->level; + + /* initialize the io threads */ + io_init(&io, state, buffermax, dry_data_reader, handle, diskmax, dry_parity_reader, 0, parity_handle, state->level); error = 0; + io_error = 0; /* drop until now */ state_usage_waste(state); @@ -53,95 +197,137 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit countmax = blockmax - blockstart; countsize = 0; countpos = 0; + + /* start all the worker threads */ + io_start(&io, blockstart, blockmax, &block_is_enabled, 0); + state_progress_begin(state, blockstart, blockmax, countmax); - for (i = blockstart; i < blockmax; ++i) { + while (1) { + void** buffer; + + /* go to the next block */ + blockcur = io_read_next(&io, &buffer); + if (blockcur >= blockmax) + break; + /* for each disk, process the block */ for (j = 0; j < diskmax; ++j) { + struct snapraid_task* task; int read_size; struct snapraid_block* block; - struct snapraid_disk* disk = handle[j].disk; - struct snapraid_file* file; - block_off_t file_pos; - - if (!disk) { - /* if no disk, nothing to do */ - continue; - } - - block = fs_par2block_get(disk, i); - - if (!block_has_file(block)) { - /* if no file, nothing to do */ - continue; - } - - /* get the file of this block */ - file = fs_par2file_get(disk, i, &file_pos); + struct snapraid_disk* disk; + unsigned diskcur; /* until now is CPU */ state_usage_cpu(state); - /* if the file is closed or different than the current one */ - if (handle[j].file == 0 || handle[j].file != file) { - /* close the old one, if any */ - ret = handle_close(&handle[j]); - if (ret == -1) { - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(handle[j].file->sub), strerror(errno)); - log_fatal("DANGER! Unexpected close error in a data disk, it isn't possible to dry.\n"); - log_fatal("Stopping at block %u\n", i); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } + /* get the next task */ + task = io_data_read(&io, &diskcur); - /* open the file only for reading */ - ret = handle_open(&handle[j], file, state->file_mode, log_fatal, 0); - if (ret == -1) { - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Open error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_fatal("DANGER! Unexpected open error in a data disk, it isn't possible to dry.\n"); - log_fatal("Stopping at block %u\n", i); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } - } + /* get the task results */ + disk = task->disk; + block = task->block; + read_size = task->read_size; - /* read from the file */ - read_size = handle_read(&handle[j], file_pos, buffer_aligned, state->block_size, log_error, 0); - if (read_size == -1) { - log_tag("error:%u:%s:%s: Read error at position %u\n", i, disk->name, esc(file->sub), file_pos); - ++error; + /* if the disk position is not used */ + if (!disk) continue; - } /* until now is disk */ state_usage_disk(state, disk); + /* if the block is not used */ + if (!block_has_file(block)) + continue; + + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; + } + if (task->state == TASK_STATE_ERROR) { + ++error; + goto bail; + } + if (task->state == TASK_STATE_ERROR_CONTINUE) { + ++error; + continue; + } + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, task->path); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + /* LCOV_EXCL_STOP */ + } + + /* otherwise continue */ + continue; + } + if (task->state != TASK_STATE_DONE) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in task state\n"); + os_abort(); + /* LCOV_EXCL_STOP */ + } + countsize += read_size; } + /* until now is CPU */ + state_usage_cpu(state); + /* read the parity */ for (l = 0; l < state->level; ++l) { - /* until now is CPU */ - state_usage_cpu(state); + struct snapraid_task* task; + unsigned levcur; - ret = parity_read(&parity_handle[l], i, buffer_aligned, state->block_size, log_error); - if (ret == -1) { - log_tag("parity_error:%u:%s: Read error\n", i, lev_config_name(l)); - ++error; - } + task = io_parity_read(&io, &levcur); /* until now is parity */ - state_usage_parity(state, l); + state_usage_parity(state, levcur); + + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; + } + if (task->state == TASK_STATE_ERROR) { + ++error; + goto bail; + } + if (task->state == TASK_STATE_ERROR_CONTINUE) { + ++error; + continue; + } + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(levcur)); + log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(levcur)); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + /* LCOV_EXCL_STOP */ + } + continue; + } + if (task->state != TASK_STATE_DONE) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in task state\n"); + os_abort(); + /* LCOV_EXCL_STOP */ + } } /* count the number of processed block */ ++countpos; /* progress */ - if (state_progress(state, i, countpos, countmax, countsize)) { + if (state_progress(state, blockcur, countpos, countmax, countsize)) { /* LCOV_EXCL_START */ break; /* LCOV_EXCL_STOP */ @@ -153,7 +339,9 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit state_usage_print(state); bail: - /* close all the files left open */ + /* stop all the worker threads */ + io_stop(&io); + for (j = 0; j < diskmax; ++j) { struct snapraid_file* file = handle[j].file; struct snapraid_disk* disk = handle[j].disk; @@ -168,20 +356,23 @@ bail: } } - if (error) { + if (error || io_error) { msg_status("\n"); - msg_status("%8u errors\n", error); + msg_status("%8u file errors\n", error); + msg_status("%8u io errors\n", io_error); } else { msg_status("Everything OK\n"); } if (error) log_fatal("DANGER! Unexpected errors!\n"); + if (io_error) + log_fatal("DANGER! Unexpected input/output errors!\n"); free(handle); - free(buffer_alloc); + io_done(&io); - if (error != 0) + if (error + io_error != 0) return -1; return 0; } From c870b04b66a135c8c2b4eddb67b231baf5812a38 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 5 Jan 2016 16:37:29 +0100 Subject: [PATCH 21/34] Refactor locks use in support --- cmdline/mktest.c | 4 ++ cmdline/snapraid.c | 3 + cmdline/support.c | 146 +++++++++++++++++++++++++++------------------ cmdline/support.h | 34 +++++++++++ 4 files changed, 129 insertions(+), 58 deletions(-) diff --git a/cmdline/mktest.c b/cmdline/mktest.c index c781df0d..1df95cc9 100644 --- a/cmdline/mktest.c +++ b/cmdline/mktest.c @@ -647,6 +647,8 @@ int main(int argc, char* argv[]) { int i, j, b; + lock_init(); + if (argc < 2) { help(); exit(EXIT_SUCCESS); @@ -785,6 +787,8 @@ int main(int argc, char* argv[]) /* LCOV_EXCL_STOP */ } + lock_done(); + return 0; } diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index 22674292..c52f4d27 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -463,6 +463,8 @@ int main(int argc, char* argv[]) struct tm* tm; int i; + lock_init(); + /* defaults */ config(conf, sizeof(conf), argv[0]); memset(&opt, 0, sizeof(opt)); @@ -1319,6 +1321,7 @@ int main(int argc, char* argv[]) tommy_list_foreach(&filterlist_disk, (tommy_foreach_func*)filter_free); os_done(); + lock_done(); return EXIT_SUCCESS; } diff --git a/cmdline/support.c b/cmdline/support.c index cc783c56..f29646a4 100644 --- a/cmdline/support.c +++ b/cmdline/support.c @@ -19,29 +19,67 @@ #include "support.h" +/****************************************************************************/ +/* lock */ + +/** + * Locks used externally. + */ +#if HAVE_PTHREAD +static pthread_mutex_t msg_lock; +static pthread_mutex_t memory_lock; +#endif + +void lock_msg(void) +{ +#if HAVE_PTHREAD + pthread_mutex_lock(&msg_lock); +#endif +} + +void unlock_msg(void) +{ +#if HAVE_PTHREAD + pthread_mutex_unlock(&msg_lock); +#endif +} + +void lock_memory(void) +{ +#if HAVE_PTHREAD + pthread_mutex_lock(&memory_lock); +#endif +} + +void unlock_memory(void) +{ +#if HAVE_PTHREAD + pthread_mutex_unlock(&memory_lock); +#endif +} + +void lock_init(void) +{ +#if HAVE_PTHREAD + /* initialize the locks as first operation as log_fatal depends on them */ + pthread_mutex_init(&msg_lock, 0); + pthread_mutex_init(&memory_lock, 0); +#endif +} + +void lock_done(void) +{ +#if HAVE_PTHREAD + pthread_mutex_destroy(&msg_lock); + pthread_mutex_destroy(&memory_lock); +#endif +} + /****************************************************************************/ /* print */ int msg_level = 0; -/** - * Mutex used for printf. - * - * In Windows printf() is not atomic, and multiple threads - * will have output interleaved. - * - * Note that even defining __USE_MINGW_ANSI_STDIO the problem persists. - * - * See for example: - * - * Weird output when I use pthread and printf. - * http://stackoverflow.com/questions/13190254/weird-output-when-i-use-pthread-and-printf - * - * This is also required in other OS because we split output in stdlog in - * two fprintf calls. - */ -static pthread_mutex_t msg_lock = PTHREAD_MUTEX_INITIALIZER; - /* * Note that in the following functions we always flush both * stdout and stderr, because we want to ensure that they mixes @@ -58,7 +96,7 @@ void log_fatal(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -73,14 +111,14 @@ void log_fatal(const char* format, ...) fflush(stderr); va_end(ap); - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void log_error(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -95,14 +133,14 @@ void log_error(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void log_expected(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -112,14 +150,14 @@ void log_expected(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void log_tag(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -129,26 +167,26 @@ void log_tag(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void log_flush(void) { - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) fflush(stdlog); fflush(stdout); fflush(stderr); - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_status(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -165,14 +203,14 @@ void msg_status(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_info(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); /* don't output in stdlog as these messages */ /* are always paired with a msg_tag() call */ @@ -184,14 +222,14 @@ void msg_info(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_progress(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -208,14 +246,14 @@ void msg_progress(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_bar(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); /* don't output in stdlog as these messages */ /* are intended for screen only */ @@ -227,14 +265,14 @@ void msg_bar(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_verbose(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -251,17 +289,17 @@ void msg_verbose(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_flush(void) { - pthread_mutex_lock(&msg_lock); + lock_msg(); fflush(stdout); fflush(stderr); - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } /** @@ -607,35 +645,27 @@ int fmtime(int f, int64_t mtime_sec, int mtime_nsec) */ static size_t mcounter; -/** - * Mutex protection for the global memory counter. - */ -#if HAVE_PTHREAD -pthread_mutex_t mcounter_mutex = PTHREAD_MUTEX_INITIALIZER; -#endif size_t malloc_counter_get(void) { size_t ret; -#if HAVE_PTHREAD - pthread_mutex_lock(&mcounter_mutex); -#endif + + lock_memory(); + ret = mcounter; -#if HAVE_PTHREAD - pthread_mutex_unlock(&mcounter_mutex); -#endif + + unlock_memory(); + return ret; } void malloc_counter_inc(size_t inc) { -#if HAVE_PTHREAD - pthread_mutex_lock(&mcounter_mutex); -#endif + lock_memory(); + mcounter += inc; -#if HAVE_PTHREAD - pthread_mutex_unlock(&mcounter_mutex); -#endif + + unlock_memory(); } /* LCOV_EXCL_START */ diff --git a/cmdline/support.h b/cmdline/support.h index c15210c0..d7162304 100644 --- a/cmdline/support.h +++ b/cmdline/support.h @@ -18,6 +18,40 @@ #ifndef __SUPPORT_H #define __SUPPORT_H +/****************************************************************************/ +/* lock */ + +/** + * Initialize and destroy the locks. + */ +void lock_init(void); +void lock_done(void); + +/** + * Lock used for printf. + * + * In Windows printf() is not atomic, and multiple threads + * will have output interleaved. + * + * Note that even defining __USE_MINGW_ANSI_STDIO the problem persists. + * + * See for example: + * + * Weird output when I use pthread and printf. + * http://stackoverflow.com/questions/13190254/weird-output-when-i-use-pthread-and-printf + * + * This is also required in other OS because we split output in stdlog in + * two fprintf calls. + */ +void lock_msg(void); +void unlock_msg(void); + +/** + * Lock used for memory counter. + */ +void lock_memory(void); +void unlock_memory(void); + /****************************************************************************/ /* log */ From d71b1b21b67b6ae08de2220a3b0aa589ae222c1c Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 5 Jan 2016 17:12:46 +0100 Subject: [PATCH 22/34] Always print the CPU value, even if 0% In dry, it's really 0. --- cmdline/state.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmdline/state.c b/cmdline/state.c index 465f9f89..d15949f4 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -3968,8 +3968,7 @@ int state_progress(struct snapraid_state* state, block_off_t blockpos, block_off msg_bar("%u%%, %u MiB", out_perc, (unsigned)(countsize / MEGA)); if (out_speed) msg_bar(", %u MiB/s", out_speed); - if (out_cpu) - msg_bar(", CPU %u%%", out_cpu); + msg_bar(", CPU %u%%", out_cpu); if (out_eta) msg_bar(", %u:%02u ETA", out_eta / 60, out_eta % 60); msg_bar("%s\r", PROGRESS_CLEAR); From 22d806a04fd6d47620b59a445d3c97be050fd282 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 5 Jan 2016 17:29:26 +0100 Subject: [PATCH 23/34] Minor beatification at the IO module --- cmdline/io.c | 10 +++++----- cmdline/io.h | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmdline/io.c b/cmdline/io.c index 02627787..3178aadc 100644 --- a/cmdline/io.c +++ b/cmdline/io.c @@ -227,7 +227,6 @@ static void io_writer_sched_empty(struct snapraid_io* io, int index, block_off_t } } - /** * Get the next task to work on for a reader. * @@ -353,7 +352,7 @@ block_off_t io_read_next(struct snapraid_io* io, void*** buffer) /* schedule the next read */ io_reader_sched(io, io->reader_index, blockcur_schedule); - /* get the next task to return to the caller */ + /* set the index for the tasks to return to the caller */ io->reader_index = (io->reader_index + 1) % IO_MAX; /* get the position to operate at high level from one task */ @@ -380,7 +379,7 @@ void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip, int* wri /* setup the list of workers to process */ for (i = 0; i <= io->writer_max; ++i) io->writer_list[i] = i; - + /* the synchronization is protected by the io mutex */ pthread_mutex_lock(&io->mutex); @@ -398,9 +397,10 @@ void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip, int* wri io_writer_sched(io, io->writer_index, blockcur); } + /* at this point the writers must be in sync with the readers */ assert(io->writer_index == io->reader_index); - /* get the next task to return to the caller */ + /* set the index to be used for the next write */ io->writer_index = (io->writer_index + 1) % IO_MAX; /* signal all the workers that there is a new pending task */ @@ -607,7 +607,7 @@ static void* io_writer_thread(void* arg) void io_start(struct snapraid_io* io, block_off_t blockstart, block_off_t blockmax, - int (*block_is_enabled)(void* arg,block_off_t), void* blockarg) + int (*block_is_enabled)(void* arg, block_off_t), void* blockarg) { unsigned i; diff --git a/cmdline/io.h b/cmdline/io.h index 6ecf7eff..4134631f 100644 --- a/cmdline/io.h +++ b/cmdline/io.h @@ -203,7 +203,7 @@ struct snapraid_io { block_off_t block_start; block_off_t block_max; block_off_t block_next; - int (*block_is_enabled)(void* arg,block_off_t); + int (*block_is_enabled)(void* arg, block_off_t); void* block_arg; /** @@ -294,7 +294,7 @@ void io_done(struct snapraid_io* io); */ void io_start(struct snapraid_io* io, block_off_t blockstart, block_off_t blockmax, - int (*block_is_enabled)(void* arg,block_off_t), void* blockarg); + int (*block_is_enabled)(void* arg, block_off_t), void* blockarg); /** * Stop all the worker threads. @@ -359,3 +359,4 @@ void io_parity_write(struct snapraid_io* io, unsigned* levcur); void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip, int* writer_error); #endif + From 8e47e2b02424e605a36cd1a55b81ba88f9498c40 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Wed, 6 Jan 2016 10:10:25 +0100 Subject: [PATCH 24/34] Signal the 'done' condition only at the waiting index This reduces the number of context switches. --- cmdline/io.c | 92 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/cmdline/io.c b/cmdline/io.c index 3178aadc..b15aefd3 100644 --- a/cmdline/io.c +++ b/cmdline/io.c @@ -235,42 +235,51 @@ static void io_writer_sched_empty(struct snapraid_io* io, int index, block_off_t static struct snapraid_task* io_reader_step(struct snapraid_worker* worker) { struct snapraid_io* io = worker->io; - struct snapraid_task* task = 0; /* the synchronization is protected by the io mutex */ pthread_mutex_lock(&io->mutex); while (1) { - unsigned index; + unsigned next_index; /* check if the worker has to exit */ /* even if there is work to do */ - if (io->done) - break; + if (io->done) { + pthread_mutex_unlock(&io->mutex); + return 0; + } /* get the next pending task */ - index = (worker->index + 1) % IO_MAX; + next_index = (worker->index + 1) % IO_MAX; /* if the queue of pending tasks is not empty */ - if (index != io->reader_index) { + if (next_index != io->reader_index) { + struct snapraid_task* task; + + /* the index that the IO may be waiting for */ + unsigned waiting_index = io->reader_index; + + /* the index that worker just completed */ + unsigned done_index = worker->index; + /* get the new working task */ - worker->index = index; + worker->index = next_index; task = &worker->task_map[worker->index]; - /* notify the io that a new read is complete */ - pthread_cond_signal(&io->read_done); + /* if the just completed task is at this index */ + if (done_index == waiting_index) + /* notify the IO that a new read is complete */ + pthread_cond_signal(&io->read_done); /* return the new task */ - break; + pthread_mutex_unlock(&io->mutex); + + return task; } /* otherwise wait for a read_sched event */ pthread_cond_wait(&io->read_sched, &io->mutex); } - - pthread_mutex_unlock(&io->mutex); - - return task; } /** @@ -281,7 +290,6 @@ static struct snapraid_task* io_reader_step(struct snapraid_worker* worker) static struct snapraid_task* io_writer_step(struct snapraid_worker* worker, int state) { struct snapraid_io* io = worker->io; - struct snapraid_task* task = 0; int error_index; /* the synchronization is protected by the io mutex */ @@ -293,36 +301,46 @@ static struct snapraid_task* io_writer_step(struct snapraid_worker* worker, int ++io->writer_error[error_index]; while (1) { - unsigned index; + unsigned next_index; /* get the next pending task */ - index = (worker->index + 1) % IO_MAX; + next_index = (worker->index + 1) % IO_MAX; /* if the queue of pending tasks is not empty */ - if (index != io->writer_index) { + if (next_index != io->writer_index) { + struct snapraid_task* task; + + /* the index that the IO may be waiting for */ + unsigned waiting_index = (io->writer_index + 1) % IO_MAX; + + /* the index that worker just completed */ + unsigned done_index = worker->index; + /* get the new working task */ - worker->index = index; + worker->index = next_index; task = &worker->task_map[worker->index]; - /* notify the io that a new writer is complete */ - pthread_cond_signal(&io->write_done); + /* if the just completed task is at this index */ + if (done_index == waiting_index) + /* notify the IO that a new write is complete */ + pthread_cond_signal(&io->write_done); /* return the new task */ - break; + pthread_mutex_unlock(&io->mutex); + + return task; } /* check if the worker has to exit */ /* but only if there is no work to do */ - if (io->done) - break; + if (io->done) { + pthread_mutex_unlock(&io->mutex); + return 0; + } /* otherwise wait for a write_sched event */ pthread_cond_wait(&io->write_sched, &io->mutex); } - - pthread_mutex_unlock(&io->mutex); - - return task; } /** @@ -416,12 +434,12 @@ static struct snapraid_task* io_task_read(struct snapraid_io* io, unsigned base, while (1) { unsigned char* let; - unsigned index; + unsigned busy_index; - /* get the index the caller is using */ + /* get the index the IO is using */ /* we must ensure that this index has not a read in progress */ /* to avoid a concurrent access */ - index = io->reader_index; + busy_index = io->reader_index; /* search for a worker that has already finished */ let = &io->reader_list[0]; @@ -439,7 +457,7 @@ static struct snapraid_task* io_task_read(struct snapraid_io* io, unsigned base, worker = &io->reader_map[i]; /* if the worker has finished this index */ - if (index != worker->index) { + if (busy_index != worker->index) { struct snapraid_task* task; task = &worker->task_map[io->reader_index]; @@ -483,14 +501,14 @@ void io_parity_write(struct snapraid_io* io, unsigned* pos) while (1) { unsigned char* let; - unsigned index; + unsigned busy_index; - /* get the next index the caller is going to use */ + /* get the next index the IO is going to use */ /* we must ensure that this index has not a write in progress */ /* to avoid a concurrent access */ /* note that we are already sure that a write is not in progress */ - /* at the index the caller is using at now */ - index = (io->writer_index + 1) % IO_MAX; + /* at the index the IO is using at now */ + busy_index = (io->writer_index + 1) % IO_MAX; /* search for a worker that has already finished */ let = &io->writer_list[0]; @@ -508,7 +526,7 @@ void io_parity_write(struct snapraid_io* io, unsigned* pos) assert(io->writer_index != worker->index); /* if the worker has finished this index */ - if (index != worker->index) { + if (busy_index != worker->index) { pthread_mutex_unlock(&io->mutex); /* mark the worker as processed */ From 2e12b105901a9d42f37d7d1c7308be57ff9e7ae7 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Wed, 6 Jan 2016 10:27:07 +0100 Subject: [PATCH 25/34] Signal condition variables outside the lock This reduces the number of context switches, because the signaled thread can start immediately. Some interesting discussion is at: Condvars: signal with mutex locked or not? http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/ Calling pthread_cond_signal without locking mutex http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex/4544494#4544494 --- cmdline/io.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/cmdline/io.c b/cmdline/io.c index b15aefd3..afa974e9 100644 --- a/cmdline/io.c +++ b/cmdline/io.c @@ -19,6 +19,25 @@ #include "io.h" +/** + * Implementation note about conditional variables. + * + * In release code the conditional variables are signaled outside the mutex, + * to reduce the number of context switches. + * + * Instead, when testing with helgrind and drd, they are signaled inside the mutex + * to help such tools to see the dependency between the signal and the wait. + * We use the CHECKER define to detect such case. + * + * Here some interesting discussion: + * + * Condvars: signal with mutex locked or not? + * http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/ + * + * Calling pthread_cond_signal without locking mutex + * http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex/4544494#4544494 + */ + void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buffer_max, void (*data_reader)(struct snapraid_worker*, struct snapraid_task*), struct snapraid_handle* handle_map, unsigned handle_max, @@ -266,14 +285,22 @@ static struct snapraid_task* io_reader_step(struct snapraid_worker* worker) worker->index = next_index; task = &worker->task_map[worker->index]; +#ifndef CHECKER + /* without the thread checker unlock before signaling, */ + /* this reduces the number of context switches */ + pthread_mutex_unlock(&io->mutex); +#endif + /* if the just completed task is at this index */ if (done_index == waiting_index) /* notify the IO that a new read is complete */ pthread_cond_signal(&io->read_done); - /* return the new task */ +#ifdef CHECKER pthread_mutex_unlock(&io->mutex); +#endif + /* return the new task */ return task; } @@ -320,14 +347,22 @@ static struct snapraid_task* io_writer_step(struct snapraid_worker* worker, int worker->index = next_index; task = &worker->task_map[worker->index]; +#ifndef CHECKER + /* without the thread checker unlock before signaling, */ + /* this reduces the number of context switches */ + pthread_mutex_unlock(&io->mutex); +#endif + /* if the just completed task is at this index */ if (done_index == waiting_index) /* notify the IO that a new write is complete */ pthread_cond_signal(&io->write_done); - /* return the new task */ +#ifdef CHECKER pthread_mutex_unlock(&io->mutex); +#endif + /* return the new task */ return task; } @@ -379,10 +414,18 @@ block_off_t io_read_next(struct snapraid_io* io, void*** buffer) /* set the buffer to use */ *buffer = io->buffer_map[io->reader_index]; +#ifndef CHECKER + /* without the thread checker unlock before signaling, */ + /* this reduces the number of context switches */ + pthread_mutex_unlock(&io->mutex); +#endif + /* signal all the workers that there is a new pending task */ pthread_cond_broadcast(&io->read_sched); +#ifdef CHECKER pthread_mutex_unlock(&io->mutex); +#endif return blockcur_caller; } @@ -421,10 +464,18 @@ void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip, int* wri /* set the index to be used for the next write */ io->writer_index = (io->writer_index + 1) % IO_MAX; +#ifndef CHECKER + /* without the thread checker unlock before signaling, */ + /* this reduces the number of context switches */ + pthread_mutex_unlock(&io->mutex); +#endif + /* signal all the workers that there is a new pending task */ pthread_cond_broadcast(&io->write_sched); +#ifdef CHECKER pthread_mutex_unlock(&io->mutex); +#endif } static struct snapraid_task* io_task_read(struct snapraid_io* io, unsigned base, unsigned count, unsigned* pos) From da5ef977e309b308d74b2acaa717721e128ebcab Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Thu, 7 Jan 2016 07:59:38 +0100 Subject: [PATCH 26/34] Add the no-warnings option in the regression test --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index d5f253b5..3f54e0f2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -160,7 +160,7 @@ maintainer-clean-local: # --test-force-order-alpha # Ensures to process files always in the same order despites # the inode, physical location, and dir order assigned by the OS. -CHECKFLAGS_BASE = --test-skip-device --test-skip-self --test-force-order-alpha --test-force-progress +CHECKFLAGS_BASE = --test-skip-device --test-skip-self --test-force-order-alpha --test-force-progress --no-warnings # Physical offset options CHECKFLAGS_PHYSICAL = --test-skip-device --test-skip-self --test-force-order-physical -q -q -q From f93b1845f5bbef381186f037b6554209c3ea6483 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 8 Jan 2016 18:19:53 +0100 Subject: [PATCH 27/34] Add support for various sanitizers --- configure.ac | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index b5adb42f..2175a6fc 100644 --- a/configure.ac +++ b/configure.ac @@ -228,8 +228,6 @@ AC_ARG_ENABLE([sgcheck], ], []) -AM_CONDITIONAL(HAVE_MEMORY_CHECKER, [test x"$MEMORY_CHECKER" != x]) - AC_ARG_ENABLE([helgrind], [AS_HELP_STRING([--enable-helgrind],[enable the use of helgrind in testing])], [ @@ -250,6 +248,40 @@ AC_ARG_ENABLE([drd], ], []) +AC_ARG_ENABLE([asan], + [AS_HELP_STRING([--enable-asan],[enable the use of AddressSanitizer in testing])], + [ + CFLAGS="$CFLAGS -DCHECKER -fsanitize=address" + MEMORY_CHECKER=1 + ], + []) + +AC_ARG_ENABLE([msan], + [AS_HELP_STRING([--enable-msan],[enable the use of MemorySanitizer in testing])], + [ + CFLAGS="$CFLAGS -DCHECKER -fsanitize=memory" + MEMORY_CHECKER=1 + ], + []) + +AC_ARG_ENABLE([ubsan], + [AS_HELP_STRING([--enable-ubsan],[enable the use of UndefinedBehaviourSanitizer in testing])], + [ + CFLAGS="$CFLAGS -DCHECKER -fsanitize=undefined" + MEMORY_CHECKER=1 + ], + []) + +AC_ARG_ENABLE([tsan], + [AS_HELP_STRING([--enable-tsan],[enable the use of ThreadSanitizer in testing])], + [ + CFLAGS="$CFLAGS -DCHECKER -fsanitize=thread" +dnl Use MEMORY_CHECKER instead of THREAD_CHECKER to run the full and long test + MEMORY_CHECKER=1 + ], + []) + +AM_CONDITIONAL(HAVE_MEMORY_CHECKER, [test x"$MEMORY_CHECKER" != x]) AM_CONDITIONAL(HAVE_THREAD_CHECKER, [test x"$THREAD_CHECKER" != x]) AC_ARG_ENABLE([sde], From 56a27b394fa03fe4d88892dfbcb27d897c9da4b8 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 8 Jan 2016 22:16:43 +0100 Subject: [PATCH 28/34] Extend the multithread regression test --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 3f54e0f2..f38c6c92 100644 --- a/Makefile.am +++ b/Makefile.am @@ -229,7 +229,7 @@ if HAVE_THREAD_CHECKER $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) sync # Other commands that uses threads $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) test-rewrite - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) scrub + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) scrub -p full else #### COMMAND LINE #### $(MSG) Help From 9430106c71f429235d3942fe19067a9d58fdf83f Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 8 Jan 2016 22:43:04 +0100 Subject: [PATCH 29/34] Allow to disable inline assembly from configure This is required for the -fsanitize options that don't support inline assembly statements. --- cmdline/portable.h | 4 +++- configure.ac | 11 ++++++++++ raid/internal.h | 54 +++++++++++++++++++++++++++------------------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/cmdline/portable.h b/cmdline/portable.h index 2e37eeba..18867f89 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -75,8 +75,9 @@ /** - * Architecture. + * Architecture for inline assembly. */ +#if HAVE_ASSEMBLY #if defined(__i386__) #define CONFIG_X86 1 #define CONFIG_X86_32 1 @@ -86,6 +87,7 @@ #define CONFIG_X86 1 #define CONFIG_X86_64 1 #endif +#endif /** * Includes some platform specific headers. diff --git a/configure.ac b/configure.ac index 2175a6fc..c04f1a2a 100644 --- a/configure.ac +++ b/configure.ac @@ -118,6 +118,14 @@ AC_CHECK_CC_OPT([-Wextra], CFLAGS="$CFLAGS -Wextra", []) AC_CHECK_CC_OPT([-Wuninitialized], CFLAGS="$CFLAGS -Wuninitialized", []) AC_CHECK_CC_OPT([-Wshadow], CFLAGS="$CFLAGS -Wshadow", []) +dnl Checks for asm +AC_ARG_ENABLE([asm], + AS_HELP_STRING([--disable-asm], [Disable inline assembly])) +AS_IF([test "x$enable_asm" != "xno"], + [AC_DEFINE([HAVE_ASSEMBLY], [1], [Define to 1 if inline assembly should be used.])] + +dnl AS_IF(HAVE_ASSEMBLY) NOT closed here + dnl Checks for AS supporting the SSE2 instructions. AC_MSG_CHECKING([for sse2]) asmsse2=no @@ -183,6 +191,9 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ [AC_DEFINE([HAVE_AVX2], [1], [Define to 1 if avx2 is supported by the assembler.]) asmavx2=yes]) AC_MSG_RESULT([$asmavx2]) +dnl AS_IF(HAVE_ASSEMBLY) closed here +) + dnl Checks for test environment AS_CASE([$host], [*-*-mingw*], diff --git a/raid/internal.h b/raid/internal.h index ba2f35f6..4465cb97 100644 --- a/raid/internal.h +++ b/raid/internal.h @@ -15,21 +15,6 @@ #ifndef __RAID_INTERNAL_H #define __RAID_INTERNAL_H -/* - * Supported architecture. - * - * Detect the target architecture directly from the compiler. - */ -#if defined(__i386__) -#define CONFIG_X86 1 -#define CONFIG_X86_32 1 -#endif - -#if defined(__x86_64__) -#define CONFIG_X86 1 -#define CONFIG_X86_64 1 -#endif - /* * Supported instruction sets. * @@ -45,19 +30,44 @@ /* Includes the project configuration for HAVE_* defines */ #include "config.h" -#if HAVE_SSE2 /* Enables SSE2 only if the assembler supports it */ +/* If the compiler supports assembly */ +#if HAVE_ASSEMBLY +/* Autodetect from the compiler */ +#if defined(__i386__) +#define CONFIG_X86 1 +#define CONFIG_X86_32 1 +#endif +#if defined(__x86_64__) +#define CONFIG_X86 1 +#define CONFIG_X86_64 1 +#endif +#endif + +/* Enables SSE2, SSSE3, AVX2 only if the assembler supports it */ +#if HAVE_SSE2 #define CONFIG_SSE2 1 #endif - -#if HAVE_SSSE3 /* Enables SSSE3 only if the assembler supports it */ +#if HAVE_SSSE3 #define CONFIG_SSSE3 1 #endif - -#if HAVE_AVX2 /* Enables AVX2 only if the assembler supports it */ +#if HAVE_AVX2 #define CONFIG_AVX2 1 #endif -#else -/* If no config file, assumes the assembler supports everything in x86 */ + +#else /* if HAVE_CONFIG_H is not defined */ + +/* Assume that assembly is always supported */ +#if defined(__i386__) +#define CONFIG_X86 1 +#define CONFIG_X86_32 1 +#endif + +#if defined(__x86_64__) +#define CONFIG_X86 1 +#define CONFIG_X86_64 1 +#endif + +/* Assumes that the assembler supports everything */ #ifdef CONFIG_X86 #define CONFIG_SSE2 1 #define CONFIG_SSSE3 1 From e5ab577b6d4bfcb650fd128c9fecfa7e5aaa0059 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 8 Jan 2016 22:52:03 +0100 Subject: [PATCH 30/34] Add the new makesan.sh script and some more comments --- makecov.sh | 2 ++ makesan.sh | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ makescan.sh | 2 ++ maketest.sh | 7 +++++++ 4 files changed, 68 insertions(+) create mode 100755 makesan.sh diff --git a/makecov.sh b/makecov.sh index 49b057bf..f030361e 100755 --- a/makecov.sh +++ b/makecov.sh @@ -1,5 +1,7 @@ #!/bin/sh # +# Run the Coverage test +# make distclean diff --git a/makesan.sh b/makesan.sh new file mode 100755 index 00000000..98b9d7f9 --- /dev/null +++ b/makesan.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# +# Run all the Sanitizers available +# + +# Compiler to use +COMPILER=clang-3.6 + +# Options for configure +# --disable-asm +# Inline assembly is not supported by the Sanitizers +# --without-blkid +# External libraries are not supported by the Sanitizers +OPTIONS="--disable-asm --without-blkid" + +make distclean + +# AddressSanitizer +if ! ./configure --enable-asan $OPTIONS CC=$COMPILER; then + exit 1 +fi + +if ! make check distclean; then + exit 1 +fi + +# UndefinedBehaviourSanitizer +if ! ./configure --enable-ubsan $OPTIONS CC=$COMPILER; then + exit 1 +fi + +if ! make check distclean; then + exit 1 +fi + +# MemorySanitizer +if ! ./configure --enable-msan $OPTIONS CC=$COMPILER; then + exit 1 +fi + +if ! make check distclean; then + exit 1 +fi + +# ThreadSanitizer +if ! ./configure --enable-tsan $OPTIONS CC=$COMPILER; then + exit 1 +fi + +if ! make check distclean; then + exit 1 +fi + +if ! ./configure ; then + exit 1 +fi + diff --git a/makescan.sh b/makescan.sh index 74ed8c59..918c7544 100755 --- a/makescan.sh +++ b/makescan.sh @@ -1,5 +1,7 @@ #!/bin/sh # +# Run the Coverity Scan static analyzer +# rm -r cov-int diff --git a/maketest.sh b/maketest.sh index 3d71a490..63f7d239 100755 --- a/maketest.sh +++ b/maketest.sh @@ -1,8 +1,11 @@ #!/bin/sh # +# Run all the Coverage and Valgrind tests +# make distclean +# Coverage if ! ./configure --enable-coverage --enable-sde; then exit 1 fi @@ -11,6 +14,7 @@ if ! make lcov_reset check lcov_capture lcov_html; then exit 1 fi +# Keep a copy of the coverage results rm -rf cov_release mv cov cov_release @@ -18,6 +22,7 @@ if ! make distclean; then exit 1 fi +# Valgrind if ! ./configure --enable-valgrind; then exit 1 fi @@ -26,6 +31,7 @@ if ! make check distclean; then exit 1 fi +# Helgrind if ! ./configure --enable-helgrind; then exit 1 fi @@ -34,6 +40,7 @@ if ! make check distclean; then exit 1 fi +# Drd if ! ./configure --enable-drd; then exit 1 fi From 984ea5a3292b791c7a1579e5e05e0098b0229667 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sat, 9 Jan 2016 11:18:32 +0100 Subject: [PATCH 31/34] Log info about the filesystem used --- cmdline/unix.c | 65 ++++++++++++++++++++++++++++++++++++++++---------- configure.ac | 14 +++++++++++ 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/cmdline/unix.c b/cmdline/unix.c index 6f9d1f28..9be8c232 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -297,7 +297,10 @@ int filephy(const char* path, uint64_t size, uint64_t* physical) int fsinfo(const char* path, int* has_persistent_inode, uint64_t* total_space, uint64_t* free_space) { -#if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE + char type[64]; + const char* ptype; + +#if HAVE_STATFS struct statfs st; if (statfs(path, &st) != 0) { @@ -325,9 +328,11 @@ int fsinfo(const char* path, int* has_persistent_inode, uint64_t* total_space, u if (statfs(dir, &st) != 0) return -1; } +#endif /* to get the fs type check "man stat" or "stat -f -t FILE" */ - if (has_persistent_inode) + if (has_persistent_inode) { +#if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE switch (st.f_type) { case 0x65735546 : /* FUSE, "fuseblk" in the stat command */ case 0x4d44 : /* VFAT, "msdos" in the stat command */ @@ -338,22 +343,58 @@ int fsinfo(const char* path, int* has_persistent_inode, uint64_t* total_space, u *has_persistent_inode = 1; break; } +#else + /* by default assume yes */ + *has_persistent_inode = 1; +#endif + } - if (total_space) + if (total_space) { +#if HAVE_STATFS *total_space = st.f_bsize * (uint64_t)st.f_blocks; - if (free_space) +#else + *total_space = 0; +#endif + } + + if (free_space) { +#if HAVE_STATFS *free_space = st.f_bsize * (uint64_t)st.f_bfree; #else - (void)path; - - /* by default assume yes */ - if (has_persistent_inode) - *has_persistent_inode = 1; - if (total_space) - *total_space = 0; - if (free_space) *free_space = 0; #endif + } + +#if HAVE_STATFS && HAVE_STRUCT_STATFS_F_FSTYPENAME + /* get the filesystem type directly from the struct (Mac OS X) */ + (void)type; + ptype = st.f_fstypename; +#elif HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE + /* get the filesystem type from f_type (Linux) */ + /* from: https://github.com/influxdata/gopsutil/blob/master/disk/disk_linux.go */ + switch (st.f_type) { + case 0x65735546 : ptype = "fuseblk"; break; + case 0x4D44 : ptype = "vfat/msdos"; break; + case 0xEF53 : ptype = "ext2/3/4"; break; + case 0x6969 : ptype = "nfs"; break; /* remote */ + case 0x6E667364 : ptype = "nfsd"; break; /* remote */ + case 0x517B : ptype = "smb"; break; /* remote */ + case 0x5346544E : ptype = "ntfs"; break; + case 0x52654973 : ptype = "reiserfs"; break; + case 0x3153464A : ptype = "jfs" ; break; + case 0x58465342 : ptype = "xfs"; break; + case 0x9123683E : ptype = "btrfs"; break; + case 0x2FC12FC1 : ptype = "zfs"; break; + default: + snprintf(type, sizeof(type), "0x%X", (unsigned)st.f_type); + ptype = type; + } +#else + (void)type; + ptype = "unknown"; +#endif + + log_tag("statfs:%s: %s \n", ptype, path); return 0; } diff --git a/configure.ac b/configure.ac index c04f1a2a..7abaa164 100644 --- a/configure.ac +++ b/configure.ac @@ -77,6 +77,20 @@ AC_CHECK_MEMBERS([struct statfs.f_type], [], [], [[ #include #endif ]]) +AC_CHECK_MEMBERS([struct statfs.f_fstypename], [], [], [[ +#if HAVE_SYS_PARAM_H +#include +#endif +#if HAVE_SYS_MOUNT_H +#include +#endif +#if HAVE_SYS_VFS_H +#include +#endif +#if HAVE_SYS_STATFS_H +#include +#endif +]]) dnl Checks for library functions. AC_CHECK_FUNCS([memset strchr strerror strrchr mkdir gettimeofday strtoul]) From d6ccca3106f27551f9b48d30e4a2af5cf9616e77 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Thu, 7 Jan 2016 07:45:06 +0100 Subject: [PATCH 32/34] Ensure that in the regression test to recognize a different file We have failures in Travis for Mac for this issue. --- Makefile.am | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index f38c6c92..10c1a03f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -409,7 +409,7 @@ endif mkdir bench/disk3/empty3a mkdir bench/disk3/empty3a/emptyinner mkdir bench/disk3/empty3b - echo 1 > bench/disk1/target1 + echo TARGET1 > bench/disk1/target1 ln bench/disk1/target1 bench/disk1/file_hardlink1 ln bench/disk1/target1 bench/disk1/file_hardlink2 ln bench/disk1/target1 bench/disk1/file_hardlink3 @@ -421,13 +421,16 @@ endif $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) status $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-sequential -c $(PAR1) check #### CHANGE LINKS #### - echo 2 > bench/disk1/target2 +# Use a different size ("22" instead of "1") to ensure to recognize the file different +# even if it gets the same timestamp in case subsecond timestamp is no available + echo TARGET22 > bench/disk1/target2 rm bench/disk1/file_hardlink1 ln bench/disk1/target2 bench/disk1/file_hardlink1 rm bench/disk1/file_symlink1 ln -s bench/disk1/target2 bench/disk1/file_symlink1 $(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) --test-expect-need-sync diff > output.log - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) sync -l ">&1" + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) check -l ">&1" #### MISC COMMANDS #### $(MSG) Some commands with a not empty array $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(PAR1) dup @@ -438,7 +441,7 @@ endif $(MSG) Extend PAR1 to max parity with fix and check $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-expect-recoverable -c $(CONF) check -l test.log $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) fix -d 2-parity -d 3-parity -d 4-parity -d 5-parity -d 6-parity -l test.log - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) check + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) check -l ">&1" $(MSG) Fix with unaccessible parity and disk $(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(NOACCESS) fix --test-expect-failure $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(NOACCESS) fix --force-device --test-expect-recoverable -l test.log From d5d4d7896eab8550210e3693c162e99e75c61f1f Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sat, 9 Jan 2016 17:22:40 +0100 Subject: [PATCH 33/34] Message beautification in the regression test --- Makefile.am | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index 10c1a03f..5c903ac0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -274,7 +274,7 @@ endif $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) check $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) -a check #### CONTROLLED #### - @echo -- Filesystem allocation test + $(MSG) Filesystem allocation test head -c 8192 /dev/zero > bench/disk1/TEST # Copy the file to trigger the copy optimization check cp -a bench/disk1/TEST bench/disk2/TEST @@ -289,7 +289,7 @@ endif $(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) sync --test-expect-failure # Now sync will complete with -E $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) -E sync - @echo -- Filesystem fragmentation test + $(MSG) Filesystem fragmentation test head -c 8192 /dev/zero > bench/disk1/TEST1 head -c 8192 /dev/zero > bench/disk1/TEST2 head -c 8192 /dev/zero > bench/disk1/TEST3 @@ -445,7 +445,7 @@ endif $(MSG) Fix with unaccessible parity and disk $(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(NOACCESS) fix --test-expect-failure $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(NOACCESS) fix --force-device --test-expect-recoverable -l test.log - echo -- Rename a disk + $(MSG) Rename a disk $(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(RENAME) --test-match-first-uuid sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) --test-match-first-uuid sync #### SCRUB #### @@ -897,7 +897,7 @@ endif endif $(MSG) Regression test completed with SUCCESS! $(MSG) Please ignore any error message printed above, they are expected! - $(MSG) Everything is OK + $(MSG) Everything OK # Manual testing From 58e05e2dfebec8b6beb129c488314eb350f1c903 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sat, 9 Jan 2016 17:30:38 +0100 Subject: [PATCH 34/34] Update HISTORY --- HISTORY | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/HISTORY b/HISTORY index 95d633bb..fb8aacb9 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,15 @@ SnapRAID HISTORY ================ +10.0 2016/01 +=========== + * Increases the speed of the 'sync' and 'scrub' commands with a new + read-ahead implementation using threads. We have one thread for each + disk, dedicated exclusively to read data or parity in advance, + and to write parity later. + This ensures to keep the disks always busy, maximizing the data + throughput. + 9.2 2016/01 =========== * Fixes support for symlinks pointing to an empty target. Before they