[asio] Tutorial 7 using strdup, new

Why is http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/tutorial... using raw pointers, strdup, new, free and delete? This is soo 1996. Tutorials shoud teach. -- Peter Dimov

Peter Dimov wrote:
Why is
http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/tutorial...
using raw pointers, strdup, new, free and delete? This is soo 1996. Tutorials shoud teach.
FWIW, I've also seen Boost documentation that uses void main(), e.g. http://boost.org/libs/serialization/doc/rationale.html Regards, m Send instant messages to your online friends http://au.messenger.yahoo.com

Hi Peter, --- Peter Dimov <pdimov@mmltd.net> wrote:
Why is
http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/tutorial...
using raw pointers, strdup, new, free and delete? This is soo 1996. Tutorials shoud teach.
Yep, this already came up in a review and I'm planning to change it. IIRC, the motivation for doing it this way was: - to avoid introducing too many boost facilities at once - to illustrate the "exactly-once" callback invocation Since I want to keep the tutorial as simple as possible, I'm a bit undecided about what to put instead though. I want to ensure that whatever I use instead doesn't distract from the things that I am trying to teach. For example, one option I'm considering is to simply replace them with shared_ptr<socket>, shared_ptr<std::string> etc, but keep using free functions as callbacks. The "distraction" I see with std::string is that I probably need to explain how calling asio::buffer() on it uses the data() member function, and the conditions under which the pointer returned by data() is invalidated. Any other ideas would be appreciated. Explaining features in isolation is not easy. Cheers, Chris

Christopher Kohlhoff wrote:
Hi Peter,
--- Peter Dimov <pdimov@mmltd.net> wrote:
Why is
http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/tutorial...
using raw pointers, strdup, new, free and delete? This is soo 1996. Tutorials shoud teach.
Yep, this already came up in a review and I'm planning to change it. IIRC, the motivation for doing it this way was:
- to avoid introducing too many boost facilities at once
- to illustrate the "exactly-once" callback invocation
Since I want to keep the tutorial as simple as possible, I'm a bit undecided about what to put instead though. I want to ensure that whatever I use instead doesn't distract from the things that I am trying to teach.
But the C-style facilities used in the tutorial actually do serve as a distraction to me, not just a mere annoyance. I am now forced to think of the following: - what happens if something along the way throws? - does the author of the tutorial really endorse this programming style, and do I want to use a library designed with this use style in mind? - if the tutorial doesn't show the recommended way to use asio, what _is_ the recommended way to use it?
For example, one option I'm considering is to simply replace them with shared_ptr<socket>, shared_ptr<std::string> etc, but keep using free functions as callbacks. The "distraction" I see with std::string is that I probably need to explain how calling asio::buffer() on it uses the data() member function, and the conditions under which the pointer returned by data() is invalidated.
shared_ptr<socket> is the easy part, although this immediately leads me to ask the obvious question: why isn't a socket already a shared_ptr<socket_impl>? For the string, my first thought was to just pass a std::string by value. This of course doesn't work because the string needs to stay alive until the operation is complete, so we need something like a shared_array<char>. I think that the library should provide one. In summary, I think that eliminating the C-style facilities from the tutorial will improve both the tutorial and the library.

Peter Dimov wrote:
[...] shared_ptr<socket> is the easy part, although this immediately leads me to ask the obvious question: why isn't a socket already a shared_ptr<socket_impl>?
While Christopher can certainly answer better than i can, i think that the answer is: because it often doesn't need to be! A socket is a non copyable value based object. It doesn't need dynamic allocation and could very well be stack based (as the datagram_socket and the socket_acceptor in the example). Only if you need the lifetime of the object to be dynamic (as the example does, but it is *not* always the case) you directly allocate it. Or make it be a member of a class that is itself dynamically allocated. You could make the socket class hold internally a shared_ptr to a socket_impl and make it (shallow) copyable, but it would it be surprising and would require dynamic allocation(*) even if not needed. A nice solution would be to make socket movable... (*) Not really, all copies of stream could hold a copy of socket_imp and only the last one would close it, but it would still require some sort of reference counting. -- Giovanni P. Deretta

On 2/6/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote: [snip]
While Christopher can certainly answer better than i can, i think that the answer is: because it often doesn't need to be! A socket is a non copyable value based object. It doesn't need dynamic allocation and could very well be stack based (as the datagram_socket and the socket_acceptor in the example).
And it doesnt have to, I use shared_ptr to handle my sockets while not using dynamic allocation. The ability to configure a deleter makes it very easy to handle resources without using operator new and nor delete. And IMO it would be much easier than prohibing copying of the socket.
Only if you need the lifetime of the object to be dynamic (as the example does, but it is *not* always the case) you directly allocate it. Or make it be a member of a class that is itself dynamically allocated.
Even making it a member of the class you'll probably need it to be passed along to another class, which is impossible without copy-construction. Which would need the socket (or the class that owns the socket) to be dynamically allocated and its lifetime to be watched (maybe through shared_ptr).
You could make the socket class hold internally a shared_ptr to a socket_impl and make it (shallow) copyable, but it would it be surprising and would require dynamic allocation(*) even if not needed.
Yes, probably always since you cant copy or move the socket...
A nice solution would be to make socket movable...
IMO, it wouldnt be a complete solution, but part of it.
(*) Not really, all copies of stream could hold a copy of socket_imp and only the last one would close it, but it would still require some sort of reference counting.
shared_ptr can be used, since it is already done and optimized to a lot of platforms. Why not use it inside the library?
-- Giovanni P. Deretta _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Felipe Magno de Almeida

Felipe Magno de Almeida wrote:
On 2/6/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote:
While Christopher can certainly answer better than i can, i think that the answer is: because it often doesn't need to be! A socket is a non copyable value based object. It doesn't need dynamic allocation and could very well be stack based (as the datagram_socket and the socket_acceptor in the example).
And it doesnt have to, I use shared_ptr to handle my sockets while not using dynamic allocation. The ability to configure a deleter makes it very easy to handle resources without using operator new and nor delete. And IMO it would be much easier than prohibing copying of the socket.
The problem is that a socket is *conceptually* not copyable. Even if you have multiple handles to it, they all refer to the same underlying stream. Standard streams are not copyable for the same reason. If you want different objects to *share* a socket you use a shared_ptr and dynamically allocate it.
Only if you need the lifetime of the object to be dynamic (as the example does, but it is *not* always the case) you directly allocate it. Or make it be a member of a class that is itself dynamically allocated.
Even making it a member of the class you'll probably need it to be passed along to another class, which is impossible without copy-construction. Which would need the socket (or the class that owns the socket) to be dynamically allocated and its lifetime to be watched (maybe through shared_ptr).
well, you could hold a shared_ptr (with a null deleter) to the socket or class that owns it and have its lifetime be watched by weak_pointers. This way no dynamic allocation is needed. If you want the lifetime to be dynamic, that is "as long as some one has a reference to it", use dynamic allocation an shared_ptr.
You could make the socket class hold internally a shared_ptr to a socket_impl and make it (shallow) copyable, but it would it be surprising and would require dynamic allocation(*) even if not needed.
Yes, probably always since you cant copy or move the socket...
Yes what? Always what? Are you talking about always needing dynamic allocation? Definitely not. I've written code that uses asio stream_sockets without using dynamic allocation nor moving nor sharing ownership.
A nice solution would be to make socket movable...
IMO, it wouldnt be a complete solution, but part of it.
Why not? If you want shared ownership you dynamically allocate, there is no way around it. If you want to give up ownership (to move the object a long a chain of functions implementing a state machine for example), you move it.
(*) Not really, all copies of stream could hold a copy of socket_imp and only the last one would close it, but it would still require some sort of reference counting.
shared_ptr can be used, since it is already done and optimized to a lot of platforms. Why not use it inside the library?
Well, I'm sure asio uses it in lot's of places :) ... if you mean asio should use it to implement reference counting of the underlying socket_impl, yes, it could do it *if* you decide to make the socket copyable (and I think it the wrong choice). BTW, I might have misunderstood some (or all) of your reply and be arguing about the wrong thing. Sorry if it is so :) -- Giovanni P. Deretta

On 2/6/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote:
The problem is that a socket is *conceptually* not copyable. Even if you have multiple handles to it, they all refer to the same underlying stream. Standard streams are not copyable for the same reason. If you want different objects to *share* a socket you use a shared_ptr and dynamically allocate it.
IMHO it is not true that a socket is *conceptually* not copyable. In every platform I use copying sockets are a very intuitive operation. Couldnt a socket be *conceptually* a reference counted resource? What, IMO, a socket is *not* is a resource that uses dynamic allocation. There's no point in using new operator to allocate memory for a socket!
Only if you need the lifetime of the object to be dynamic (as the example does, but it is *not* always the case) you directly allocate it. Or make it be a member of a class that is itself dynamically allocated.
Even making it a member of the class you'll probably need it to be passed along to another class, which is impossible without copy-construction. Which would need the socket (or the class that owns the socket) to be dynamically allocated and its lifetime to be watched (maybe through shared_ptr).
well, you could hold a shared_ptr (with a null deleter) to the socket or class that owns it and have its lifetime be watched by weak_pointers. This way no dynamic allocation is needed. If you want the lifetime to be dynamic, that is "as long as some one has a reference to it", use dynamic allocation an shared_ptr.
My point is that it shouldnt be needed to use dynamic allocation to use a socket. Even if you need to share it. [snip]
Yes what? Always what? Are you talking about always needing dynamic allocation? Definitely not. I've written code that uses asio stream_sockets without using dynamic allocation nor moving nor sharing ownership.
If you could show me maybe it could explain a lot of what you mean. Can you show me any code and what is its use case?
Why not? If you want shared ownership you dynamically allocate, there is no way around it. If you want to give up ownership (to move the object a long a chain of functions implementing a state machine for example), you move it.
I cant see why shared resource ownership must imply dynamic memory allocation, IMO it only makes sense to use dynamic memory allocation when the shared resource is memory. But not when it is a socket, or file or anything else. Using dynamic memory allocation when it is not really conceptually needed is a cost that, IMHO, doesnt make sense.
Well, I'm sure asio uses it in lot's of places :) ... if you mean asio should use it to implement reference counting of the underlying socket_impl, yes, it could do it *if* you decide to make the socket copyable (and I think it the wrong choice).
IMO, should be both.
BTW, I might have misunderstood some (or all) of your reply and be arguing about the wrong thing. Sorry if it is so :)
I think you got it right. But I'm still not getting how you solve most of the use cases for asio without relying on dynamic memory allocation or why you believe it should be needed dynamic memory allocation on these cases.
-- Giovanni P. Deretta
thanks, -- Felipe Magno de Almeida

Felipe Magno de Almeida wrote:
IMHO it is not true that a socket is *conceptually* not copyable. In every platform I use copying sockets are a very intuitive operation. Couldnt a socket be *conceptually* a reference counted resource? What, IMO, a socket is *not* is a resource that uses dynamic allocation. There's no point in using new operator to allocate memory for a socket!
Well, what you have is actually an *handle* to a dynamically allocated socket resource: an int on posix system (where the resource is usually in kernel space) and a SOCKET on win32 that if I'm not mistaken is actually a typedefed pointer to a dynamically allocated userspace resource. So, yes, the handle can be copied in the same way you can copy a *pointer* to a asio::stream_socket. The fact that boost::stream_socket is itself implemented using a non aliased handle to a socket is an implementation and transport specific detail. Conceptually a stream_socket behaves as a reference to the underlying stream. This means that if you want to share a asio::stream_socket you usually have do dynamically allocate it. And btw system sockets cannot be copied. For example, you can open a file two times or you can dup() a socket, but the new handle refers always to the underlying resource.
My point is that it shouldnt be needed to use dynamic allocation to use a socket. Even if you need to share it.
[snip]
Yes what? Always what? Are you talking about always needing dynamic allocation? Definitely not. I've written code that uses asio stream_sockets without using dynamic allocation nor moving nor sharing ownership.
If you could show me maybe it could explain a lot of what you mean. Can you show me any code and what is its use case?
Well, my code is not actually the expected way to use asio. I'm using a coroutine library tightly coupled with asio. asio_sockets are allocated on the stack and are and callbacks simply resume the coroutine that queued a call to an asio asynch function. You can find some experimental code in the boost vault under concurrency. Beware, it is still experimental, and I've found out that the package that I've uploaded does not compile. Unfortunately i won't be able to work on it until the next week. But you can check the examples to get a feeling of what I've in mind.
But I'm still not getting how you solve most of the use cases for asio without relying on dynamic memory allocation or why you believe it should be needed dynamic memory allocation on these cases.
Not considering my coroutine example (very exotic and not yet complete), I think that often you do not need to really share an asio::stream_socket. That means that you usually have only one object that owns it. Other objects that might have a reference do not dictate its lifetime. This means that you can use weak pointers (and, as you have already stated, shared_ptr and smart_ptr do not imply dynamic allocation). You could argue that as this is a useful behavior stream_socket should have pointer semantics by default and be reference counted. But you would force the space and time overhead of a shared_ptr to everybody (btw, shared_ptr still require dynamic allocation of the reference counter). Most of asio examples use a dynamically allocate stream_socket that is passed from callback to callback until the last one closes it explicitly. A shared_ptr in this case works well, but is less than ideal (you really have only one reference to the object at any time). An move pointer would be better, but it still require dynamic allocation. If the asio_stream itself was movable it would be perfect. This would require asio to be *move* around callbacks instead of copying them (and probably would require support from boost::bind and boost::function as these are often used with asio). Disclaimer: I do not really have enough experience to say that asio can really solve all use case easily. You are certainly more experienced than I am. In the network library i was working on I've used exactly the same approach you have described (the stream object holds a shared_ptr to the stream_impl), but later I did realize that a non copyable socket would have been better. -- Giovanni P. Deretta

Giovanni P. Deretta wrote:
You could argue that as this is a useful behavior stream_socket should have pointer semantics by default and be reference counted. But you would force the space and time overhead of a shared_ptr to everybody (btw, shared_ptr still require dynamic allocation of the reference counter).
If users would always only hold references to the real resource, how would they declare interest in a 'ready-for-read' event, and which of the references would receive it ? Regards, Stefan

Stefan Seefeld wrote:
Giovanni P. Deretta wrote:
You could argue that as this is a useful behavior stream_socket should have pointer semantics by default and be reference counted. But you would force the space and time overhead of a shared_ptr to everybody (btw, shared_ptr still require dynamic allocation of the reference counter).
If users would always only hold references to the real resource, how would they declare interest in a 'ready-for-read' event, and which of the references would receive it ?
I do not understand exactly what you are asking? Could you eleaborate? -- Giovanni P. Deretta

Giovanni P. Deretta wrote:
Stefan Seefeld wrote:
Giovanni P. Deretta wrote:
You could argue that as this is a useful behavior stream_socket should have pointer semantics by default and be reference counted. But you would force the space and time overhead of a shared_ptr to everybody (btw, shared_ptr still require dynamic allocation of the reference counter).
If users would always only hold references to the real resource, how would they declare interest in a 'ready-for-read' event, and which of the references would receive it ?
I do not understand exactly what you are asking? Could you eleaborate?
As far as I understand the above suggestion, sockets (streams) having reference semantics (or be copyable) implies that multiple objects exist that refer to the same underlaying 'device'. If the system has data ready to be read for that device, which reference should it dispatch them to ? Thanks, Stefan

Stefan Seefeld wrote:
Giovanni P. Deretta wrote:
Stefan Seefeld wrote:
Giovanni P. Deretta wrote:
You could argue that as this is a useful behavior stream_socket should have pointer semantics by default and be reference counted. But you would force the space and time overhead of a shared_ptr to everybody (btw, shared_ptr still require dynamic allocation of the reference counter).
If users would always only hold references to the real resource, how would they declare interest in a 'ready-for-read' event, and which of the references would receive it ?
I do not understand exactly what you are asking? Could you eleaborate?
As far as I understand the above suggestion, sockets (streams) having reference semantics (or be copyable) implies that multiple objects exist that refer to the same underlaying 'device'.
If the system has data ready to be read for that device, which reference should it dispatch them to ?
Well the system does not dispatch readiness messages to handles, but you register an handle with a callback to receive notification. If you register multiple handles, all of them get the notification. Anyway, even if you have multiple copies of an handle, you should'n use more than one at once expecially from multiple threads and expecially stream handles (actually using the same datagram socket or accept socket from multiple threads should be fine) BTW, I was not the one arguing in favor of copyable sockets. -- Giovanni P. Deretta

Giovanni P. Deretta wrote:
If the system has data ready to be read for that device, which reference should it dispatch them to ?
Well the system does not dispatch readiness messages to handles, but you register an handle with a callback to receive notification. If you register multiple handles, all of them get the notification.
That doesn't make much sense to me. Assuming an underlaying 'select()', I register file descriptors, not handlers, with a callback. And even if that callback would itself call multiple functions, how much sense does it make to let them all read from the device ? They would all need to coordinate so they see all the data they need.
Anyway, even if you have multiple copies of an handle, you should'n use more than one at once expecially from multiple threads and expecially stream handles (actually using the same datagram socket or accept socket from multiple threads should be fine)
Exactly. My point was really that I think making streams non-copyable would prevent such design errors, or at least, would make them harder to do. :-) Regards, Stefan

Stefan Seefeld wrote:
Giovanni P. Deretta wrote:
If the system has data ready to be read for that device, which reference should it dispatch them to ?
Well the system does not dispatch readiness messages to handles, but you register an handle with a callback to receive notification. If you register multiple handles, all of them get the notification.
That doesn't make much sense to me. Assuming an underlaying 'select()', I register file descriptors, not handlers, with a callback.
I said handle, not handler. An fd is an handle. In select you do not register callback but you get the list of reay fd and call the callbacks your self. But if you use posix aio or O_ASYNC(linux only), you may a posix realtime signal that will call your registered callback.
And even if that callback would itself call multiple functions, how much sense does it make to let them all read from the device ? They would all need to coordinate so they see all the data they need.
Well you do not need to coordinate to read/write from datagram socket. Only one will get the incoming datagram and all datagrams will be sent.
Anyway, even if you have multiple copies of an handle, you should'n use more than one at once expecially from multiple threads and expecially stream handles (actually using the same datagram socket or accept socket from multiple threads should be fine)
Exactly. My point was really that I think making streams non-copyable would prevent such design errors, or at least, would make them harder to do. :-)
And I completely agree with you. But I also understand that there are use case where having two references to the same device is useful (even if you use only one for I/O). May be the second reference is used for setting options, or just watch state. May be copying is used as a simple alternative to moving (copy a to b, destroy a and use b from now on). -- Giovanni P. Deretta

On 2/7/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote: [snip]
You could argue that as this is a useful behavior stream_socket should have pointer semantics by default and be reference counted. But you would force the space and time overhead of a shared_ptr to everybody (btw, shared_ptr still require dynamic allocation of the reference counter).
Yes and No. I'm arguing that it is a very useful behavior that should be supported by the library. The default I'm not quite sure, maybe yes (it is the safest choice, you wont be able to use the dangling socket without knowing it), maybe no (unnecessary overhead for the reference count). Or you believe that it shouldnt be supported by asio? Be in another library? A move_resource<>/shared_resource<> "smart pointer"? [snip]
-- Giovanni P. Deretta
best regards, -- Felipe Magno de Almeida

Felipe Magno de Almeida wrote:
On 2/7/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote:
[snip]
You could argue that as this is a useful behavior stream_socket should have pointer semantics by default and be reference counted. But you would force the space and time overhead of a shared_ptr to everybody (btw, shared_ptr still require dynamic allocation of the reference counter).
Yes and No. I'm arguing that it is a very useful behavior that should be supported by the library. The default I'm not quite sure, maybe yes (it is the safest choice, you wont be able to use the dangling socket without knowing it), maybe no (unnecessary overhead for the reference count). Or you believe that it shouldnt be supported by asio? Be in another library? A move_resource<>/shared_resource<> "smart pointer"?
Now we agree :) Yes, it is useful behaviour and probably should be supported directly by asio. But it should not be the default or at least the user should have the option. Along side the current non-copyable (but maybe movable) stream_socket asio could provide a stream_socket_ptr that would behave as if it were a shared_ptr to a stream_socket (that is, pointer semantics), but it wouldn't actually allocate a stream_socket (similar to the way an optional looks like a pointer but actualy is stack based). Would it work for you? I'haven't really thought much about it, so I'm certanly missing something. -- Giovanni P. Deretta

On 2/7/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote: [snip]
Now we agree :) Yes, it is useful behaviour and probably should be supported directly by asio. But it should not be the default or at least the user should have the option.
Perfect!
Along side the current non-copyable (but maybe movable) stream_socket asio could provide a stream_socket_ptr that would behave as if it were a shared_ptr to a stream_socket (that is, pointer semantics), but it wouldn't actually allocate a stream_socket (similar to the way an optional looks like a pointer but actualy is stack based).
It is nice, but it wouldnt need to behave like a pointer, syntactically speaking. It should have the same interface as the proper stream_socket, IMO. Only the copying being different.
Would it work for you? I'haven't really thought much about it, so I'm certanly missing something.
I think it would.
-- Giovanni P. Deretta
best regards, -- Felipe Magno de Almeida

Felipe Magno de Almeida wrote:
On 2/7/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote:
[snip]
Now we agree :) Yes, it is useful behaviour and probably should be supported directly by asio. But it should not be the default or at least the user should have the option.
Perfect!
Along side the current non-copyable (but maybe movable) stream_socket asio could provide a stream_socket_ptr that would behave as if it were a shared_ptr to a stream_socket (that is, pointer semantics), but it wouldn't actually allocate a stream_socket (similar to the way an optional looks like a pointer but actualy is stack based).
It is nice, but it wouldnt need to behave like a pointer, syntactically speaking. It should have the same interface as the proper stream_socket, IMO. Only the copying being different.
Well, it behaves like a (smart) pointer (the last to exit closes the door) so it should look like a (smart) pointer. Why do you think it should have the same interface of a stream_socket? What advantages would give you? (Btw, technically it would have the same interface, but you would call members functions using -> instead of . ) -- Giovanni P. Deretta

--- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote:
Felipe Magno de Almeida wrote:
On 2/7/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote:
Along side the current non-copyable (but maybe movable) stream_socket asio could provide a stream_socket_ptr that would behave as if it were a shared_ptr to a stream_socket (that is, pointer semantics), but it wouldn't actually allocate a stream_socket (similar to the way an optional looks like a pointer but actualy is stack based).
It is nice, but it wouldnt need to behave like a pointer, syntactically speaking. It should have the same interface as the proper stream_socket, IMO. Only the copying being different.
Well, it behaves like a (smart) pointer (the last to exit closes the door) so it should look like a (smart) pointer. Why do you think it should have the same interface of a stream_socket? What advantages would give you?
(Btw, technically it would have the same interface, but you would call members functions using -> instead of . )
This idea has a couple of issues that I can see: - It does not allow an implementation to store additional state inside the socket object directly, so forcing a dynamic allocation on the implementation. - It does not address stream layering, as in: asio::ssl::stream<asio::stream_socket> s; or asio::buffered_read_stream< asio::ssl::stream<asio::stream_socket> > s; Each layer adds its own state, and the whole lot would need to be reference counted. Cheers, Chris

Christopher Kohlhoff wrote:
--- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote:
Well, it behaves like a (smart) pointer (the last to exit closes the door) so it should look like a (smart) pointer. Why do you think it should have the same interface of a stream_socket? What advantages would give you?
(Btw, technically it would have the same interface, but you would call members functions using -> instead of . )
This idea has a couple of issues that I can see:
- It does not allow an implementation to store additional state inside the socket object directly, so forcing a dynamic allocation on the implementation.
Looking at the current asio implementation looks like all state are pointers (even the ssl streams) or container of pointers. This means that as long as the pointed object is always the same (but its state might change) they can freely be copyied. The last reference cleans things up as usual. If you really really need non-pointer state for a particulare socket type, well, you need dynamic allocation, but only in that case. Btw, only asio::socket_ptr<> would pay for the eventual dynamic allocation, asio::*_socket would not.
- It does not address stream layering, as in:
asio::ssl::stream<asio::stream_socket> s;
or
asio::buffered_read_stream< asio::ssl::stream<asio::stream_socket> > s;
Each layer adds its own state, and the whole lot would need to be reference counted.
No, you make an asio::socket_ptr<asio::buffered_read_stream<
asio::ssl::stream<asio::stream_socket> > >. Only the resulting
object need reference counting. In the end asio::socket_ptr<Stream> would look at its pointed object and decide if it needs dynamic allocation or not. If it does it dynamically allocates it and it is *exactly like* a shared_ptr<Strem>, if it does not (all state are pointers), it keep the object as internal state and behaves as a collection of shared_ptrs to the pointed to objects. BTW, in the end you always have dynamic allocation. There is no way around it. But it usually it happens inside some C library (SSL_CTX for example or win32 SOCKETS) or in the kernel. So the point is not preventing dynamic allocation at all, but preventing unneeded extra allocation. This complicates the desing a lot, so I'm not arguing that it should be done (in fact i'm strongly in favour of the current interface, plus movable sockets), but I think that *if*, after some experience with asio, dynamic allocation of sockets is really needed *and* it is a real bottleneck, it can be done as an optimization (with asio::stream_ptr). -- Giovanni P. Deretta

Hi Giovanni, --- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote: <snip>
In the end asio::socket_ptr<Stream> would look at its pointed object and decide if it needs dynamic allocation or not.
I'm happy with such a solution provided that no requirement is made that any asio class (stream_socket, etc) must support copying. But let me ask: why would this need to be part of asio at all? Consider a hypothetical smart pointer copyable_ptr<T> that works as follows: - checks if a type trait is_intrusively_refcounted<T>::value is true, and if so is implemented in terms of intrusive_ptr<T>. - checks if a type trait is_refcounted_value_type<T>::value is true, and if so is simply implemented in terms of T. - otherwise is implemented in terms of shared_ptr<T>. Then all asio would need to do is implement the type traits for its own classes as appropriate. Maybe boost already has such a thing?
If it does it dynamically allocates it and it is *exactly like* a shared_ptr<Strem>, if it does not (all state are pointers), it keep the object as internal state and behaves as a collection of shared_ptrs to the pointed to objects. BTW, in the end you always have dynamic allocation. There is no way around it. But it usually it happens inside some C library (SSL_CTX for example or win32 SOCKETS) or in the kernel. So the point is not preventing dynamic allocation at all, but preventing unneeded extra allocation.
But whether allocation occurs inside the kernel or some C library isn't really relevant, because that memory doesn't give us a place to store a reference count. The question is whether the asio public interface should require dynamic allocation inside asio's own implementation -- and that is something I am definitely against. However, as I said, the above smart pointer solution is something I could live with, since it simply says that if an asio implementation happens to use dynamic allocation, then it may as well also support intrusive reference counting.
This complicates the desing a lot, so I'm not arguing that it should be done (in fact i'm strongly in favour of the current interface, plus movable sockets), but I think that *if*, after some experience with asio, dynamic allocation of sockets is really needed *and* it is a real bottleneck, it can be done as an optimization (with asio::stream_ptr).
Yep, I agree. (Except for the movable bit :) see other reply.) Cheers, Chris

Christopher Kohlhoff wrote:
Hi Giovanni,
--- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote: <snip>
In the end asio::socket_ptr<Stream> would look at its pointed object and decide if it needs dynamic allocation or not.
I'm happy with such a solution provided that no requirement is made that any asio class (stream_socket, etc) must support copying.
But let me ask: why would this need to be part of asio at all?
Convenience first and foremost - it would let you write a tutorial without introducing other boost components - and freedom to change the implementation later. Lack of dependencies, in other words.

Christopher Kohlhoff wrote:
Hi Giovanni,
--- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote: <snip>
In the end asio::socket_ptr<Stream> would look at its pointed object and decide if it needs dynamic allocation or not.
I'm happy with such a solution provided that no requirement is made that any asio class (stream_socket, etc) must support copying.
Yes, it would be in *addition* to a non copyable (but hopefully movable :) ) socket.
But let me ask: why would this need to be part of asio at all?
Consider a hypothetical smart pointer copyable_ptr<T> that works as follows:
- checks if a type trait is_intrusively_refcounted<T>::value is true, and if so is implemented in terms of intrusive_ptr<T>.
- checks if a type trait is_refcounted_value_type<T>::value is true, and if so is simply implemented in terms of T.
- otherwise is implemented in terms of shared_ptr<T>.
Then all asio would need to do is implement the type traits for its own classes as appropriate.
Would work but not as you think: the pointed object is not an asio asio::*_socket, but a set of object pointed to from the private members of asio::*_socket: a pointer to the demuxer, a "pointer" to the device (the file descriptor), a pointer to an SSL_CTX, a pointer to the buffer etc. If you look at most asio sockets, they are nothing more than that (that or I'm really wrong here!! :) ). Some of this pointers are owned by the socket (and have all the same lifetime), other pointers (the demuxer for example) are not owned. Your copyable_ptr would hold internally the full set of these pointers plus a (stateless) interface to use them (the asio::*_socket set of member functions). The whole set of pointers is reference counted (by a single counter as they all share the same lifetime). When the counter drop to zero, a deleters is called for each pointer that does the clean up (if the pointer is not owned by the copyable_ptr is not deleted). It may be the case that some types of asio::*_socket have actual internal state (instead of a pointer to state). In this case the copyable_ptr would resort to dynamically allocate the socket and refcout it. So to make copyable_ptr a separate lib, it would need formidable introspection powers to 'see' inside a asio::*_socket or each asio::*_socket should in some way (via trait classes) 'explain' its state. Because of this tight coupling i think that *if* such a class is really needed it should be part of asio. BTW, when do you plan to release next version of asio? -- Giovanni P. Deretta,

Hi Giovanni, --- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote:
Would work but not as you think: the pointed object is not an asio asio::*_socket, but a set of object pointed to from the private members of asio::*_socket: a pointer to the demuxer, a "pointer" to the device (the file descriptor), a pointer to an SSL_CTX, a pointer to the buffer etc. If you look at most asio sockets, they are nothing more than that (that or I'm really wrong here!! :) ).
That may be the case now (i.e. 0.3.6) but it may not be the case in the future. I certainly can't say it will be the case for all new stream layers. Possible non-pointer state that I'm thinking of adding to a socket includes: - a flag indicating whether the socket is blocking or not - flags indicating readiness for read/write for use with level-trigged demultiplexing - the protocol object used to open the socket (maybe as part of adding dual IPv4/IPv6 support) <snip>
So to make copyable_ptr a separate lib, it would need formidable introspection powers to 'see' inside a asio::*_socket or each asio::*_socket should in some way (via trait classes) 'explain' its state. Because of this tight coupling i think that *if* such a class is really needed it should be part of asio.
I was thinking the "introspection" would just be that the traits classes would be specialised inside asio. But yeah, I don't see sufficient reason at this time to add something like this over just using shared_ptr<> on whatever it is that needs reference counting (which may not be a socket anyway).
BTW, when do you plan to release next version of asio?
I want to get the IPv6 support finished first so that I can get comments on it. Probably a couple of weeks away at least. Cheers, Chris

Giovanni P. Deretta wrote:
The problem is that a socket is *conceptually* not copyable. Even if you have multiple handles to it, they all refer to the same underlying stream. Standard streams are not copyable for the same reason. If you want different objects to *share* a socket you use a shared_ptr and dynamically allocate it.
In principle, it is true that a stream or a socket a conceptually not copyable. In practice, a stream or a socket is much easier to use (correctly) if it is copyable with reference semantics (and thread safe too, but that's a different story :-) ) - as ordinary C FILE*s and sockets are (although they have to be closed manually, of course.) I'm not really arguing that an asio socket has to be copyable and refcounted... yet, because I don't have the experience to determine how often I'd need to allocate it on the heap in real code. But the tutorial does allocate its sockets on the heap. :-)

Hi Giovanni, --- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote: <snip>
A nice solution would be to make socket movable...
Yes, this would be nice, but unfortunately I see problems reconciling this with some other changes arising from the review that I want to make :( Basically, I want the public interface to allow an implementation that stores state directly in the socket object. Some of the advantages of doing this include: - The ability to eliminate per-socket memory allocations. Coupled with the custom handler allocation support, you could write programs with no ongoing memory allocations occurring inside asio. - Improved performance, e.g. by eliminating many of the hash table lookups to find the list of operations associated with a descriptor. Some of this "state" might be that the socket object is part of an intrusive linked list or hash map. This would make implementing movability, at best, difficult and inefficient. Cheers, Chris

Christopher Kohlhoff wrote:
Hi Giovanni,
--- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote: <snip>
A nice solution would be to make socket movable...
Yes, this would be nice, but unfortunately I see problems reconciling this with some other changes arising from the review that I want to make :(
Basically, I want the public interface to allow an implementation that stores state directly in the socket object. Some of the advantages of doing this include:
- The ability to eliminate per-socket memory allocations. Coupled with the custom handler allocation support, you could write programs with no ongoing memory allocations occurring inside asio.
I think this is very important and I'm all in favor of it. But I do not see how making the socket movable excludes this point. A movable socket would have an extra bit saying if the state is valid or not. When it is "moved" the receving socket gets a copy of the state. The valid bit on the source is turned off while the same bit is turned on in the receiving socket (*). I expect that the internal state of the socket is not big enough that copying is a bottle-neck. Moving is thus not a performance optimization, but a correctess operation. The source object is no longer valid (can only be destoryed) and the destination socket becomes the true handle for the device
- Improved performance, e.g. by eliminating many of the hash table lookups to find the list of operations associated with a descriptor.
Some of this "state" might be that the socket object is part of an intrusive linked list or hash map. This would make implementing movability, at best, difficult and inefficient.
I do not see how this would be inefficient. Suppose that a socket is : struct socket { socket* prev; socket* next; bool valid; /* more state here */ void swap(socket& from) { prev = to.prev; next = to.next; prev->next = this; next->prev = this; /* swap state */ } } ; void move(socket& from, socket& to) { socket().swap(to); from.swap(to) } This requires the socket to be default constructible (or else do a socket(to.demuxer()).swap(to)) and a swap function in socket (i thought that asio::socket already had it, but looking at the docs i see i was wrong). Yes this is thread unsafe. To make it thread safe, you need to lock the list (or at least the objects involved) to protect the swap. But you need a mutex anyway because the object in the intrusive list could be destroyed at any time. Ah, btw, at least on posix systems, file descriptors are guaranteed to be allocated contiguously. So you could create a vector as big as the hightest fd, store the list of operations in it and use the socket_impl as a key for an O(1) lookup in the vector. (*) Btw, you do not really need an extra bit of course. A socket_impl equal to null_socket would identify an invalid socket (as is the case right now). -- Giovanni P. Deretta

On 2/7/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote:
Ah, btw, at least on posix systems, file descriptors are guaranteed to be allocated contiguously. So you could create a vector as big as the hightest fd, store the list of operations in it and use the socket_impl as a key for an O(1) lookup in the vector.
This is not true of course... I don't know what i was thinking... Anyway, a file descriptor is *usually* a small integer, so on those systems that keep the set mostly contiguous a vector would work and not waste much space.

On 2/7/06, Giovanni Piero Deretta <gpderetta@gmail.com> wrote:
On 2/7/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote:
Ah, btw, at least on posix systems, file descriptors are guaranteed to be allocated contiguously. So you could create a vector as big as the hightest fd, store the list of operations in it and use the socket_impl as a key for an O(1) lookup in the vector.
This is not true of course... I don't know what i was thinking... Anyway, a file descriptor is *usually* a small integer, so on those systems that keep the set mostly contiguous a vector would work and not waste much space.
Why is not true? I think you were right. Although *all* file descriptors are contiguously, which means that other parts of the program may create "holes" in the vector. But it would work alright (I think ACE does this on Posix systems). -- Felipe Magno de Almeida

Felipe Magno de Almeida wrote:
On 2/7/06, Giovanni Piero Deretta <gpderetta@gmail.com> wrote:
On 2/7/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote:
Ah, btw, at least on posix systems, file descriptors are guaranteed to be allocated contiguously. So you could create a vector as big as the hightest fd, store the list of operations in it and use the socket_impl as a key for an O(1) lookup in the vector.
This is not true of course... I don't know what i was thinking... Anyway, a file descriptor is *usually* a small integer, so on those systems that keep the set mostly contiguous a vector would work and not waste much space.
Why is not true? I think you were right. Although *all* file descriptors are contiguously, which means that other parts of the program may create "holes" in the vector. But it would work alright (I think ACE does this on Posix systems).
After writing the first email I went and checket the SUSv3. No where it says that when the OS generates a new filedesc it needs to reuse the first number available. In pratice i think that most posix systems do (there are many programs that rely on the set being small, think select() ). But consider dup2(), the user may duplicate a socket fd of value 4 with an fd of value 1234567 or whatever. Yes, this is evil, but might actually happen. -- Giovanni P. Deretta

Hi Giovanni, --- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote:
I think this is very important and I'm all in favor of it. But I do not see how making the socket movable excludes this point. A movable socket would have an extra bit saying if the state is valid or not. When it is "moved" the receving socket gets a copy of the state. The valid bit on the source is turned off while the same bit is turned on in the receiving socket (*). I expect that the internal state of the socket is not big enough that copying is a bottle-neck. Moving is thus not a performance optimization, but a correctess operation. The source object is no longer valid (can only be destoryed) and the destination socket becomes the true handle for the device
Yep, I understand the concept, but... <snip>
Yes this is thread unsafe. To make it thread safe, you need to lock the list (or at least the objects involved) to protect the swap.
This, for me, is the killer. The motivation for move is to avoid expensive copying of "heavy" objects. Using locking to do the move is probably worse :) I think there aren't sufficiently common use cases for movability to justify the costs. In my experience with asio I have not yet come across a real life situation where I would benefit from move, so it was really just a "wouldn't it be cool if..." sort of idea. The use cases are probably even more limited if you consider that what some people really want is copying and reference counting. It's worth noting that locking the list mutex may also be required to implement swap. But since locking a mutex can throw an exception, you now have a swap() that can throw. IMHO, to avoid nasty surprises having no swap() is better than having a throwing swap().
Ah, btw, at least on posix systems, file descriptors are guaranteed to be allocated contiguously. So you could create a vector as big as the hightest fd, store the list of operations in it and use the socket_impl as a key for an O(1) lookup in the vector.
Even for systems where this is true, it is only true for the whole process, not a single asio::demuxer object. You might have one demuxer per socket, in which case most of the vector is wasted. Cheers, Chris

Christopher Kohlhoff wrote:
Yes this is thread unsafe. To make it thread safe, you need to lock the list (or at least the objects involved) to protect the swap.
This, for me, is the killer. The motivation for move is to avoid expensive copying of "heavy" objects. Using locking to do the move is probably worse :)
As i said, i see, in this case, move as a correctness issue than an optimization. I do not see how you can avoid locking if you are going for intrusive refcounting. For example, thread A creates a stream_socket and register it in a demuxer. The demuxer might be shared between threads so it needs to lock the list mutex to insert it in the queue. Then thread A might destroy the socket at any time, so it need to relock the queue to remove the socket. Am i missing something?
I think there aren't sufficiently common use cases for movability to justify the costs. In my experience with asio I have not yet come across a real life situation where I would benefit from move, so it was really just a "wouldn't it be cool if..." sort of idea. The use cases are probably even more limited if you consider that what some people really want is copying and reference counting.
I have (*) one use case that would really benefit if asio moved its callback instead of copying it. As the callback owns the socket, it would need the socket to be movable, or at least swappable. And no, i do not think it is a wouldn't be just "a cool thing"... it is obvious from asio examples that support for moving (with support from bind of course :) ) an auto pointer would be obviate the need of a shared_ptr. A movable socket wouldn't even need dynamic allocation.
It's worth noting that locking the list mutex may also be required to implement swap. But since locking a mutex can throw an exception, you now have a swap() that can throw. IMHO, to avoid nasty surprises having no swap() is better than having a throwing swap().
Yes, locking is required for swap too if you use ref counting. I do not see how and why locking a mutex could trow? Boost::thread mutexes do not throw. Pthread mutex fail only while trying to relock a non recursive mutex (a precondition violation) or an uninitialized mutex (another precondition violation). Btw, you could use a lock free list (not that i know how to use it :) ).
Ah, btw, at least on posix systems, file descriptors are guaranteed to be allocated contiguously. So you could create a vector as big as the hightest fd, store the list of operations in it and use the socket_impl as a key for an O(1) lookup in the vector.
Even for systems where this is true, it is only true for the whole process, not a single asio::demuxer object. You might have one demuxer per socket, in which case most of the vector is wasted.
Sure, but the highest socket value seen by a demuxer is not very high (in the order of MAX_OPEN on sane systems). So the space wasted for a not full demuxer is not much, and probably not much higher than keeping a list with pointers to siblings. Most applications will have approximately of one demux per thread (And one thread per cpu). The one demuxer per socket could be special cased. (*) Well, not working code now, still experimenting on it. I can work around non having move by using copying, but it requires dynamic allocation and ref counting. -- Giovanni P. Deretta

Hi Giovanni, --- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote:
As i said, i see, in this case, move as a correctness issue than an optimization.
Can you explain what you mean by correctness? Today I was pondering move (and swap) as a general design idea, and I suspect that a good rule of thumb might be that they are appropriate on value-type classes that have "heavy" implementations, and particularly where copying may throw. They do not seem appropriate operations for a non-value-type class which has identity, since to me the address of the object is part of its identity. I do believe the usual motivation for move is as a performance optimisation, to construct the target object with minimum expense, e.g. see: http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm If it is not possible to implement move efficiently, then I think it should not be there at all, otherwise it could lead to unpleasant surprises.
I do not see how you can avoid locking if you are going for intrusive refcounting. For example, thread A creates a stream_socket and register it in a demuxer. The demuxer might be shared between threads so it needs to lock the list mutex to insert it in the queue. Then thread A might destroy the socket at any time, so it need to relock the queue to remove the socket. Am i missing something?
But this locking is currently only needed when the socket object is constructed or destroyed, which are not usually common operations. Moving is something that could occur quite a lot.
I have (*) one use case that would really benefit if asio moved its callback instead of copying it. As the callback owns the socket, it would need the socket to be movable, or at least swappable. And no, i do not think it is a wouldn't be just "a cool thing"... it is obvious from asio examples that support for moving (with support from bind of course :) ) an auto pointer would be obviate the need of a shared_ptr.
The callbacks are an example of why I think a move operation would have limited application. Move support can only be used if there is just one chain of asynchronous operations associated with the object. As soon as there is a second chain (e.g. running an async_wait on a timer while you do an async_read, or doing an async_read and async_write at the same time) the move support becomes useless.
I do not see how and why locking a mutex could trow? Boost::thread mutexes do not throw.
I see that it does not, but perhaps it should :) On Windows 2000 or earlier the EnterCriticalSection function can fail in low memory situations. This is not handled at all in the boost::mutex class as far as I can see.
Btw, you could use a lock free list (not that i know how to use it :)).
I would categorise this as the "difficult" bit of "difficult and inefficient" :) Seriously though, the more I think about move the more problems and limitations I see it creating for the implementation. Some more examples: - An implementation that stores state direct in the socket object may have pointers to that state in other data structures. All these would need to be tracked and updated if a move occurred. - The data structures used by epoll and kqueue can contain a pointer to user data. For efficiency, this could (and probably should) point directly to the state that is embedded in the socket object. This pointer can only be updated via a system call. This discussion has actually been quite helpful for me in confirming why the sockets should not support move and swap... Sorry :)
Sure, but the highest socket value seen by a demuxer is not very high (in the order of MAX_OPEN on sane systems).
Hmmm, I dunno. I have used asio in servers that support more than 50000 concurrent connections. That's potentially a lot of wasted space! Cheers, Chris

Christopher Kohlhoff wrote:
Hi Giovanni,
--- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote:
As i said, i see, in this case, move as a correctness issue than an optimization.
Can you explain what you mean by correctness?
The same reason that auto_ptr is movable. It cannot be copied because it wouldn't make any sense, but it is movable because it let an owned object escape the scope in witch it has been created. Certanly I wouldn't consider auto_ptr a value type object. With auto_ptr not copying is not a problem of efficiency, but a problem of not having two auto_ptr owning the same pointer. I see it the same way with asio sockets. You can't have have two asio sockets because they would own the same internal socket_impl. They can't be copied because that wouldn't make any sense, but if they were movable, they could escape the scope where they have been created. To do this now you need dynamic allocation.
I do not see how you can avoid locking if you are going for intrusive refcounting. For example, thread A creates a stream_socket and register it in a demuxer. The demuxer might be shared between threads so it needs to lock the list mutex to insert it in the queue. Then thread A might destroy the socket at any time, so it need to relock the queue to remove the socket. Am i missing something?
But this locking is currently only needed when the socket object is constructed or destroyed, which are not usually common operations.
You need locking *every time* you traverse the list, and this is going to be the most expensive locking.
Moving is something that could occur quite a lot.
Well, right now is not possible at all :). Swap would just add another option to the user. Certanly if it is really going to be expensive, probably it is not worth it.
I have (*) one use case that would really benefit if asio moved its callback instead of copying it. As the callback owns the socket, it would need the socket to be movable, or at least swappable. And no, i do not think it is a wouldn't be just "a cool thing"... it is obvious from asio examples that support for moving (with support from bind of course :) ) an auto pointer would be obviate the need of a shared_ptr.
The callbacks are an example of why I think a move operation would have limited application. Move support can only be used if there is just one chain of asynchronous operations associated with the object. As soon as there is a second chain (e.g. running an async_wait on a timer while you do an async_read, or doing an async_read and async_write at the same time) the move support becomes useless.
Even if you end up not making socket movable, could you make asio move the callback instead of copying it (may be a asio_move() customization function that by default copies and can be overloaded to move for specific user types)?
I do not see how and why locking a mutex could trow? Boost::thread mutexes do not throw.
I see that it does not, but perhaps it should :) On Windows 2000 or earlier the EnterCriticalSection function can fail in low memory situations. This is not handled at all in the boost::mutex class as far as I can see.
Hum, looks really bad. I do not really know the win32 api, but i think that if ECS fails with a low memory error, it might be because the kernel has exhausted the internal memory. I do not think that an application has realistically any way to recover (like freeing more memory on a low memory situation in userspace). May be the mutex should just fail (abort the application). Conceptualy a mutex CAN'T fail, it locks or wait.
Btw, you could use a lock free list (not that i know how to use it :)).
I would categorise this as the "difficult" bit of "difficult and inefficient" :)
Seriously though, the more I think about move the more problems and limitations I see it creating for the implementation. Some more examples:
- An implementation that stores state direct in the socket object may have pointers to that state in other data structures. All these would need to be tracked and updated if a move occurred.
They would need to be tracked when the object is destroyed any way. I think that having a pointer inside aio that points back to a user socket is not going to be fun.
- The data structures used by epoll and kqueue can contain a pointer to user data. For efficiency, this could (and probably should) point directly to the state that is embedded in the socket object. This pointer can only be updated via a system call.
Same reason above. BTW, why not doing the other way around? asio user sockets are only trivial objects pointing to internal asio objects. These objects are kept in a list owned by the demuxer and are allocated using a fast allocator. When the user creates a new object, asio allocates the implementation and puts it in a temporary (thread safe) insert list, then gives the user a pointer to this implementation. At the beginning of run() the demuxer thread acquires the insert list lock and splices all objects in the main list. Deletion is done in the same way. When the user destroys the socket, the implementation is put inside a delete list. At the beginning of run() the demuxer removes all objects in the delete list from the main list, then deallocates all of them. In fact instead of an allocator you could use a free list: the same mutex used for the insert list could be used to lock the free list. Allocating would just be a matter of moving the front object from the free list to the insert list. When the free list is empty, you just allocate a batch of new objects. Having the implementation owned by the demuxer would simplify storing state in the object; have the guarantee that all pointer kept by epoll and kqueue (or whatever api) will never be dangling; have the guarantee that any file descriptor you have (in an epoll set for example) has not be reused (you defer the destruction of the socket until you have no references to it); it makes it easy to reuse SOCKETs on win32 (where they are expensive to allocate AFAIK): you never free them, just close and add them to the free list. This solution requires the same amount of locking of your intrusive list when creating and destroying a socket, but the lock is going to be contended less often (expecially if every thread has its own insert/delete list to the demuxer). The demuxer need to lock only once per run() and only for a fast splice. Ah yes, it makes the implementation of move/swap trivial and lockless :) This is just a wild untested idea anyway, it might not be appropriate for the current asio implementation.
This discussion has actually been quite helpful for me in confirming why the sockets should not support move and swap... Sorry :)
Glad to have been helpful :). I will not argue any longer that asio socket need to be movable. In the end you know the inner working of asio way better than me and certanly know what can and can't be done.
Sure, but the highest socket value seen by a demuxer is not very high (in the order of MAX_OPEN on sane systems).
Hmmm, I dunno. I have used asio in servers that support more than 50000 concurrent connections. That's potentially a lot of wasted space!
To handle that many connections you need lots of memory anyway. 200kb doesn't look that bad. I'm looking forward to try next releaso of asio. Good work. -- Giovanni P. Deretta

Hi Peter, --- Peter Dimov <pdimov@mmltd.net> wrote: <snip>
But the C-style facilities used in the tutorial actually do serve as a distraction to me, not just a mere annoyance.
That's fine. Don't get me wrong - I'm not arguing in favour of keeping the C-style usage. I'm just saying that I am not yet sure which of the alternative approaches is most appropriate for the tutorial. <snip>
- if the tutorial doesn't show the recommended way to use asio, what _is_ the recommended way to use it?
Why must there be the one true recommended way to use it? As far as possible I want the interface to allow alternative implementation approaches. Ideally the tutorial would introduce a few of them. My preferred approach is to use classes, binding the member functions as callbacks, and using shared_from_this() to ensure the class is kept alive as long as needed. But this may not be suited to every application. As for using this approach in the tutorial, I'm concerned that introducing shared_from_this() may be a distraction for users not already familiar with the facility.
shared_ptr<socket> is the easy part, although this immediately leads me to ask the obvious question: why isn't a socket already a shared_ptr<socket_impl>?
Why should it be? A socket has an identity and is not a value-type object. And I don't want the interface to impose the cost of using a shared pointer when it's not required. In many applications the socket object might be on the stack, or it could be a member of some other class, e.g.: class my_connection { ... asio::stream_socket socket_; ... }; where the outer class is the one that's in a shared_ptr (as it is in my preferred approach). Consider also stream layering, e.g.: asio::ssl::stream<asio::stream_socket> socket; In this case a reference counted stream_socket implementation gives no advantage - it's the entire composed stream object that would need to be reference counted. Hence the shared_ptr should be external to the stream, not internal. Perhaps the problem is that the tutorial is a little too simple, and I should find an example application that has more "real world" requirements.
For the string, my first thought was to just pass a std::string by value. This of course doesn't work because the string needs to stay alive until the operation is complete, so we need something like a shared_array<char>. I think that the library should provide one.
I don't think that this should be part of the library at this time. (Perhaps in the longer term after some patterns emerge from use - although it may be better as a separate "buffers" library.) Instead, I am tightening up the usage of the Mutable_Buffers and Const_Buffers concepts to ensure that reference counted implementations of these concepts will work correctly. Cheers, Chris

Christopher Kohlhoff wrote:
Hi Peter,
- if the tutorial doesn't show the recommended way to use asio, what _is_ the recommended way to use it?
Why must there be the one true recommended way to use it? As far as possible I want the interface to allow alternative implementation approaches. Ideally the tutorial would introduce a few of them.
Whatever gets presented in the tutorial becomes the de-facto one true recommended way to use the library, whether you like it or not. :-)

On 2/7/06, Christopher Kohlhoff <chris@kohlhoff.com> wrote: [snip]
Why should it be? A socket has an identity and is not a value-type object. And I don't want the interface to impose the cost of using a shared pointer when it's not required.
That's why it should be able to have both, one that's moveable and another that's reference counted.
In many applications the socket object might be on the stack, or it could be a member of some other class, e.g.:
Moveable would work nicely this way, but hardly well without even moveable-semantics. [snip]
where the outer class is the one that's in a shared_ptr (as it is in my preferred approach). Consider also stream layering, e.g.:
asio::ssl::stream<asio::stream_socket> socket;
In this case a reference counted stream_socket implementation gives no advantage - it's the entire composed stream object that would need to be reference counted. Hence the shared_ptr should be external to the stream, not internal.
asio::ssl::stream should give a reference counted and a moveable interface too, and use the moveable internally. [snip]
Cheers, Chris
-- Felipe Magno de Almeida

Christopher Kohlhoff wrote:
Hi Peter,
--- Peter Dimov <pdimov@mmltd.net> wrote:
Why is
http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/tutorial...
using raw pointers, strdup, new, free and delete? This is soo 1996. Tutorials shoud teach.
Yep, this already came up in a review and I'm planning to change it. IIRC, the motivation for doing it this way was:
- to avoid introducing too many boost facilities at once
- to illustrate the "exactly-once" callback invocation
Can you explain the "exactly once" principle in more detail, or point me to the documentation that discusses it? boost::asio::socket_acceptor tcp_acceptor(demuxer, boost::asio::ipv4::tcp::endpoint(13)); boost::asio::stream_socket* tcp_socket = new boost::asio::stream_socket(demuxer); tcp_acceptor.async_accept(*tcp_socket, boost::bind(handle_tcp_accept, &tcp_acceptor, tcp_socket, boost::asio::placeholders::error)); For example, what happens here if async_accept fails, perhaps because of insufficient memory? Does it throw bad_alloc? Is the handler executed? http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/design/d... could have answered my question since the boost::function<> constructor can throw and in this case tcp_socket would obviously leak, but I don't see this cited as an argument against function<> use?

Hi Peter, --- Peter Dimov <pdimov@mmltd.net> wrote:
Can you explain the "exactly once" principle in more detail, or point me to the documentation that discusses it?
Basically if: - the function used to initiate the operation does not throw; and - the demuxer::run() call is run until there is no work left. the handlers for the operations are guaranteed to be called. See: http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/referenc... for example code showing how to ensure that demuxer::run() keeps going until all work is completed even if exceptions are thrown.
boost::asio::socket_acceptor tcp_acceptor(demuxer, boost::asio::ipv4::tcp::endpoint(13));
boost::asio::stream_socket* tcp_socket = new boost::asio::stream_socket(demuxer);
tcp_acceptor.async_accept(*tcp_socket, boost::bind(handle_tcp_accept, &tcp_acceptor, tcp_socket, boost::asio::placeholders::error));
For example, what happens here if async_accept fails, perhaps because of insufficient memory? Does it throw bad_alloc?
It could.
Is the handler executed?
No (according to above rules). This particular code doesn't seem to be exception safe as it stands :o) It should be more like: std::auto_ptr<boost::asio::stream_socket> tcp_socket( new boost::asio::stream_socket(demuxer)); tcp_acceptor.async_accept(*tcp_socket, boost::bind(handle_tcp_accept, &tcp_acceptor, tcp_socket, boost::asio::placeholders::error)); tcp_socket.release(); However, this is probably all too complicated for a tutorial. I'm now leaning towards just going with the shared_from_this() style for the tutorial (as used in the HTTP server example), with an appropriate link into the shared_ptr docs explaining it. If a developer is sufficiently motivated to want to eliminate the costs of using shared_ptr, I'll point them to other examples that showcase alternative styles. Cheers, Chris

Christopher Kohlhoff wrote:
boost::asio::socket_acceptor tcp_acceptor(demuxer, boost::asio::ipv4::tcp::endpoint(13));
boost::asio::stream_socket* tcp_socket = new boost::asio::stream_socket(demuxer);
tcp_acceptor.async_accept(*tcp_socket, boost::bind(handle_tcp_accept, &tcp_acceptor, tcp_socket, boost::asio::placeholders::error));
For example, what happens here if async_accept fails, perhaps because of insufficient memory? Does it throw bad_alloc?
It could.
Is the handler executed?
No (according to above rules).
Not good. If you want the library to support this low-level style, the above snippet should be exception-safe; this could be accomplished by calling the handler with an appropriate asio error code. This aside, why would one need shared_from_this? boost::asio::socket_acceptor tcp_acceptor(demuxer, boost::asio::ipv4::tcp::endpoint(13)); boost::shared_ptr<boost::asio::stream_socket> tcp_socket( new boost::asio::stream_socket(demuxer) ); tcp_acceptor.async_accept(*tcp_socket, boost::bind(handle_tcp_accept, &tcp_acceptor, tcp_socket, boost::asio::placeholders::error));

Hi Peter, --- Peter Dimov <pdimov@mmltd.net> wrote: <snip>
Is the handler executed?
No (according to above rules).
Not good. If you want the library to support this low-level style, the above snippet should be exception-safe;
As I said in my previous reply, the tutorial code is simply broken with respect to exception safety. It could be made exception safe by putting the pointer in an auto_ptr and only releasing it after the async_accept function call has returned successfully. However, I do plan to redo the tutorial to use a different style anyway, so this is a moot point.
this could be accomplished by calling the handler with an appropriate asio error code.
This would not be appropriate as it may break other important behaviour of the interface, namely: - the handler must not be invoked inside the async_accept call; and - the handler must only be invoked from a thread that is calling demuxer::run()
This aside, why would one need shared_from_this?
Because the tutorial may be misleading due to being too simple, and I plan to change it to use an approach that will also fit more complex problems better. After all, you did say that whatever is used in the tutorial will effectively become the recommended approach. I am thinking along the lines of the HTTP server example, where you have a per-connection class, e.g.: class tcp_connection : public enable_shared_from_this<tcp_connection> { ... void send_reply() { asio::async_send(socket_, asio::buffer(reply_), boost::bind(&tcp_connection::handle_send, shared_from_this(), asio::placeholders::error)); } void handle_send(const asio::error& e) { ... } ... asio::stream_socket socket_; std::string reply_; }; Cheers, Chris

On 2/8/06, Christopher Kohlhoff <chris@kohlhoff.com> wrote: [snip]
However, this is probably all too complicated for a tutorial. I'm now leaning towards just going with the shared_from_this() style for the tutorial (as used in the HTTP server example), with an appropriate link into the shared_ptr docs explaining it. If a developer is sufficiently motivated to want to eliminate the costs of using shared_ptr, I'll point them to other examples that showcase alternative styles.
Is there any ready example about alternative styles without shared_ptr overhead? I'm very curious about how it is possible (without cluttering the code).
Cheers, Chris
best regards, -- Felipe Magno de Almeida

Hi Felipe, --- Felipe Magno de Almeida <felipe.m.almeida@gmail.com> wrote:
Is there any ready example about alternative styles without shared_ptr overhead? I'm very curious about how it is possible (without cluttering the code).
Not as yet, sorry. I'm planning to write an example where all objects are created up front, including buffer space for custom handler allocation. For example, one can imagine a server where a configuration file specifies the maximum number of concurrent connections that will be allowed. Once the server is up and running there will be no ongoing memory allocation. The motivations for using this approach would include performance, robustness, and eliminating heap fragmentation in what might be a very long running process. Since all the application objects would have a lifetime that spans the call to demuxer::run(), you could simply pass plain pointers around to your handlers. Cheers, Chris
participants (7)
-
Christopher Kohlhoff
-
Felipe Magno de Almeida
-
Giovanni P. Deretta
-
Giovanni Piero Deretta
-
Martin Wille
-
Peter Dimov
-
Stefan Seefeld