restarting tcp boost asio io_service with uncompleted completion handlers - multithreading

I wrote an asynchronous boost::asio TCP application that uses a pool of threads declared as std::vector<std::thread> mIOServicePool. These threads asynchronously read and write TCP data to a server. The following code is taken from the GUI's start push button event handler.
// launch multiple asio service threads to
// handle the protocol instances - effectively
// thread pooling the ioservice
//mpIOService->reset();
for (auto i=0; i<3; i++) {
mIOServicePool.emplace_back(
std::thread([this]() { mpIOService->run(); }));
}
The code is part of a Qt based GUI application with the mIOServicePool stored as a member of the GUI's mainwindow class.
This works fine when I start the application and leave it running, however, while attempting to restart the connection to the back end server, things start to go wrong. The problem is most likely related to uncompleted handlers which I thought would have been flushed when I reset the io_service::work associated with the io_service (when the GUI stop button is pressed). The problem manifests itself when I attempt to start the TCP communications (on windows at least) via an access violation while reading memory asio::socket's stream buffer. As you can see from the stack trace below, it is handling a completion handler associated with the read socket.
app739.exe!boost::asio::basic_streambuf<std::allocator<char> >::commit(unsigned __int64 n) Line 226 C++
app739.exe!boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > >::operator()(const boost::system::error_code & ec, unsigned __int64 bytes_transferred, int start) Line 624 C++
app739.exe!boost::asio::detail::binder2<boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > >,boost::system::error_code,unsigned __int64>::operator()() Line 129 C++
app739.exe!boost::asio::asio_handler_invoke<boost::asio::detail::binder2<boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > >,boost::system::error_code,unsigned __int64> >(boost::asio::detail::binder2<boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const &>,boost::_bi::list2<boost::_bi::value<VCDUProtocol *>,boost::arg<1> > > >,boost::system::error_code,unsigned __int64> & function, ...) Line 70 C++
app739.exe!boost_asio_handler_invoke_helpers::invoke<boost::asio::detail::binder2<boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > >,boost::system::error_code,unsigned __int64>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > >(boost::asio::detail::binder2<boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const &>,boost::_bi::list2<boost::_bi::value<VCDUProtocol *>,boost::arg<1> > > >,boost::system::error_code,unsigned __int64> & function, boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const &>,boost::_bi::list2<boost::_bi::value<VCDUProtocol *>,boost::arg<1> > > & context) Line 39 C++
app739.exe!boost::asio::detail::asio_handler_invoke<boost::asio::detail::binder2<boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > >,boost::system::error_code,unsigned __int64>,boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > >(boost::asio::detail::binder2<boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const &>,boost::_bi::list2<boost::_bi::value<VCDUProtocol *>,boost::arg<1> > > >,boost::system::error_code,unsigned __int64> & function, boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const &>,boost::_bi::list2<boost::_bi::value<VCDUProtocol *>,boost::arg<1> > > > * this_handler) Line 685 C++
app739.exe!boost_asio_handler_invoke_helpers::invoke<boost::asio::detail::binder2<boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > >,boost::system::error_code,unsigned __int64>,boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > > >(boost::asio::detail::binder2<boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const &>,boost::_bi::list2<boost::_bi::value<VCDUProtocol *>,boost::arg<1> > > >,boost::system::error_code,unsigned __int64> & function, boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const &>,boost::_bi::list2<boost::_bi::value<VCDUProtocol *>,boost::arg<1> > > > & context) Line 39 C++
app739.exe!boost::asio::detail::win_iocp_socket_recv_op<boost::asio::mutable_buffers_1,boost::asio::detail::read_until_delim_string_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::allocator<char>,boost::_bi::bind_t<void,boost::_mfi::mf1<void,VCDUProtocol,boost::system::error_code const & __ptr64>,boost::_bi::list2<boost::_bi::value<VCDUProtocol * __ptr64>,boost::arg<1> > > > >::do_complete(boost::asio::detail::win_iocp_io_service * owner, boost::asio::detail::win_iocp_operation * base, const boost::system::error_code & result_ec, unsigned __int64 bytes_transferred) Line 97 C++
app739.exe!boost::asio::detail::win_iocp_operation::complete(boost::asio::detail::win_iocp_io_service & owner, const boost::system::error_code & ec, unsigned __int64 bytes_transferred) Line 47 C++
app739.exe!boost::asio::detail::win_iocp_io_service::do_one(bool block, boost::system::error_code & ec) Line 406 C++
app739.exe!boost::asio::detail::win_iocp_io_service::run(boost::system::error_code & ec) Line 164 C++
app739.exe!boost::asio::io_service::run() Line 59 C++
app739.exe!MainWindow::on_pushButtonStart_clicked::__l13::<lambda>() Line 943 C++
This answer indicates that the problem may have to do with io_service.reset(). The stack-trace at the time of the access violation shows that the thread was processing an asio completion handler. I think from reading other posts that the key to resolving this is to correctly sequence io_service.stop() and io_service.reset() to the boost::asio::io_service object, also it may be important to reset the socket prior to stopping the io_service or resetting the sentinel work object.
The code below shows how I attempt to stop the io_service threads, while debugging the code I do see all the threads complete their joins so I do not understand why there are completion handlers outsanding.
void
MainWindow::stopSys()
{
// make sure that we have no more work keeping services alive
mpWork.reset();
// check to see if the protocol threads were started
if (mVCDUProtocol) {
// terminate protocol thread by setting the shared mShutdown atomic flag
mVCDUProtocol->shutdown();
// Once each thread sees the shutdown flag, it will cleanly
// terminate so we can call join here to wait for the entire
// pool to finish
std::for_each(mIOServicePool.begin(), mIOServicePool.end(),
[](std::thread& rNext) {
rNext.join();
});
mIOServicePool.clear();
}
}
My code shown below is pretty straightforward. It kicks off an asynchronous resolve - which is handled within a lambda handler. From there, it calls the start_async_ops(endPointIter) to perform an asynchronous connect() and from this lambda, the code calls VCDUProtocol::do_read() which performs a boost::asio::async_read_until() to wait for the data from the server.
void
VCDUProtocol::prosimAsyncIOThreadFn()
{
static auto& gLogger = gpLogger->getLoggerRef(
gUseSysLog ? Logger::LogDest::SysLog :
Logger::LogDest::EventLog);
try {
// convert the host-name/port to a usable endpoint
tcp::resolver resolver(*mpIOService);
tcp::resolver::query query(mProtocolConfig.getProsimHostName(),
std::to_string(mProtocolConfig.getProsimPortNum()));
const auto endPointIter = std::find_if(
resolver.resolve(query), tcp::resolver::iterator(),
[](const tcp::endpoint& next) {
return next.protocol() == tcp::v4();
});
if (endPointIter != tcp::resolver::iterator()) {
mpSocket = std::make_unique<tcp::socket>(*mpIOService);
mpSocketTimer = std::make_unique<deadline_timer>(*mpIOService);
start_async_ops(endPointIter);
}
} catch (std::exception& rEx) {
LOG_ERROR(gLogger, gChannel) << boost::format(
"%1%: %2%")
% __FUNCTION__
% rEx.what();
}
}
void
VCDUProtocol::start_async_ops(tcp::resolver::iterator endpoint_iter)
{
// Start the connect actor.
do_connect(endpoint_iter);
// Start the deadline actor. You will note that we're not setting any
// particular deadline here. Instead, the connect and input actors will
// update the deadline prior to each asynchronous operation.
mpSocketTimer->async_wait(boost::bind(
&VCDUProtocol::check_deadline, this, _1));
}
void
VCDUProtocol::do_connect(
tcp::resolver::iterator endpoint_iter)
{
if (endpoint_iter != tcp::resolver::iterator()) {
// Set a deadline for the connect operation to complete.
mpSocketTimer->expires_from_now(boost::posix_time::seconds(5));
boost::asio::async_connect(*mpSocket, endpoint_iter,
[this](boost::system::error_code ec, tcp::resolver::iterator) {
if (!mShutdownFlag && !ec) {
// successfully connected here - cancel the
// connect timer and kick off async write ops
mpSocketTimer->cancel();
// kick off the prosim read operation
do_read();
}
});
} else {
// No more endpoints. Close the socket.
shutdown();
}
}
void
VCDUProtocol::do_read()
{
// Start or continue an asynchronous line reads. This will read at least
// up to a carriage return or line feed
async_read_until(*mpSocket, *mTLS->mSocketStreamBuf, "\r\n",
boost::bind(&VCDUProtocol::handle_read, this, _1));
}
This is the asynchronous read completion handler - THIS NEEDS TO BE CANCELLED, I read somewhere that simply closing the socket is insufficient as the completions handlers will not get called. Should I call the cancel;
/**
* Asynchronous read callback.
*
* #param ec [in] Boost ASIO library error code.
*/
void
VCDUProtocol::handle_read(const boost::system::error_code& ec)
{
static auto& gLogger = gpLogger->getLoggerRef(
gUseSysLog ? Logger::LogDest::SysLog :
Logger::LogDest::EventLog);
if (!mShutdownFlag) {
if (!ec) {
// Extract the newline-delimited message from the buffer.
std::string line;
std::istream is(mTLS->mSocketStreamBuf.get());
while (std::getline(is, line)) {
// Critical Section
std::lock_guard<std::mutex> lock (gMutexGuard);
// handle partial line reads
if (is.eof()) {
mTLS->mPartialLine = std::move(line);
continue;
} else if (!mTLS->mPartialLine.empty()) {
line = std::move(mTLS->mPartialLine) + line;
}
. . .
// update GUI
mpListener->handlePageUpdate(
mProtocolConfig.getCduID(),
mTLS->mVCDUPage, bRefreshCDU);
}
// not really required
line.clear();
}
// keep reading
do_read();
} else {
LOG_ERROR(gLogger, gChannel) << boost::format(
"CDU_%1%: handle_read - error[%2%]")
% mProtocolConfig.getCduID()
% ec.message();
shutdown();
}
}
}

restarting tcp boost asio io_service with uncompleted completion handlers
AFAICT that's impossible.
The documentation clearly specifies that reset() must be called before run() can be called again.
I think the only viable alternative is to create your own event loop based on e.g. poll_one() and thereby prevent having to stop the service in the first place.
This is the asynchronous read completion handler - THIS NEEDS TO BE CANCELLED, I read somewhere that simply closing the socket is insufficient as the completions handlers will not get called.
That's not true. Cancelling a socket will cancel the operations in flight and they will cause the completion handlers to be invoked with ec == operation_aborted. Closing the socket will probably cause different error codes like bad_socket.

Related

ZIO scala sleep method not sleeping the thread vs. using directly Thread.sleep

In my existing Scala code I replaced Thread.sleep(10000) with ZIO.sleep(Duration.fromScala(10.seconds)) with the understanding that it won't block thread from the thread pool (performance issue). When program runs it does not wait at this line (whereas of course in first case it does). Do I need to add any extra code for ZIO method to work ?
Adding code section from Play+Scala code:
def sendMultipartEmail = Action.async(parse.multipartFormData) { request =>
.....
//inside this controller below method is called
def retryEmailOnFail(pList: ListBuffer[JsObject], content: String) = {
if (!sendAndGetStatus(pList, content)) {
println("<--- email sending failed - retry once after a delay")
ZIO.sleep(Duration.fromScala(10.seconds))
println("<--- retrying email sending after a delay")
finalStatus = finalStatus && sendAndGetStatus(pList, content)
} else {
finalStatus = finalStatus && true
}
}
.....
}
As you said, ZIO.sleep will only suspend the fiber that is running, not the operating system thread.
If you want to start something after sleeping, you should just chain it after the sleep:
// value 42 will only be computed after waiting for 10s
val io = ZIO.sleep(Duration.fromScala(10.seconds)).map(_ => 42)

.wait() on a task in c++/cx throws exception

I have a function which calls Concurrency::create_task to perform some work in the background. Inside that task, there is a need to call a connectAsync method on the StreamSocket class in order to connect a socket to a device. Once the device is connected, I need to grab some references to things inside the connected socket (like input and output streams).
Since it is an asynchronous method and will return an IAsyncAction, I need to create another task on the connectAsync function that I can wait on. This works without waiting, but complications arise when I try to wait() on this inner task in order to error check.
Concurrency::create_task( Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService::FromIdAsync( device_->Id ) )
.then( [ this ]( Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService ^device_service_ )
{
_device_service = device_service_;
_stream_socket = ref new Windows::Networking::Sockets::StreamSocket();
// Connect the socket
auto inner_task = Concurrency::create_task( _stream_socket->ConnectAsync(
_device_service->ConnectionHostName,
_device_service->ConnectionServiceName,
Windows::Networking::Sockets::SocketProtectionLevel::BluetoothEncryptionAllowNullAuthentication ) )
.then( [ this ]()
{
//grab references to streams, other things.
} ).wait(); //throws exception here, but task executes
Basically, I have figured out that the same thread (presumably the UI) that creates the initial task to connect, also executes that task AND the inner task. Whenever I attempt to call .wait() on the inner task from the outer one, I immediately get an exception. However, the inner task will then finish and connect successfully to the device.
Why are my async chains executing on the UI thread? How can i properly wait on these tasks?
In general you should avoid .wait() and just continue the asynchronous chain. If you need to block for some reason, the only fool-proof mechanism would be to explicitly run your code from a background thread (eg, the WinRT thread pool).
You could try using the .then() overload that takes a task_options and pass concurrency::task_options(concurrency::task_continuation_context::use_arbitrary()), but that doesn't guarantee the continuation will run on another thread; it just says that it's OK if it does so -- see documentation here.
You could set an event and have the main thread wait for it. I have done this with some IO async operations. Here is a basic example of using the thread pool, using an event to wait on the work:
TEST_METHOD(ThreadpoolEventTestCppCx)
{
Microsoft::WRL::Wrappers::Event m_logFileCreatedEvent;
m_logFileCreatedEvent.Attach(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, WRITE_OWNER | EVENT_ALL_ACCESS));
long x = 10000000;
auto workItem = ref new WorkItemHandler(
[&m_logFileCreatedEvent, &x](Windows::Foundation::IAsyncAction^ workItem)
{
while (x--);
SetEvent(m_logFileCreatedEvent.Get());
});
auto asyncAction = ThreadPool::RunAsync(workItem);
WaitForSingleObjectEx(m_logFileCreatedEvent.Get(), INFINITE, FALSE);
long i = x;
}
Here is a similar example except it includes a bit of Windows Runtime async IO:
TEST_METHOD(AsyncOnThreadPoolUsingEvent)
{
std::shared_ptr<Concurrency::event> _completed = std::make_shared<Concurrency::event>();
int i;
auto workItem = ref new WorkItemHandler(
[_completed, &i](Windows::Foundation::IAsyncAction^ workItem)
{
Windows::Storage::StorageFolder^ _picturesLibrary = Windows::Storage::KnownFolders::PicturesLibrary;
Concurrency::task<Windows::Storage::StorageFile^> _getFileObjectTask(_picturesLibrary->GetFileAsync(L"art.bmp"));
auto _task2 = _getFileObjectTask.then([_completed, &i](Windows::Storage::StorageFile^ file)
{
i = 90210;
_completed->set();
});
});
auto asyncAction = ThreadPool::RunAsync(workItem);
_completed->wait();
int j = i;
}
I tried using an event to wait on Windows Runtime Async work, but it blocked. That's why I had to use the threadpool.

Socket ReceiveTimeout on Linux

I am writing a synchronous client. Part of it is a Connection object which is responsible for the actual sending and receiving of the data. The entire library is written using the Boost ASIO ip::tcp::socket class.
I have a test in which the client calls a method on the server (which sleeps for 2 seconds) with a timeout of 1 second. My code has detected that execution took more than the requested time, but it didn't return in time. Instead, it returned after the 2 whole seconds.
I have narrowed down the problem to the receive method:
void Connection::receive(const mutable_buffers_1& buffers, const DurationType& timeout)
{
// to make sure it isn't 0 by mistake
auto actualTimeout = std::max(DurationType(milliseconds(1)), timeout);
SocketReceiveTimeoutOption timeoutOption(actualTimeout);
error_code ec;
_socket.set_option(timeoutOption, ec);
RPC_LOG(TRACE) << "Setting timeout " << actualTimeout << " returned: " << ec.message();
RPC_LOG(TRACE) << "Receiving...";
if (_socket.receive(buffers, MSG_WAITALL, ec) != buffer_size(buffers))
{
throw RpcCommunicationError("Did not receive the expected number of bytes from connection");
}
RPC_LOG(TRACE) << "Received! With error code: " << ec.message();
}
DurationType is just a convenience typedef:
typedef boost::chrono::system_clock ClockType;
typedef ClockType::time_point::duration DurationType;
SocketReceiveTimeoutOption is an option implemented for sockets:
template <int Name>
class SocketTimeoutOption
{
public:
#ifdef BSII_WINDOWS
SocketTimeoutOption(const DurationType& timeout) : _value(static_cast<DWORD>(boost::chrono::duration_cast<boost::chrono::milliseconds>(timeout).count())) {}
#else
SocketTimeoutOption(const DurationType& timeout) : _value(Utils::toTimeval(timeout)) {}
#endif
// Get the level of the socket option.
template <typename Protocol>
int level(const Protocol&) const
{
return SOL_SOCKET;
}
// Get the name of the socket option.
template <typename Protocol>
int name(const Protocol&) const
{
return Name;
}
// Get the address of the timeout data.
template <typename Protocol>
void* data(const Protocol&)
{
return &_value;
}
// Get the address of the timeout data.
template <typename Protocol>
const void* data(const Protocol&) const
{
return &_value;
}
// Get the size of the boolean data.
template <typename Protocol>
std::size_t size(const Protocol&) const
{
return sizeof(_value);
}
private:
#ifdef BSII_WINDOWS
DWORD _value;
#else
timeval _value;
#endif
};
typedef SocketTimeoutOption<SO_RCVTIMEO> SocketReceiveTimeoutOption;
typedef SocketTimeoutOption<SO_SNDTIMEO> SocketSendTimeoutOption;
And finally
namespace Utils
{
inline
timeval toTimeval(const DurationType& duration)
{
timeval val;
auto seconds = boost::chrono::duration_cast<boost::chrono::seconds>(duration); // TODO: make sure this is truncated down in case there's fractional seconds
val.tv_sec = static_cast<long>(seconds.count());
auto micro = boost::chrono::duration_cast<boost::chrono::microseconds>(duration - seconds);
val.tv_usec = static_cast<long>(micro.count());
return val;
}
}
The problem is that even though I specify a 1s timeout, the receive method still takes the entire 2 seconds. Here's the log:
2014-09-14 10:27:53.348383 | trace | 0x007f24e50ae7c0 | Setting timeout 999917107 nanoseconds returned: Success
2014-09-14 10:27:53.348422 | trace | 0x007f24e50ae7c0 | Receiving...
2014-09-14 10:27:55.349152 | trace | 0x007f24e50ae7c0 | Received! With error code: Success
As you can see, setting the timeout worked, but still the receive method took 2 seconds.
The same code works just fine on Windows.
socket::receive() will block until either:
One or more bytes of data has been received successfully
An error occurs that would prevent data from being received
For blocking synchronous operations, if the underlying OS operation returns with a non-critical error, such as one indicating that the operation would block or should be tried again, then Boost.Asio will block in poll() waiting for the file descriptor to become ready. The blocking call to poll() is not affected by the SO_RCVTIMEO socket option. Once the file descriptor is ready, Boost.Asio will reattempt the operation.
Thus, the scenario in the original question occurs as follows:
Time | Client | Server
-----+----------------------------------------+-------------------------------
| socket.connect(...); | acceptor.accept(...);
0.00 | socket.set_option(timeout(second(1))); | sleep(seconds(2));
0.01 | socket.receive(...); |
0.02 | |-- recv(...); |
1.02 | | // timeout, errno = EAGAIN |
1.03 | |-- poll(socket); |
2.00 | | // data available, poll unblocks | socket.write(...);
2.01 | `-- recv(...);// success |
To get the desired timeout behavior, either:
Use the pattern presented in the official Boost.Asio timeout examples.
Invoke the OS call directly. However, be cautious, as other operations may indirectly affect this approach. For example, if an asynchronous operation is initiated on the socket, then the socket will be set to non-blocking. This will cause the recv() function to return immediately on a non-blocking socket regardless of the SO_RCVTIMEO socket option.

In Node.js, how do I create a sha512 hash asynchronously?

var crypto = require('crypto');
var sha = crypto.createHash('sha512').update(String(s));
var result = sha.digest('hex');
That's my current code.
How do I do this async? I'm planning to do the sha512 100,000 times.
Node's crypto module does not provide asynchronous SHA512 hashing at present, and although the createHash() stream interface looks asynchronous it will also execute in the main thread and block the event loop.
There is an issue open for this: https://github.com/nodejs/node/issues/678
In the interim, you can use #ronomon/crypto-async to do SHA512 asynchronously and concurrently in the threadpool without blocking the event loop, for multi-core throughput.
If you can not find any better solutions, this trick may help you:
You can create a standalone SHA-512 generator application, which receives your String "s" as standard input, generates the hash, and writes it out to the standard output.
From within your app, you can exec it via the child_process module, and catch the response with an event handler. There is an other stackoverflow thread, which may come handy about child_process:
Is it possible to execute an external program from within node js?
This way you can encapsulate the sync function into an async context. :)
Node.js runs in a single thread, so if you want to do asynchronous processing, you have to either:
use a module that implements threading natively;
spawn multiple Node.js processes.
The method I present below uses the latter approach.
Node.js API provides a module called cluster that allows you to fork your process as you would do if you were programming in C.
My approach breaks the input data (the strings you want to hash) into chunks, where each chunk is passed to a child worker process. When the worker finishes work on its chunk, it signals the master process, passing the results back.
The master node keeps running while the workers do their job, so it can do any unrelated asynchronous work without being blocked. When all workers finish, the master is signaled and it is free to further process the final results.
To run my test, you can simply do:
node parhash
My tests ran on an Intel Core i5 4670 with 8 GB RAM DDR3.
For your need of 100'000 strings, 1 worker completed in 450 ms, while 10 workers took 350 ms.
In a test with a million strings, 1 worker did the job in 4.5 seconds, while 10 workers did in 3.5 seconds.
Here is the code:
parhash.js
var
crypto = require('crypto'),
cluster = require('cluster');
var
STRING_COUNT = 1000000,
STRINGS_PER_WORKER = 100000,
WORKER_COUNT = Math.ceil(STRING_COUNT / STRINGS_PER_WORKER),
chunks = [],
nextChunkId = 0,
results = [],
startTime,
pendingWorkers = WORKER_COUNT;
/**
* Generates strings partitioned in WORKER_COUNT chunks.
* Each of these chunks will later be passed to a child process to be parsed asynchronously.
*
* You should replace this with your working data.
*/
function generateDemoStringChunks() {
var
si, wi,
chunk;
for (wi = 0; wi < WORKER_COUNT; wi++) {
chunk = [];
for (si = STRINGS_PER_WORKER * wi; (si < STRINGS_PER_WORKER * (wi + 1)) && (si < STRING_COUNT); si++) {
chunk.push(si.toString());
}
chunks.push(chunk);
}
}
/**
* After all workers finish processing, this will be executed.
*
* Here you should do whatever you want to process the resulting hashes.
*/
function mergeResults() {
results.sort(function compare(a, b) {
return a.id - b.id;
});
console.info('Summary:');
results.forEach(function (result) {
console.info('\tChunk %d: %d hashes (here is the first hash: "%s")', result.id, result.data.length, result.data[0]);
});
}
/**
* This will be called on the master side every time a worker finishes working.
*
* #param {object} worker the Worker that finished
* #param {{id: number, data: [string]}} result the result
*/
function processWorkerResult(worker, result) {
console.info('Worker %d finished computing %d hashes.', worker.id, result.data.length);
results.push(result);
worker.kill();
if (--pendingWorkers == 0) {
console.info('Work is done. Whole process took %d seconds.', process.hrtime(startTime)[0]);
mergeResults();
}
}
/**
* Gets a chunk of data available for processing.
*
* #returns {{id: number, data: [string]}} the chunk to be passed to the worker
*/
function getNextAvailableChunk() {
var chunk = {
id: nextChunkId,
data: chunks[nextChunkId]
};
nextChunkId++;
return chunk;
}
/**
* The master node will send a chunk of data every time a worker node
* signals it's ready to work.
*/
function waitForWorkers() {
cluster.on('online', function (worker) {
console.info('Worker %d is online.', worker.id);
worker.on('message', processWorkerResult.bind(null, worker));
worker.send(getNextAvailableChunk());
});
}
/**
* Start workers.
*/
function spawnWorkers() {
var wi;
for (wi = 0; wi < WORKER_COUNT; wi++) {
cluster.fork();
}
}
/**
* The hash function.
*
* #param {string} s a string to be hashed
* #returns {string} the hash string
*/
function hashString(s) {
return crypto.createHash('sha512').update(s).digest('hex');
}
/**
* A worker will wait for the master to send a chunk of data and will
* start processing as soon as it arrives.
*/
function processChunk() {
cluster.worker.on('message', function(chunk) {
var
result = [];
console.info('Worker %d received chunk %d with a load of %d strings.', cluster.worker.id, chunk.id, chunk.data.length);
chunk.data.forEach(function processChunk(s) {
result.push(hashString(s));
});
cluster.worker.send({
id: chunk.id,
data: result
});
});
}
function main() {
if (cluster.isMaster) {
/*
The master node will instantiate all required workers
and then pass a chunk of data for each one.
It will then wait for all of them to finish so it can
merge the results.
*/
startTime = process.hrtime();
generateDemoStringChunks();
spawnWorkers();
waitForWorkers();
} else {
/*
A worker node will wait for a chunk to arrive and
then will start processing it. When finished, it will
send a message back to the master node with the
resulting hashes.
*/
console.info('Worker %d is starting.', cluster.worker.id);
processChunk();
}
}
main();
I can't tell how well it would perform if it were implemented using threads because I haven't tested it. You could try WebWorker Threads if you want to do a benchmark (note: I haven't tried the WebWorkers module yet and I don't guarantee it works - you are on your own here).

There's no sleep()/wait for mutex in node.js, so how to deal with large IO tasks?

I have a large array of filenames I need to check, but I also need to respond to network clients. The easiest way is to perform:
for(var i=0;i < array.length;i++) {
fs.readFile(array[i], function(err, data) {...});
}
, but array can be of any length, say 100000, so it's not a good idea to perform 100000 reads at once, on the other hand doing fs.readFileSync() can take too long. Also launching next fs.readFile() in callback, like this:
var Idx = 0;
function checkFile() {
fs.readFile(array[Idx], function (err, data) {
Idx++;
if (Idx < array.length) {
checkFile();
} else {
Idx = 0;
setTimeout(checkFile, 10000); // start checking files in one second
}
});
}
is also not a best option, because array[] gets constantly updated by network clients - some items deleted, new added and so on.
What is the best way to accomplish such a task in node.js?
You should stick to your first solution (fs.readFile). For file I/O, node.js uses a thread pool. The reason is that most unix kernels don't provide efficient asynchronous APIs for the file system. Even if you start 10,000 reads concurrently, only a few reads will actually run and the rest will wait in a queue.
In order to make this answer more interesting, I browsed through node's code again to make sure that things hadn't changed.
Long story short, file I/O uses blocking system calls and is made by a thread pool with at most 4 concurrent threads.
The important code is in libeio, which is abstracted by libuv. All I/O code is wrapped by macros which queue requests. For example:
eio_req *eio_read (int fd, void *buf, size_t length, off_t offset, int pri, eio_cb cb, void *data, eio_channel *channel)
{
REQ (EIO_READ); req->int1 = fd; req->offs = offset; req->size = length; req->ptr2 = buf; SEND;
}
REQ prepares the request and SEND queues it. We eventually end up in etp_maybe_start_thread:
static unsigned int started, idle, wanted = 4;
(...)
static void
etp_maybe_start_thread (void)
{
if (ecb_expect_true (etp_nthreads () >= wanted))
return;
(...)
The queue keeps 4 threads running to process the requests. When our read request is finally executed, eio simply use the block read from unistd.h:
case EIO_READ: ALLOC (req->size);
req->result = req->offs >= 0
? pread (req->int1, req->ptr2, req->size, req->offs)
: read (req->int1, req->ptr2, req->size); break;

Resources