diff --git a/ChangeLog b/ChangeLog index dd7d1813b..b779d1619 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * close files in separate thread on systems where close() may block (Mac OS X for instance) * don't create all directories up front when adding torrents * support DHT scrape * added support for fadvise/F_RDADVISE for improved disk read performance diff --git a/include/libtorrent/config.hpp b/include/libtorrent/config.hpp index 7aa60b7e9..6015fff37 100644 --- a/include/libtorrent/config.hpp +++ b/include/libtorrent/config.hpp @@ -144,6 +144,7 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_USE_ICONV #define TORRENT_USE_ICONV 0 #define TORRENT_USE_LOCALE 0 +#define TORRENT_CLOSE_MAY_BLOCK 1 #endif #endif #define TORRENT_HAS_FALLOCATE 0 @@ -311,6 +312,14 @@ inline int snprintf(char* buf, int len, char const* fmt, ...) #define TORRENT_USE_LOCALE 0 #endif +// set this to true if close() may block on your system +// Mac OS X does this if the file being closed is not fully +// allocated on disk yet for instance. When defined, the disk +// I/O subsytem will use a separate thread for closing files +#ifndef TORRENT_CLOSE_MAY_BLOCK +#define TORRENT_CLOSE_MAY_BLOCK 0 +#endif + #ifndef TORRENT_BROKEN_UNIONS #define TORRENT_BROKEN_UNIONS 0 #endif diff --git a/include/libtorrent/file_pool.hpp b/include/libtorrent/file_pool.hpp index 6b1c51e3a..14ad433a8 100644 --- a/include/libtorrent/file_pool.hpp +++ b/include/libtorrent/file_pool.hpp @@ -53,7 +53,8 @@ namespace libtorrent { struct TORRENT_EXPORT file_pool : boost::noncopyable { - file_pool(int size = 40): m_size(size), m_low_prio_io(true) {} + file_pool(int size = 40); + ~file_pool(); boost::intrusive_ptr open_file(void* st, std::string const& p , file_storage::iterator fe, file_storage const& fs, int m, error_code& ec); @@ -64,7 +65,6 @@ namespace libtorrent void set_low_prio_io(bool b) { m_low_prio_io = b; } private: - file_pool(file_pool const&); void remove_oldest(); @@ -86,6 +86,16 @@ namespace libtorrent file_set m_files; mutex m_mutex; + +#if TORRENT_CLOSE_MAY_BLOCK + void closer_thread_fun(); + mutex m_closer_mutex; + std::vector > m_queued_for_close; + bool m_stop_thread; + + // used to close files + thread m_closer_thread; +#endif }; } diff --git a/src/file_pool.cpp b/src/file_pool.cpp index bea27a3ed..3f43515aa 100644 --- a/src/file_pool.cpp +++ b/src/file_pool.cpp @@ -40,6 +40,67 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { + + file_pool::file_pool(int size) + : m_size(size) + , m_low_prio_io(true) +#if TORRENT_CLOSE_MAY_BLOCK + , m_stop_thread(false) + , m_closer_thread(boost::bind(&file_pool::closer_thread_fun, this)) +#endif + {} + + file_pool::~file_pool() + { +#if TORRENT_CLOSE_MAY_BLOCK + mutex::scoped_lock l(m_closer_mutex); + m_stop_thread = true; + l.unlock(); + // wait for hte closer thread to finish closing all files + m_closer_thread.join(); +#endif + } + +#if TORRENT_CLOSE_MAY_BLOCK + void file_pool::closer_thread_fun() + { + for (;;) + { + mutex::scoped_lock l(m_closer_mutex); + if (m_stop_thread) + { + l.unlock(); + m_queued_for_close.clear(); + return; + } + + // find a file that doesn't have any other threads referencing + // it. Since only those files can be closed in this thead + std::vector >::iterator i = std::find_if( + m_queued_for_close.begin(), m_queued_for_close.end() + , boost::bind(&file::refcount, boost::bind(&boost::intrusive_ptr::get, _1)) == 1); + + if (i == m_queued_for_close.end()) + { + l.unlock(); + // none of the files are ready to be closet yet + // because they're still in use by other threads + // hold off for a while + sleep(1000); + } + else + { + // ok, first pull the file out of the queue, release the mutex + // (since closing the file may block) and then close it. + boost::intrusive_ptr f = *i; + m_queued_for_close.erase(i); + l.unlock(); + f->close(); + } + } + } +#endif + boost::intrusive_ptr file_pool::open_file(void* st, std::string const& p , file_storage::iterator fe, file_storage const& fs, int m, error_code& ec) { @@ -77,7 +138,15 @@ namespace libtorrent // close the file before we open it with // the new read/write privilages TORRENT_ASSERT(e.file_ptr->refcount() == 1); + +#if TORRENT_CLOSE_MAY_BLOCK + mutex::scoped_lock l(m_closer_mutex); + m_queued_for_close.push_back(e.file_ptr); + l.unlock(); + e.file_ptr = new file; +#else e.file_ptr->close(); +#endif std::string full_path = combine_path(p, fs.file_path(*fe)); if (!e.file_ptr->open(full_path, m, ec)) { @@ -133,6 +202,12 @@ namespace libtorrent , boost::bind(&lru_file_entry::last_use, boost::bind(&file_set::value_type::second, _1)) < boost::bind(&lru_file_entry::last_use, boost::bind(&file_set::value_type::second, _2))); if (i == m_files.end()) return; + +#if TORRENT_CLOSE_MAY_BLOCK + mutex::scoped_lock l(m_closer_mutex); + m_queued_for_close.push_back(i->second.file_ptr); + l.unlock(); +#endif m_files.erase(i); } @@ -140,7 +215,14 @@ namespace libtorrent { mutex::scoped_lock l(m_mutex); file_set::iterator i = m_files.find(std::make_pair(st, file_index)); - if (i != m_files.end()) m_files.erase(i); + if (i == m_files.end()) return; + +#if TORRENT_CLOSE_MAY_BLOCK + mutex::scoped_lock l2(m_closer_mutex); + m_queued_for_close.push_back(i->second.file_ptr); + l2.unlock(); +#endif + m_files.erase(i); } // closes files belonging to the specified