From 5b551c1761c5faf74b6204bc64f2c3d9ffe6ae4e Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Sat, 17 Jan 2009 08:35:48 +0000 Subject: [PATCH] merged storage::readv() and storage::writev() --- docs/manual.rst | 56 +++---- include/libtorrent/file.hpp | 11 ++ src/file.cpp | 54 ++++++- src/storage.cpp | 284 ++++++++++++++---------------------- 4 files changed, 198 insertions(+), 207 deletions(-) diff --git a/docs/manual.rst b/docs/manual.rst index ec0662a92..a65ea48e6 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -4974,6 +4974,14 @@ this:: virtual bool release_files() = 0; virtual bool delete_files() = 0; virtual ~storage_interface() {} + + // non virtual functions + + disk_io_thread* io_thread(); + void set_error(boost::filesystem::path const& file, error_code const& ec) const; + error_code const& error() const; + std::string const& error_file() const; + void clear_error(); }; @@ -4991,40 +4999,17 @@ it will also ``ftruncate`` all files to their target size. Returning ``true`` indicates an error occurred. -readv() -------- +readv() writev() +---------------- :: int readv(file::iovec_t const* buf, int slot, int offset, int num_bufs) = 0; - -This function should read the data in the given ``slot`` and at the given ``offset``. -It should read ``num_bufs`` buffers, where the size of each buffer is specified in the -buffer array ``bufs``. The file::iovec_t type has the following members:: - - struct iovec_t - { - void* iov_base; - size_t iov_len; - }; - -The return value is the number of bytes actually read. - -Every buffer in ``bufs`` can be assumed to be page aligned and be of a page aligned size, -except for the last buffer of the torrent. The buffer can be assumed to fit a fully page -aligned number of bytes though. - - -writev() --------- - - :: - int write(const char* buf, int slot, int offset, int size) = 0; -This function should write the data to the given ``slot`` and at the given ``offset``. -It should write ``num_bufs`` buffers, where the size of each buffer is specified in the -buffer array ``bufs``. The file::iovec_t type has the following members:: +These functions should read or write the data in or to the given ``slot`` at the given ``offset``. +It should read or write ``num_bufs`` buffers sequentially, where the size of each buffer +is specified in the buffer array ``bufs``. The file::iovec_t type has the following members:: struct iovec_t { @@ -5032,13 +5017,18 @@ buffer array ``bufs``. The file::iovec_t type has the following members:: size_t iov_len; }; -The return value is the number of bytes actually written. +The return value is the number of bytes actually read or written, or -1 on failure. If +it returns -1, the error code is expected to be set to Every buffer in ``bufs`` can be assumed to be page aligned and be of a page aligned size, -except for the last buffer of the torrent. The buffer can be assumed to fit a fully page -aligned number of bytes though. -This function should write the data in ``buf`` to the given slot (``slot``) at offset -``offset`` in that slot. The buffer size is ``size``. +except for the last buffer of the torrent. The allocated buffer can be assumed to fit a +fully page aligned number of bytes though. This is useful when reading and writing the +last piece of a file in unbuffered mode. + +The ``offset`` is aligned to 16 kiB boundries *most of the time*, but there are rare +exceptions when it's not. Specifically if the read cache is disabled/or full and a +client requests unaligned data, or the file itself is not aligned in the torrent. +Most clients request aligned data. move_storage() diff --git a/include/libtorrent/file.hpp b/include/libtorrent/file.hpp index 828d2d954..4218f641f 100644 --- a/include/libtorrent/file.hpp +++ b/include/libtorrent/file.hpp @@ -130,6 +130,14 @@ namespace libtorrent void close(); bool set_size(size_type size, error_code& ec); + int open_mode() const { return m_open_mode; } + + // when opened in unbuffered mode, this is the + // required alignment of file_offsets. i.e. + // any (file_offset & (pos_alignment()-1)) == 0 + // is a precondition + int pos_alignment() const; + size_type writev(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec); size_type readv(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec); @@ -152,6 +160,9 @@ namespace libtorrent static int m_page_size; #endif int m_open_mode; +#if defined TORRENT_WINDOWS || defined TORRENT_LINUX + mutable int m_sector_size; +#endif }; } diff --git a/src/file.cpp b/src/file.cpp index c57dc4b4d..c3e89007d 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -115,6 +115,9 @@ namespace libtorrent : m_fd(-1) #endif , m_open_mode(0) +#if defined TORRENT_WINDOWS || defined TORRENT_LINUX + , m_sector_size(0) +#endif {} file::file(fs::path const& path, int mode, error_code& ec) @@ -216,8 +219,51 @@ namespace libtorrent #endif } + int file::pos_alignment() const + { + // on linux and windows, file offsets needs + // to be aligned to the disk sector size +#if defined TORRENT_LINUX + if (m_sector_size == 0) + { + struct statvfs fs; + if (fstatvfs(m_fd, &fs) == 0) + m_sector_size = fs.f_bsize; + else + m_sector_size = 4096; + } + return m_sector_size; +#elif defined TORRENT_WINDOWS + if (m_sector_size == 0) + { + DWORD sectors_per_cluster; + DWORD bytes_per_sector; + DWORD free_clusters; + DWORD total_clusters; +#ifdef TORRENT_USE_WPATH + wchar_t backslash = L'\\'; +#else + char backslash = '\\'; +#endif + if (GetDiskFreeSpace(m_path.substr(0, m_path.find_first_of(backslash)+1).c_str() + , §ors_per_cluster, &bytes_per_sector + , &free_clusters, &total_clusters)) + m_sector_size = bytes_per_sector; + else + m_sector_size = 4096; + } + return m_sector_size; +#else + return 1; +#endif + } + void file::close() { +#if defined TORRENT_WINDOWS || defined TORRENT_LINUX + m_sector_size = 0; +#endif + #ifdef TORRENT_WINDOWS if (m_file_handle == INVALID_HANDLE_VALUE) return; CloseHandle(m_file_handle); @@ -270,7 +316,9 @@ namespace libtorrent { bool eof = false; int size = 0; - TORRENT_ASSERT((file_offset & (m_page_size-1)) == 0); + // when opened in no_buffer mode, the file_offset must + // be aligned to pos_alignment() + TORRENT_ASSERT((file_offset & (pos_alignment()-1)) == 0); for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) { TORRENT_ASSERT((int(i->iov_base) & (m_page_size-1)) == 0); @@ -427,7 +475,9 @@ namespace libtorrent { bool eof = false; int size = 0; - TORRENT_ASSERT((file_offset & (m_page_size-1)) == 0); + // when opened in no_buffer mode, the file_offset must + // be aligned to pos_alignment() + TORRENT_ASSERT((file_offset & (pos_alignment()-1)) == 0); for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) { TORRENT_ASSERT((int(i->iov_base) & (m_page_size-1)) == 0); diff --git a/src/storage.cpp b/src/storage.cpp index ec6960ce7..b23d22ef7 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -445,9 +445,30 @@ namespace libtorrent bool write_resume_data(entry& rd) const; sha1_hash hash_for_slot(int slot, partial_hash& ph, int piece_size); + // this identifies a read or write operation + // so that storage::readwrite() knows what to + // do when it's actually touching the file + struct fileop + { + size_type (file::*regular_op)(size_type file_offset + , file::iovec_t const* bufs, int num_bufs, error_code& ec); + size_type (storage::*unaligned_op)(boost::shared_ptr const& f + , size_type file_offset, file::iovec_t const* bufs, int num_bufs + , error_code& ec); + int mode; + }; + + int readwritev(file::iovec_t const* bufs, int slot, int offset + , int num_bufs, fileop const&); + ~storage() { m_pool.release(this); } + size_type read_unaligned(boost::shared_ptr const& file_handle + , size_type file_offset, file::iovec_t const* bufs, int num_bufs, error_code& ec); + size_type write_unaligned(boost::shared_ptr const& file_handle + , size_type file_offset, file::iovec_t const* bufs, int num_bufs, error_code& ec); + file_storage const& files() const { return m_mapped_files?*m_mapped_files:m_files; } boost::scoped_ptr m_mapped_files; @@ -1020,20 +1041,36 @@ ret: return r; } - int storage::readv( - file::iovec_t const* bufs - , int slot - , int offset + int storage::writev(file::iovec_t const* bufs, int slot, int offset , int num_bufs) + { + fileop op = { &file::writev, &storage::write_unaligned, file::read_write }; + return readwritev(bufs, slot, offset, num_bufs, op); + } + + int storage::readv(file::iovec_t const* bufs, int slot, int offset + , int num_bufs) + { + fileop op = { &file::readv, &storage::read_unaligned, file::read_only }; + return readwritev(bufs, slot, offset, num_bufs, op); + } + + // much of what needs to be done when reading and writing + // is buffer management and piece to file mapping. Most + // of that is the same for reading and writing. This function + // is a template, and the fileop decides what to do with the + // file and the buffers. + int storage::readwritev(file::iovec_t const* bufs, int slot, int offset + , int num_bufs, fileop const& op) { TORRENT_ASSERT(bufs != 0); - TORRENT_ASSERT(slot >= 0 && slot < m_files.num_pieces()); + TORRENT_ASSERT(slot >= 0); + TORRENT_ASSERT(slot < m_files.num_pieces()); TORRENT_ASSERT(offset >= 0); TORRENT_ASSERT(offset < m_files.piece_size(slot)); TORRENT_ASSERT(num_bufs > 0); int size = bufs_size(bufs, num_bufs); - TORRENT_ASSERT(size > 0); #ifdef TORRENT_DEBUG @@ -1058,21 +1095,18 @@ ret: ++file_iter; TORRENT_ASSERT(file_iter != files().end()); } - + int buf_pos = 0; error_code ec; - boost::shared_ptr in; - int left_to_read = size; + boost::shared_ptr file_handle; + int bytes_left = size; int slot_size = static_cast(m_files.piece_size(slot)); - if (offset + left_to_read > slot_size) - left_to_read = slot_size - offset; + if (offset + bytes_left > slot_size) + bytes_left = slot_size - offset; - TORRENT_ASSERT(left_to_read >= 0); - - size_type result = left_to_read; - TORRENT_ASSERT(left_to_read == size); + TORRENT_ASSERT(bytes_left >= 0); #ifdef TORRENT_DEBUG int counter = 0; @@ -1082,23 +1116,23 @@ ret: file::iovec_t* current_buf = TORRENT_ALLOCA(file::iovec_t, num_bufs); copy_bufs(bufs, size, current_buf); TORRENT_ASSERT(count_bufs(current_buf, size) == num_bufs); - int read_bytes; - for (;left_to_read > 0; ++file_iter, left_to_read -= read_bytes - , buf_pos += read_bytes) + int file_bytes_left; + for (;bytes_left > 0; ++file_iter, bytes_left -= file_bytes_left + , buf_pos += file_bytes_left) { TORRENT_ASSERT(file_iter != files().end()); TORRENT_ASSERT(buf_pos >= 0); - read_bytes = left_to_read; - if (file_offset + read_bytes > file_iter->size) - read_bytes = (std::max)(static_cast(file_iter->size - file_offset), 0); + file_bytes_left = bytes_left; + if (file_offset + file_bytes_left > file_iter->size) + file_bytes_left = (std::max)(static_cast(file_iter->size - file_offset), 0); - if (read_bytes == 0) continue; + if (file_bytes_left == 0) continue; #ifdef TORRENT_DEBUG TORRENT_ASSERT(int(slices.size()) > counter); size_type slice_size = slices[counter].size; - TORRENT_ASSERT(slice_size == read_bytes); + TORRENT_ASSERT(slice_size == file_bytes_left); TORRENT_ASSERT(files().at(slices[counter].file_index).path == file_iter->path); ++counter; @@ -1106,35 +1140,52 @@ ret: if (file_iter->pad_file) { - int num_tmp_bufs = copy_bufs(current_buf, read_bytes, tmp_bufs); - TORRENT_ASSERT(count_bufs(tmp_bufs, read_bytes) == num_tmp_bufs); - TORRENT_ASSERT(num_tmp_bufs <= num_bufs); - clear_bufs(tmp_bufs, num_tmp_bufs); - advance_bufs(current_buf, read_bytes); - TORRENT_ASSERT(count_bufs(current_buf, left_to_read - read_bytes) <= num_bufs); + if (op.mode == file::read_only) + { + int num_tmp_bufs = copy_bufs(current_buf, file_bytes_left, tmp_bufs); + TORRENT_ASSERT(count_bufs(tmp_bufs, file_bytes_left) == num_tmp_bufs); + TORRENT_ASSERT(num_tmp_bufs <= num_bufs); + clear_bufs(tmp_bufs, num_tmp_bufs); + } + advance_bufs(current_buf, file_bytes_left); + TORRENT_ASSERT(count_bufs(current_buf, bytes_left - file_bytes_left) <= num_bufs); continue; } fs::path path = m_save_path / file_iter->path; error_code ec; - int mode = file::read_only; + int mode = op.mode; if (io_thread() && io_thread()->no_buffer() && ((file_iter->offset + file_iter->file_base) & (m_page_size-1)) == 0) mode |= file::no_buffer; - in = m_pool.open_file(this, path, mode, ec); - if (!in || ec) + file_handle = m_pool.open_file(this, path, mode, ec); + if (!file_handle || ec) { set_error(path, ec); return -1; } - int num_tmp_bufs = copy_bufs(current_buf, read_bytes, tmp_bufs); - TORRENT_ASSERT(count_bufs(tmp_bufs, read_bytes) == num_tmp_bufs); + int num_tmp_bufs = copy_bufs(current_buf, file_bytes_left, tmp_bufs); + TORRENT_ASSERT(count_bufs(tmp_bufs, file_bytes_left) == num_tmp_bufs); TORRENT_ASSERT(num_tmp_bufs <= num_bufs); - int actual_read = int(in->readv(file_iter->file_base - + file_offset, tmp_bufs, num_tmp_bufs, ec)); + int bytes_transferred = 0; + // if the file is opened in no_buffer mode, and the + // read is unaligned, we need to fall back on a slow + // special read that reads aligned buffers and copies + // it into the one supplied + if ((file_handle->open_mode() & file::no_buffer) + && ((file_iter->file_base + file_offset) & (file_handle->pos_alignment()-1)) != 0) + { + bytes_transferred = (this->*op.unaligned_op)(file_handle, file_iter->file_base + + file_offset, tmp_bufs, num_tmp_bufs, ec); + } + else + { + bytes_transferred = (int)((*file_handle).*op.regular_op)(file_iter->file_base + + file_offset, tmp_bufs, num_tmp_bufs, ec); + } file_offset = 0; if (ec) @@ -1143,21 +1194,41 @@ ret: return -1; } - if (read_bytes != actual_read) + if (file_bytes_left != bytes_transferred) { // the file was not big enough #ifdef TORRENT_WINDOWS - ec = error_code(ERROR_READ_FAULT, get_system_category()); + ec = error_code(ERROR_HANDLE_EOF, get_system_category()); #else ec = error_code(EIO, get_posix_category()); #endif set_error(m_save_path / file_iter->path, ec); return -1; } - advance_bufs(current_buf, actual_read); - TORRENT_ASSERT(count_bufs(current_buf, left_to_read - read_bytes) <= num_bufs); + advance_bufs(current_buf, bytes_transferred); + TORRENT_ASSERT(count_bufs(current_buf, bytes_left - file_bytes_left) <= num_bufs); } - return result; + return size; + } + + // these functions are inefficient, but should be fairly uncommon. The read + // case happens if unaligned files are opened in no_buffer mode or if clients + // makes unaligned requests (and the disk cache is disabled or fully utilized + // for write cache). + + // they read an unaligned buffer from a file that requires aligned access + size_type storage::read_unaligned(boost::shared_ptr const& file_handle + , size_type file_offset, file::iovec_t const* bufs, int num_bufs, error_code& ec) + { + TORRENT_ASSERT(false); // not implemented + return 0; + } + + size_type storage::write_unaligned(boost::shared_ptr const& file_handle + , size_type file_offset, file::iovec_t const* bufs, int num_bufs, error_code& ec) + { + TORRENT_ASSERT(false); // not implemented + return 0; } int storage::write( @@ -1184,137 +1255,6 @@ ret: return readv(&b, slot, offset, 1); } - int storage::writev( - file::iovec_t const* bufs - , int slot - , int offset - , int num_bufs) - { - TORRENT_ASSERT(bufs != 0); - TORRENT_ASSERT(slot >= 0); - TORRENT_ASSERT(slot < m_files.num_pieces()); - TORRENT_ASSERT(offset >= 0); - TORRENT_ASSERT(num_bufs > 0); - - int size = bufs_size(bufs, num_bufs); - TORRENT_ASSERT(size > 0); - -#ifdef TORRENT_DEBUG - std::vector slices - = files().map_block(slot, offset, size); - TORRENT_ASSERT(!slices.empty()); -#endif - - size_type start = slot * (size_type)m_files.piece_length() + offset; - TORRENT_ASSERT(start + size <= m_files.total_size()); - - // find the file iterator and file offset - size_type file_offset = start; - std::vector::const_iterator file_iter; - - for (file_iter = files().begin();;) - { - if (file_offset < file_iter->size) - break; - - file_offset -= file_iter->size; - ++file_iter; - TORRENT_ASSERT(file_iter != files().end()); - } - - int buf_pos = 0; - error_code ec; - - boost::shared_ptr out; - int left_to_write = size; - int slot_size = static_cast(m_files.piece_size(slot)); - - if (offset + left_to_write > slot_size) - left_to_write = slot_size - offset; - - TORRENT_ASSERT(left_to_write >= 0); - -#ifdef TORRENT_DEBUG - int counter = 0; -#endif - - file::iovec_t* tmp_bufs = TORRENT_ALLOCA(file::iovec_t, num_bufs); - file::iovec_t* current_buf = TORRENT_ALLOCA(file::iovec_t, num_bufs); - copy_bufs(bufs, size, current_buf); - TORRENT_ASSERT(count_bufs(current_buf, size) == num_bufs); - int write_bytes; - for (;left_to_write > 0; ++file_iter, left_to_write -= write_bytes - , buf_pos += write_bytes) - { - TORRENT_ASSERT(file_iter != files().end()); - TORRENT_ASSERT(buf_pos >= 0); - - write_bytes = left_to_write; - if (file_offset + write_bytes > file_iter->size) - write_bytes = (std::max)(static_cast(file_iter->size - file_offset), 0); - - if (write_bytes == 0) continue; - -#ifdef TORRENT_DEBUG - TORRENT_ASSERT(int(slices.size()) > counter); - size_type slice_size = slices[counter].size; - TORRENT_ASSERT(slice_size == write_bytes); - TORRENT_ASSERT(files().at(slices[counter].file_index).path - == file_iter->path); - ++counter; -#endif - - if (file_iter->pad_file) - { - advance_bufs(current_buf, write_bytes); - TORRENT_ASSERT(count_bufs(current_buf, left_to_write - write_bytes) <= num_bufs); - continue; - } - - fs::path path = m_save_path / file_iter->path; - - error_code ec; - int mode = file::read_write; - if (io_thread() - && io_thread()->no_buffer() - && ((file_iter->offset + file_iter->file_base) & (m_page_size-1)) == 0) - mode |= file::no_buffer; - out = m_pool.open_file(this, path, mode, ec); - if (!out || ec) - { - set_error(path, ec); - return -1; - } - - int num_tmp_bufs = copy_bufs(current_buf, write_bytes, tmp_bufs); - TORRENT_ASSERT(count_bufs(tmp_bufs, write_bytes) == num_tmp_bufs); - int actual_written = int(out->writev(file_iter->file_base - + file_offset, tmp_bufs, num_tmp_bufs, ec)); - - file_offset = 0; - - if (ec) - { - set_error(m_save_path / file_iter->path, ec); - return -1; - } - - if (write_bytes != actual_written) - { - // the file was not big enough -#ifdef TORRENT_WINDOWS - ec = error_code(ERROR_READ_FAULT, get_system_category()); -#else - ec = error_code(EIO, get_posix_category()); -#endif - set_error(m_save_path / file_iter->path, ec); - return -1; - } - advance_bufs(current_buf, actual_written); - } - return size; - } - storage_interface* default_storage_constructor(file_storage const& fs , fs::path const& path, file_pool& fp) {