最近在設計一個多線程分塊支持續傳的http的異步客戶端時, 測試部門常常發現http下載模
塊退出時偶爾會卡住, 在win7系統上由爲明顯. 反覆檢查代碼, 並未明顯問題, 因而專門寫
了一個反覆退出的單元測試, 當即發現問題, 並定位在io_service的析構函數中, 奇怪的是,
個人投遞io的全部socket都早已經關閉, run線程也已經退出, 按理說, 這時的io_service的
outstanding_work_應該爲0纔是, 可我一看它倒是1, 因而始終在win_iocp_io_service.hpp的
shutdown_service裏一直循環調用GetQueuedCompletionStatus, 從而致使沒法正常退出...
很明顯, 這是asio對outstanding_work_計數維護的有問題, 爲了解決
問題, 因而我很快想到不使用iocp, 添加宏BOOST_ASIO_DISABLE_IOCP一切就正常了...
因爲本身使用的是boost.1.45版本, 因而換了個boost.1.46.1再試試, 結果同樣. 難道這麼嚴
重的bug跨在了這兩個很是重要的發行版本上而沒人知道?
在官方的郵件列表中細節檢查, 終於看到了某人的bug報告和我描述的狀況差很少, 並且
在那我的報告了bug的次日, asio做者就發佈了補丁, 但這個補丁並未更新到boost.1.45
和boost.1.46中, 唉, 這兩個版本但是大版本啊, 估計受害人很多...
不過幸運的是, 我在boost的主分枝中看到了修正的代碼.
下面是這個補丁內容:
react
From 81a6a51c0cb66de6bc77e1fa5dcd46b2794995e4 Mon Sep 17 00:00:00 2001 From: Christopher Kohlhoff <chris@kohlhoff.com> Date: Wed, 23 Mar 2011 15:03:56 +1100 Subject: [PATCH] On Windows, ensure the count of outstanding work is decremented for abandoned operations (i.e. operations that are being cleaned up within the io_service destructor). --- asio/include/asio/detail/impl/dev_poll_reactor.ipp | 2 ++ asio/include/asio/detail/impl/epoll_reactor.ipp | 2 ++ asio/include/asio/detail/impl/kqueue_reactor.ipp | 2 ++ asio/include/asio/detail/impl/select_reactor.ipp | 2 ++ .../asio/detail/impl/signal_set_service.ipp | 2 ++ asio/include/asio/detail/impl/task_io_service.ipp | 7 +++++++ .../asio/detail/impl/win_iocp_io_service.ipp | 11 +++++++++++ asio/include/asio/detail/task_io_service.hpp | 4 ++++ asio/include/asio/detail/win_iocp_io_service.hpp | 4 ++++ 9 files changed, 36 insertions(+), 0 deletions(-) diff --git a/asio/include/asio/detail/impl/dev_poll_reactor.ipp b/asio/include/asio/detail/impl/dev_poll_reactor.ipp index 06d89ea..2a01993 100644 --- a/asio/include/asio/detail/impl/dev_poll_reactor.ipp +++ b/asio/include/asio/detail/impl/dev_poll_reactor.ipp @@ -63,6 +63,8 @@ void dev_poll_reactor::shutdown_service() op_queue_[i].get_all_operations(ops); timer_queues_.get_all_timers(ops); + + io_service_.abandon_operations(ops); } // Helper class to re-register all descriptors with /dev/poll. diff --git a/asio/include/asio/detail/impl/epoll_reactor.ipp b/asio/include/asio/detail/impl/epoll_reactor.ipp index 22f567a..d08dedb 100644 --- a/asio/include/asio/detail/impl/epoll_reactor.ipp +++ b/asio/include/asio/detail/impl/epoll_reactor.ipp @@ -84,6 +84,8 @@ void epoll_reactor::shutdown_service() } timer_queues_.get_all_timers(ops); + + io_service_.abandon_operations(ops); } void epoll_reactor::fork_service(asio::io_service::fork_event fork_ev) diff --git a/asio/include/asio/detail/impl/kqueue_reactor.ipp b/asio/include/asio/detail/impl/kqueue_reactor.ipp index f0cdf73..45aff60 100644 --- a/asio/include/asio/detail/impl/kqueue_reactor.ipp +++ b/asio/include/asio/detail/impl/kqueue_reactor.ipp @@ -74,6 +74,8 @@ void kqueue_reactor::shutdown_service() } timer_queues_.get_all_timers(ops); + + io_service_.abandon_operations(ops); } void kqueue_reactor::fork_service(asio::io_service::fork_event fork_ev) diff --git a/asio/include/asio/detail/impl/select_reactor.ipp b/asio/include/asio/detail/impl/select_reactor.ipp index f4e0314..00fd9fc 100644 --- a/asio/include/asio/detail/impl/select_reactor.ipp +++ b/asio/include/asio/detail/impl/select_reactor.ipp @@ -81,6 +81,8 @@ void select_reactor::shutdown_service() op_queue_[i].get_all_operations(ops); timer_queues_.get_all_timers(ops); + + io_service_.abandon_operations(ops); } void select_reactor::fork_service(asio::io_service::fork_event fork_ev) diff --git a/asio/include/asio/detail/impl/signal_set_service.ipp b/asio/include/asio/detail/impl/signal_set_service.ipp index f0f0e78..4cde184 100644 --- a/asio/include/asio/detail/impl/signal_set_service.ipp +++ b/asio/include/asio/detail/impl/signal_set_service.ipp @@ -145,6 +145,8 @@ void signal_set_service::shutdown_service() reg = reg->next_in_table_; } } + + io_service_.abandon_operations(ops); } void signal_set_service::fork_service( diff --git a/asio/include/asio/detail/impl/task_io_service.ipp b/asio/include/asio/detail/impl/task_io_service.ipp index cb585d5..0a2c6fa 100644 --- a/asio/include/asio/detail/impl/task_io_service.ipp +++ b/asio/include/asio/detail/impl/task_io_service.ipp @@ -230,6 +230,13 @@ void task_io_service::post_deferred_completions( } } +void task_io_service::abandon_operations( + op_queue<task_io_service::operation>& ops) +{ + op_queue<task_io_service::operation> ops2; + ops2.push(ops); +} + std::size_t task_io_service::do_one(mutex::scoped_lock& lock, task_io_service::idle_thread_info* this_idle_thread) { diff --git a/asio/include/asio/detail/impl/win_iocp_io_service.ipp b/asio/include/asio/detail/impl/win_iocp_io_service.ipp index ca3125e..7aaa6b8 100644 --- a/asio/include/asio/detail/impl/win_iocp_io_service.ipp +++ b/asio/include/asio/detail/impl/win_iocp_io_service.ipp @@ -262,6 +262,17 @@ void win_iocp_io_service::post_deferred_completions( } } +void win_iocp_io_service::abandon_operations( + op_queue<win_iocp_operation>& ops) +{ + while (win_iocp_operation* op = ops.front()) + { + ops.pop(); + ::InterlockedDecrement(&outstanding_work_); + op->destroy(); + } +} + void win_iocp_io_service::on_pending(win_iocp_operation* op) { if (::InterlockedCompareExchange(&op->ready_, 1, 0) == 1) diff --git a/asio/include/asio/detail/task_io_service.hpp b/asio/include/asio/detail/task_io_service.hpp index 285d83e..654f83c 100644 --- a/asio/include/asio/detail/task_io_service.hpp +++ b/asio/include/asio/detail/task_io_service.hpp @@ -105,6 +105,10 @@ public: // that work_started() was previously called for each operation. ASIO_DECL void post_deferred_completions(op_queue<operation>& ops); + // Process unfinished operations as part of a shutdown_service operation. + // Assumes that work_started() was previously called for the operations. + ASIO_DECL void abandon_operations(op_queue<operation>& ops); + private: // Structure containing information about an idle thread. struct idle_thread_info; diff --git a/asio/include/asio/detail/win_iocp_io_service.hpp b/asio/include/asio/detail/win_iocp_io_service.hpp index a562834..b5d7f0b 100644 --- a/asio/include/asio/detail/win_iocp_io_service.hpp +++ b/asio/include/asio/detail/win_iocp_io_service.hpp @@ -126,6 +126,10 @@ public: ASIO_DECL void post_deferred_completions( op_queue<win_iocp_operation>& ops); + // Enqueue unfinished operation as part of a shutdown_service operation. + // Assumes that work_started() was previously called for the operations. + ASIO_DECL void abandon_operations(op_queue<operation>& ops); + // Called after starting an overlapped I/O operation that did not complete // immediately. The caller must have already called work_started() prior to // starting the operation. -- 1.7.0.1
注: 在boost.asio中, 使用這個補丁時, 須要將ASIO_DECL 改爲 BOOST_ ASIO_DECL
注: 最新發布的boost1.47版本已經修復了這個bug, 建議儘可能採用新版本的boost.
git
這是我第二次在使用asio的過程當中, 發現的比較嚴重的bug了, 不過幸運的是, 每一次都能在官方的論壇
或郵件列表中找到解決方案. 多線程