[asio] Pass some parameter to io_service::run

Hello All, I'm new in boost asio. I'm trying to adapt this library to my existent network application. There is a problem with current application's architecture. I see no way to pass some parameter to the completion routine from the io_service::run calling thread. Below brief application description. Hope this will help: 1. It is *low-latency* network application 2. Application processes incoming multicast UDP messages. 3. Number of multicast groups/ports is *unlimited*. Technical details: 1. Application encapsulates udp::sockets subscribed to multicast group 2. udp::socket::async_receive method used to receive data 3. io_service::run is used in the thread pool to call socket's complete routine. 4. Every thread of the pool encapsulates some object for messages processing. I need to pass reference to this object to the socket::async_receive complete routine to complete message processing. This object allocates a lot of memory and I cannot create it for every socket. It is possible to create but there is no sense. It is very important to avoid thread_local_storages and null_buffers to make application as simple as possible without performance gaps. I believe it is possible because POSIX and Windows I/O allow it. Please help with advise. Thanks. Dima

Hi,
I need to pass reference to this object to the socket::async_receive complete routine to complete message processing. This object allocates a lot of memory and I cannot create it for every socket. It is possible to create but there is no sense.
You can pass them using boost::bind: socket_.async_read_some(boost::asio::buffer(data_, max_length), boost::bind(&session::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, boost::ref(largeObject))); Where: void session::handle_read(const boost::system::error_code& error, size_t bytes_transferred, LargeObject & largeObject) { ...... } Best Regards, Sergei

Hello Sergey, Thank you for your participation. In this case I should create LargeObject for every socket because I don't know what thread of the pool will process incoming message. And I cannot pass the same largeObject to two or more recv operations. Looks like I should use null_buffers. What is overhead of this way? Should I go to the kernel twise (1st - socket::async_recv, 2nd - socket::recv)? The best way for me is, if asio will repeat the Windows Async I/O interface and let me call completion routine directly. 2010/1/10 Sergei Politov <spolitov@gmail.com>
Hi,
I need to pass reference to this object to the socket::async_receive complete routine to complete message processing. This object allocates a lot of memory and I cannot create it for every socket. It is possible to
create
but there is no sense.
You can pass them using boost::bind: socket_.async_read_some(boost::asio::buffer(data_, max_length), boost::bind(&session::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, boost::ref(largeObject)));
Where: void session::handle_read(const boost::system::error_code& error, size_t bytes_transferred, LargeObject & largeObject) { ...... }
Best Regards, Sergei _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Dmytro Ovdiienko wrote:
I need to pass reference to this object to the socket::async_receive complete routine to complete message processing. This object allocates a lot of memory and I cannot create it for every socket. It is possible to create but there is no sense.
In this case I should create LargeObject for every socket because I don't know what thread of the pool will process incoming message. And I cannot pass the same largeObject to two or more recv operations.
Looks like I should use null_buffers. What is overhead of this way? Should I go to the kernel twise (1st - socket::async_recv, 2nd - socket::recv)?
I'm confused by the first two statements. Is the large object 'large' because it is the receive buffer, or does it contain temporary application state? I guess the question is whether the object is owned by: - the IO operation while it is in progress - the socket - the completion handler thread while it handles the completion If it contains the buffer and you don't want to pre-allocate lots of them (ie when you start the IO) then the null buffer optimisation may help, but I'm surprised you would need to have enough UDP end points that this is an issue - surely one uses UDP to enable multiplexing lots of client systems to each endpoint? You probably don't need bufers larger than a jumbo fram anyway do you? James

Hello James, Thanks for your responce. The large object is message_parser. It is 'large' because it encapsulates 'parsing rules' and 'message protocol'. Also it is stateful. So, on your question it is 'temporary application state'. On ownership. Right now message_parser is owned by worker_thread. The incoming message processing sequence is following: 1. Reactor calls ::select function on udp::sockets 2. ::select notifies about incoming data 3. Reactor enqueues socket to the queue 4. worker_thread dequeues socket from the queue 5. worker_thread calls ::recv on dequeued socket 6. ::recv stores incoming data to the buffer 7. worker_thread passes buffer to the message_parser::parse 8. message_parser returns incoming_message 9. worker_thread processes incoming_message So the light-version of the structure is: Application := udp::socket*, worker_thread*, Reactor worker_thread := incoming_buffer, message_parser If number of sockets = 60 and worker_count = 4, I have only 4 incoming_buffers and 4 message_parsers. With Boost Asio I will have 60 incomming buffers. Though it is not a problem. Buffer size is 64k. The problem is message_parser. It uses ~5MB ...maybe more. I expect to have structure something like: Application := udp_socket*, worker_thread*, Reactor worker_thread := message_parser udp_socket := udp::socket, incoming_buffer But I don't know how to pass message_parser to the socket's completion handler. 2010/1/10 James Mansion <james@mansionfamily.plus.com>
Dmytro Ovdiienko wrote:
I need to pass reference to this object to the socket::async_receive complete routine to complete message processing. This object allocates a lot of memory and I cannot create it for every socket. It is possible to create but there is no sense. In this case I should create LargeObject for every socket because I don't know what thread of the pool will process incoming message. And I cannot pass the same largeObject to two or more recv operations.
Looks like I should use null_buffers. What is overhead of this way? Should I go to the kernel twise (1st - socket::async_recv, 2nd - socket::recv)?
I'm confused by the first two statements.
Is the large object 'large' because it is the receive buffer, or does it contain temporary application state?
I guess the question is whether the object is owned by: - the IO operation while it is in progress - the socket - the completion handler thread while it handles the completion
If it contains the buffer and you don't want to pre-allocate lots of them (ie when you start the IO) then the null buffer optimisation may help, but I'm surprised you would need to have enough UDP end points that this is an issue - surely one uses UDP to enable multiplexing lots of client systems to each endpoint? You probably don't need bufers larger than a jumbo fram anyway do you?
James
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Dmytro Ovdiienko wrote:
If number of sockets = 60 and worker_count = 4, I have only 4 incoming_buffers and 4 message_parsers.
With Boost Asio I will have 60 incomming buffers. Though it is not a problem. Buffer size is 64k. The problem is message_parser. It uses ~5MB ...maybe more.
If you can currently get away with a parser bound to each worker, then why does that need to change when using asio? I don't understand why you would have an objection to a permanent binding with TLS or a temporary binding using a pool of parsers, since it seems that each datagram must be parsed entirely and then the parser reset for the next? You said the parsers are stateful but I don't see how that can be without a great deal of pain with UDP as a result of packet loss or reordering. Those receiver buffers are large for UDP. Are you expecting packets to survive fragmentation reliably? James

Hello James, Please find my comments below. 2010/1/10 James Mansion <james@mansionfamily.plus.com>
Dmytro Ovdiienko wrote:
If number of sockets = 60 and worker_count = 4, I have only 4 incoming_buffers and 4 message_parsers.
With Boost Asio I will have 60 incomming buffers. Though it is not a problem. Buffer size is 64k. The problem is message_parser. It uses ~5MB ...maybe more.
If you can currently get away with a parser bound to each worker, then why does that need to change when using asio?
This solution with parser bound to each worker works only with my home made network library. I cannot repeat this with boost asio without TLS or parser_pool. But I need boost::asio because it uses Completion Ports and I don't want to repeat the same in my network library. I have made some tests : - synchronous send/receive operations on localhost using my library takes 20us - synchronous send/receive operations on localhost using boost::asio takes 29us - asynchronous send/receive operations on localhost using my library takes 64us - asynchronous send/receive operations on localhost using boost::asio takes 24us Looks like Completion Ports works better than select + queueing/dequeueing + recv in user mode (not kernel). That is why I decided to move to asio. I don't understand why you would have an objection to a permanent binding
with TLS
TLS looks like global variables. Global variables violate encapsulation. I suppose to support my program at least 2 years. I don't want to hate myself two years :)
or a temporary binding using a pool of parsers, since it seems that each datagram must be parsed entirely and then the parser reset for the next?
Exactly. But I don't want to create pool of the parsers. Access to the pool should be synchronized. Synchronization eats CPU times.
You said the parsers are stateful but I don't see how that can be without a great deal of pain with UDP as a result of packet loss or reordering.
State of the parser is reinitialized before every new message parsing. So every new message is parsed from initial state.
Those receiver buffers are large for UDP. Are you expecting packets to survive fragmentation reliably?
I read in the specification maximim UDP message size is 65k. I don't know how it is possible but I did not think about this until now.
James

Dmytro Ovdiienko wrote:
I don't understand why you would have an objection to a permanent binding with TLS
TLS looks like global variables. Global variables violate encapsulation. I suppose to support my program at least 2 years. I don't want to hate myself two years :)
I think that's quite a silly reason. So are singletons, but they are both useful. ...
or a temporary binding using a pool of parsers, since it seems that each datagram must be parsed entirely and then the parser reset for the next?
Exactly. But I don't want to create pool of the parsers. Access to the pool should be synchronized. Synchronization eats CPU times.
... but not nearly as silly as this one. Getting an item from a pool is a great candidate for a spinlock and if you're concerned you can easily segment the pool. Its a non-issue. I defy you to measure it in the context of all teh context switching and system calls you're doing. Of course, the pool is a singleton. And that's like a global. The right tools to get you what you want are right there - you're just choosing not to use them for academic reasons. James
participants (3)
-
Dmytro Ovdiienko
-
James Mansion
-
Sergei Politov