Re: [network] An RFC - updated

Hi Peter,
I like the proposal. It shows a certain polish that only comes with real-world experience with a design. ;-)
Thanks!
I have the following comments for now, based on a not-very-deep look at the document and the header:
Thanks for all the feedback.
1. network root class
In my opinion, this class is not needed and should be removed. It complicates the design, forces a global variable on the user, and is not library-friendly. For example, if lib1 and lib2 use the network, the user can't pass an address created by lib1 to lib2. The global state modeled by the network class should be an implementation detail.
In many of my uses, I have multiple network-derived types in use at the same time (serial, HTTP tunnel, TCP/IP). I agree that it can be an issue, so perhaps there should be a way to associate code with a particular kind of network and not need the network object. Having said that, though, I think this assumed context should only be a default to be used when the calling code has no network object in hand. All libraries should accept and pass along the context. Otherwise, one could not reuse protocols over different networks in the same app. While this can be seen as an issue, most of the time I find that I need to pass something along already: an address object or a stream, for example. That is sufficient to get back to the network object. It is also OK to create multiple network instances of the same type, though it should not be done with impunity<g>.
2. address
The address should be a standalone class and not tied to a particular network.
I think different networks (in my uses anyway) have different expectations (data size, textual form, etc.). This lead me to the conclusion that an address is an abstract idea. Its primary role is a factory for other objects related to the address it describes, which is again not something a concrete type would be able to handle (at least w/o indirection hidden inside).
It should be a simple entity and not a hierarchical container. Logical to physical resolution should take a source logical address and produce a container of physical addresses.
This container is often needed internally and, in my experience, seldom needed by the user. It is helpful for the logical address object to know the corresponding physical addresses and not have to repeat the resolution process. I see no fundamental problem exposing this collection as std::vector<address_ptr> (or whatever) and not providing an interface to encapsulate it, but I was trying to keep things purely abstract.
The address should not be created with an URL, that is, a TCP connection to www.example.com should be represented by the address tcp:/www.example.com:80, leaving http://www.example.com reserved for the stream obtained by the HTTP GET / query, not for the raw protocol stream. A connection to the serial port should probably be represented by "com1:/38400,n,8,1", and so on.
This scheme morphing seems to fit with the idea that network instances are behind the scenes, but creates complexity for the user as well since these transformations must be done at that level. For serial use, it is not sufficient to say "com1" (that was a concept I had early on as well). That would address an entire network, not a specific "port"-like concept. I think the "U" in URL is really fitting for this model. There is a lot of thought behind URL's and they have the right level of generality. IMHO, we do not need another textual form for addresses.
I think that the UDP broadcast address should be represented by udp:/0.0.0.0, and the TCP loopback should be tcp:/127.0.0.1, as usual. But I may be wrong.
All of these are forcing TCP/IP (IPv4 even<g>) concepts to the user. The central idea of the abstraction I proposed is that "datagram" and "stream" are the behavior-bundles to which one should write protocols, not TCP/UDP or sockets. The notion of loopback and broadcast can carry over from one network type to another, but these textual forms do not. That said, this will work given an IPv4 network object in my proposal: strm = net->new_address("127.0.0.1:80")->new_stream(); It is just not portable to IPv6 or any other network type. Hence, one is better off saying: strm = net->new_loopback_address()->new_stream();
3. Minor stylistic issues
p->get_X() should be p->X()
Personally, I go back and forth on this<g>. I suppose that std::basic_string<>::length is the right form to follow. I currently like the get_X/set_X symmetry, but will change back in the proposal.
p->new_Y() should probably be p->create_Y()
Is that a preferred verb for Abstract Factory? I should reread that chapter... :) I chose "new" because it advertised exactly what must happen: a new instance is made. Not that I am wedded to it, if there is ample precedent for "create".
although it might be better to move to net::create_Y( p ) or even to Y( p ) and reference semantics under the hood.
The approach I took (based on shared_ptr <g>) allowed for clear ownership, copy and layering semantics. In other words: net::stream_ptr stream = ...; stream = ssl::new_client_stream(stream); At this point, the new SSL stream impl drives the original stream which it can rely upon to exist until it is done with it. From the user's point of view, the SSL stream is now _the_ stream.
The destructors should be protected.
Agreed.
4. MT-centric model
The general idea of an asynchronous callback dispatcher is sound, but the threading decision should be left to the user. The library should provide
void net::poll( timeout tm );
that delivers any pending callbacks from the context of the thread that called poll, and
void net::async_poll( error_callback );
that acts "as if" it executes net::poll( infinite ) repeatedly in one or more background threads. (error_callback will be called on errors; the synchronous variant will probably throw an exception instead.)
The threading choices are certainly the most complex. Having said that, these are mostly implementation choices, not interface choices though one might need to extend the interface to support ideas like the one you propose. The complexity really shows up when one wants to write protocol libraries. For each choice presented to the network user, the protocol library probably needs to provide similar support. For example, an HTTP library would need to provide sync and async under my proposal. Adding more styles of notification to the network layer probably makes this job more difficult. Not to pass final judgment here; just a consideration. In my work on this, I attacked thread affinity outside the network layer for the most part. In particular, what you called net::poll() I had as a different object that handled a queue of boost::function<>-like objects. That way, the main thread was not forced to be only a network thread. I do see room for this approach in cases where the main thread wants to be a network-only thread and the queue approach feels too heavy. I think that this would fit in my proposal as a different concrete network-derived class as long as the abstraction is the same: sync and async (with other rules TBD).
Whether the library uses multiple threads under the hood, or how many, is implementation defined, even if async_poll is not called; this depends on which model delivers the best performance on the specific platform.
While I agree to some extent, the user must know the context in which callbacks will be made. Or at least, they need to know a set of rules to follow that will keep their code working for all implementations. Forcing a single-threaded view of the network on the user imposes its own penalty. At the top-most level, the programmer should have some (hopefully small<g>) number of concrete network objects to pick amongst. Once chosen, all mid-level libraries need to know that their expectations of behavior are still going to be met. At the bottom, we do what seems best to implement the abstraction on a given platform.
5. read_later, etc
I think that read_later should be dropped. async_read is enough, in my opinion.
Personally I have little use for non-blocking style, but there is a lot of desire for it. And it does have its merits for buffering data.
The functionality of write_later should be achievable with a call to async_write with size 0; write_later can be dropped, too.
I debated this myself, but decided to apply the axiom "don't parameterize behavior" and ended up with the x_later approach. Under the covers, they will be very much like async_x with no buffers on which to operate.
async_read should not take a buffer; instead, the callback should receive a pointer to a buffer managed by the library that is guaranteed to be valid for the duration of the callback. (Not by default, at least.)
async_write (by default) should not assume that the passed buffer stays valid after async_write returns.
Rationale: buffer management is a pain with multiple callbacks active ;-)
Others I believe have commented on this, but my take is that this level should not make assumptions about the ultimate destination of data. This approach, would in many cases, force data to be copied "one more time". I agree that this can be a painful process, but perhaps this is where higher level abstractions come into play, or possibly (as you suggest) optional additional features for this abstraction. Maybe passing (NULL,0) for I/O requests, or some sort of set_buffer() mechanism. Don't know.
That's it for now. Please don't take these as criticisms and wherever you see "should" read "in my humble opinion, the design probably could be enhanced by".
Please understand my responses in the same way. :) I (try<g>) to never take criticism of designs personally, and I do feel that all criticism, especially constructively phrased as yours, is only beneficial to everyone.
Thanks for reading.
Nope - thank you for taking the time read my proposal and then respond with so many ideas! Best regards, Don __________________________________________________ Do You Yahoo!? Tired of spam? Yahoo! Mail has the best spam protection around http://mail.yahoo.com

Don G wrote:
1. network root class
In my opinion, this class is not needed and should be removed. It complicates the design, forces a global variable on the user, and is not library-friendly. For example, if lib1 and lib2 use the network, the user can't pass an address created by lib1 to lib2. The global state modeled by the network class should be an implementation detail.
The network root issue is at, hm, the root of our disagreement.
In many of my uses, I have multiple network-derived types in use at the same time (serial, HTTP tunnel, TCP/IP).
The question is why do you need multiple network objects at the client side. Under the address-centric model, the form of the address determines the network to use. The library is free to maintain several network objects under the hood, ... [...]
While this can be seen as an issue, most of the time I find that I need to pass something along already: an address object or a stream, for example. That is sufficient to get back to the network object.
... and obtain them as outlined above.
2. address
The address should be a standalone class and not tied to a particular network.
I think different networks (in my uses anyway) have different expectations (data size, textual form, etc.). This lead me to the conclusion that an address is an abstract idea. Its primary role is a factory for other objects related to the address it describes, which is again not something a concrete type would be able to handle (at least w/o indirection hidden inside).
In general, data-driven designs are much more flexible. I can read server.address=tcp4:/www.example.com:5757 from a configuration file and connect. The line might have been server.address=com1:/57600,n,8,1 but my application doesn't care, as long as someone at the other end responds according to the appropriate server protocol. Under the explicit network model, I'll need to duplicate the scheme-to-network logic myself. Not that a map<string, network_ptr> is that much work; there's just no benefit. The "self contained address as a string" model also has another advantage: it allows you to take a legacy design such as std::fopen and enhance it with network capability in a backward-compatible way.
It should be a simple entity and not a hierarchical container. Logical to physical resolution should take a source logical address and produce a container of physical addresses.
This container is often needed internally and, in my experience, seldom needed by the user. It is helpful for the logical address object to know the corresponding physical addresses and not have to repeat the resolution process. I see no fundamental problem exposing this collection as std::vector<address_ptr> (or whatever) and not providing an interface to encapsulate it, but I was trying to keep things purely abstract.
Yes, I understand. This can be handled both ways. You could still cache the physical addresses inside the logical address and return them on the second and subsequent resolve() calls.
The address should not be created with an URL, that is, a TCP connection to www.example.com should be represented by the address tcp:/www.example.com:80, leaving http://www.example.com reserved for the stream obtained by the HTTP GET / query, not for the raw protocol stream. A connection to the serial port should probably be represented by "com1:/38400,n,8,1", and so on.
This scheme morphing seems to fit with the idea that network instances are behind the scenes, but creates complexity for the user as well since these transformations must be done at that level. For serial use, it is not sufficient to say "com1" (that was a concept I had early on as well). That would address an entire network, not a specific "port"-like concept.
I'm not sure I understand. A communication port is a port, not a network. I've recently dealt with one and it's very much like a socket. :-)
I think the "U" in URL is really fitting for this model. There is a lot of thought behind URL's and they have the right level of generality. IMHO, we do not need another textual form for addresses.
The form above is a valid URI/URL, by the way. The single slash means that the text after the scheme is application-dependent and does not follow the host:port/path?query#anchor format. Why I prefer tcp:/host:port instead of scheme://host? Let's get back to enhancing std::fopen in a backward-compatible way. I'd expect fopen on http://www.example.com to return the data stream obtained by the corresponding GET query, for obvious reasons.
I think that the UDP broadcast address should be represented by udp:/0.0.0.0, and the TCP loopback should be tcp:/127.0.0.1, as usual. But I may be wrong.
All of these are forcing TCP/IP (IPv4 even<g>) concepts to the user. The central idea of the abstraction I proposed is that "datagram" and "stream" are the behavior-bundles to which one should write protocols, not TCP/UDP or sockets. The notion of loopback and broadcast can carry over from one network type to another, but these textual forms do not.
Yes; another manifestation of the network issue. I agree that in a network-centric design your approach is preferable. In an address-centric design, the TCP/IP4 broadcast address is not portable between networks.
3. Minor stylistic issues
p->get_X() should be p->X()
Personally, I go back and forth on this<g>. I suppose that std::basic_string<>::length is the right form to follow. I currently like the get_X/set_X symmetry, but will change back in the proposal.
p->new_Y() should probably be p->create_Y()
Is that a preferred verb for Abstract Factory? I should reread that chapter... :) I chose "new" because it advertised exactly what must happen: a new instance is made. Not that I am wedded to it, if there is ample precedent for "create".
One consistent scheme that I follow is that functions that do not have side effects and just return something are called "something()", and functions that do something are verbs, like create_stream.
although it might be better to move to net::create_Y( p ) or even to Y( p ) and reference semantics under the hood.
The approach I took (based on shared_ptr <g>) allowed for clear ownership, copy and layering semantics. In other words:
net::stream_ptr stream = ...;
stream = ssl::new_client_stream(stream);
At this point, the new SSL stream impl drives the original stream which it can rely upon to exist until it is done with it. From the user's point of view, the SSL stream is now _the_ stream.
Yes, I understand. The question is whether to emphasize the pointers. C++ people usually like the _ptr notation. Your example above is a good argument in favor of create_stream( address ) for consistency with create_ssl_stream( stream ) (or however it ends up being called.) An OpenSSL stream would make a terrific example, by the way. I've recently dealt with one of these, too. ;-)
The threading choices are certainly the most complex. Having said that, these are mostly implementation choices, not interface choices though one might need to extend the interface to support ideas like the one you propose. The complexity really shows up when one wants to write protocol libraries. For each choice presented to the network user, the protocol library probably needs to provide similar support.
For example, an HTTP library would need to provide sync and async under my proposal. Adding more styles of notification to the network layer probably makes this job more difficult. Not to pass final judgment here; just a consideration.
I'm not sure. net::poll is specifically intended to preserve the internal structure of your library. You only need to defer dispatching the callbacks until net::poll is called (unless net::async_poll is in effect.) It also gives you the freedom to make net::poll the primary model since it maps very naturally to select/epoll. But you aren't forced to do that.
In my work on this, I attacked thread affinity outside the network layer for the most part. In particular, what you called net::poll() I had as a different object that handled a queue of boost::function<>-like objects. That way, the main thread was not forced to be only a network thread.
I do see room for this approach in cases where the main thread wants to be a network-only thread and the queue approach feels too heavy. I think that this would fit in my proposal as a different concrete network-derived class as long as the abstraction is the same: sync and async (with other rules TBD).
No, I don't believe that you need another network class for that. :-)
Whether the library uses multiple threads under the hood, or how many, is implementation defined, even if async_poll is not called; this depends on which model delivers the best performance on the specific platform.
While I agree to some extent, the user must know the context in which callbacks will be made. Or at least, they need to know a set of rules to follow that will keep their code working for all implementations.
That's the whole idea. When using net::poll the context is the thread that called net::poll. When using async_poll (your current modus operandi), the context is an unspecified background thread. The user is in control of the context.
Forcing a single-threaded view of the network on the user imposes its own penalty. At the top-most level, the programmer should have some (hopefully small<g>) number of concrete network objects to pick amongst. Once chosen, all mid-level libraries need to know that their expectations of behavior are still going to be met. At the bottom, we do what seems best to implement the abstraction on a given platform.
I don't understand this paragraph, sorry.
The functionality of write_later should be achievable with a call to async_write with size 0; write_later can be dropped, too.
I debated this myself, but decided to apply the axiom "don't parameterize behavior" and ended up with the x_later approach. Under the covers, they will be very much like async_x with no buffers on which to operate.
Yes, makes sense. I view the problem from the other side: what are the semantics of an asynchronous read/write with size 0? Answer: exactly the same as those of read/write_later. Question: why keep *_later and duplicate functionality then?
async_read should not take a buffer; instead, the callback should receive a pointer to a buffer managed by the library that is guaranteed to be valid for the duration of the callback. (Not by default, at least.)
async_write (by default) should not assume that the passed buffer stays valid after async_write returns.
Rationale: buffer management is a pain with multiple callbacks active ;-)
Others I believe have commented on this, but my take is that this level should not make assumptions about the ultimate destination of data. This approach, would in many cases, force data to be copied "one more time". I agree that this can be a painful process, but perhaps this is where higher level abstractions come into play, or possibly (as you suggest) optional additional features for this abstraction. Maybe passing (NULL,0) for I/O requests, or some sort of set_buffer() mechanism. Don't know.
Passing NULL is good enough for async_read, but the same can't be used with async_write to choose between copy/trust-the-buffer-will-be-there semantics. Again, I'm not opposed to manual buffer management. It has its uses. However in my experience so far the buffer management isn't much fun, is error prone, and in the common case is not optimized to be more efficient than the automatic case. In some cases a naive/straightforward manual buffer management scheme can be significantly less efficient.

Peter Dimov wrote:
Don G wrote: Under the address-centric model, the form of the address determines the network to use. The library is free to maintain several network objects under the hood, ...
I also prefer the address centric approach since it feels more natural. Also I wonder what would be the hooks to register new network implementations and provide the notification mechanisms for those.
Why I prefer tcp:/host:port instead of scheme://host? Let's get back to enhancing std::fopen in a backward-compatible way. I'd expect fopen on http://www.example.com to return the data stream obtained by the corresponding GET query, for obvious reasons.
This is a good example on a very specific connector that posts a GET operation to initialize the stream for the handler in this case to treat an url as a file.
All of these are forcing TCP/IP (IPv4 even<g>) concepts to the user. The central idea of the abstraction I proposed is that "datagram" and "stream" are the behavior-bundles to which one should write protocols, not TCP/UDP or sockets. The notion of loopback and broadcast can carry over from one network type to another, but these textual forms do not.
Yes; another manifestation of the network issue. I agree that in a network-centric design your approach is preferable. In an address-centric design, the TCP/IP4 broadcast address is not portable between networks.
Is it a problem that the special or specific adresses is represented by different textual representations for different networks? I guess a textual representation could be devised that makes these look the same with some psuedo notation. Like broadcast tcp:/[broadcast]:1234, tcp6:/[broadcast]:1234, local tcp:/[local]:1234, tcp6:/[local]:1234, any tcp:/[any]:1234, tcp6:/[any]:1234 this would i guess alleviate some of the eventual problems. /Michel

Hi Peter,
The network root issue is at, hm, the root of our disagreement.
Indeed :)
In many of my uses, I have multiple network-derived types in use at the same time (serial, HTTP tunnel, TCP/IP).
The question is why do you need multiple network objects at the client side.
My sad story: Over the past couple years I've had the joy of writing some ActiveX controls that run in IE (please don't shoot; they were the good kind<g>). In doing so, I have been best served by not having any active stuff running behind the scenes, especially threads. The desire to keep a library of pure objects has flavored my choices here. When an ActiveX object dtor is called, I want _all_ activity related to that instance to stop. Other instances may be held by other threads and I don't want all interfaces to be thread-safe. Anyway, that is a big part of why I avoid designs that have significant apparatus globally managed.
Under the address-centric model, the form of the address determines the network to use. The library is free to maintain several network objects under the hood, ...
There is the question of what network types does an application need. It is not likely they need all of them. So, the app must pre-register the types it wants and only those address forms will work. I hope I said this, but perhaps I didn't<g>: I think an address-to-network mapping is reasonable. I just don't want that to be the only way to fly. Given that, I suppose one could use "tcp:http://www.boost.org" in the spirit of URL: everything to the right of ":" is the scheme-specific-part. :)
In general, data-driven designs are much more flexible. I can read
server.address=tcp4:/www.example.com:5757
from a configuration file and connect. The line might have been
server.address=com1:/57600,n,8,1
but my application doesn't care, as long as someone at the other end responds according to the appropriate server protocol.
Repeat from above: I do see merit in the general approach.
Under the explicit network model, I'll need to duplicate the scheme-to-network logic myself. Not that a map<string, network_ptr> is that much work; there's just no benefit.
For the record, I've never needed such a map. There are so few places that accept or connect, and then, so few different network objects, that it just hasn't happened. For example: void tcp_tab_page::on_click_go () { net::address_ptr addr = tcp_->new_address(url); session_mgr->start(addr); } The method existed in a "TCP" page of a tabbed dialog, which of course knows that connections proceed over TCP. It created an address object and called into network-agnostic code to proceed.
The "self contained address as a string" model also has another advantage: it allows you to take a legacy design such as std::fopen and enhance it with network capability in a backward-compatible way.
While I still agree with the jist of your argument, I am not so sure everyone would appreciate their apps growing by sizeof(http_lib) + sizeof(ftp_lib) + sizeof(uucp_lib) + sizeof(gopher_lib) + ... to support this approach. :)
I'm not sure I understand. A communication port is a port, not a network. I've recently dealt with one and it's very much like a socket. :-)
You are quite right. I was not giving you my context here. In my use of serial communications, we did provide an entire network over that line including muxing streams, emulating datagrams, etc.. Underneath all that was the true nature of the serial line. So, at one level, the serial line is just a stream. Over that stream, one can layer an entire network model, including "me" and "thee" addressing. :)
The form above is a valid URI/URL, by the way. The single slash means that the text after the scheme is application-dependent and does not follow the host:port/path?query#anchor format.
Yes, see my comment above.
Why I prefer tcp:/host:port instead of scheme://host? Let's get back to enhancing std::fopen in a backward- compatible way. I'd expect fopen on http://www.example.com to return the data stream obtained by the corresponding GET query, for obvious reasons.
I'm not sure I understand the difference you are drawing between ":/" and "://". The way I read the URL/I syntax, many options will work. Here is one that would be unambiguous - ipv4:tcp:http://www.boost.org ipv4 denotes network choice; scheme-specifics follow: tcp denotes stream as opposed to datagram, and again, details follow http denotes how to talk say vs. HTTPS or FTP - ipv4:http://www.boost.org:80 tcp can be assumed as long as protocol doesn't have udp as well (like echo or discard<g>).
I think that the UDP broadcast address should be represented by udp:/0.0.0.0, and the TCP loopback should be tcp:/127.0.0.1, as usual. But I may be wrong.
All of these are forcing TCP/IP (IPv4 even<g>) concepts to the user. The central idea of the abstraction I proposed is that "datagram" and "stream" are the behavior-bundles to which one should write protocols, not TCP/UDP or sockets. The notion of loopback and broadcast can carry over from one network type to another, but these textual forms do not.
Yes; another manifestation of the network issue. I agree that in a network-centric design your approach is preferable. In an address-centric design, the TCP/IP4 broadcast address is not portable between networks.
Indeed, my design is network-centric. Its goal is to provide a complete (enough<g>) encapsulation of network behaviors and features that one can write higher level concepts or protocol cleanly. I don't want to redefine the semantics of networking. The programmer using this layer is "network programming on purpose". Going up the food chain beyond this level is good and expected. :)
One consistent scheme that I follow is that functions that do not have side effects and just return something are called "something()", and functions that do something are verbs, like create_stream.
Sounds reasonable. Using this approach, I constantly stumble on this kind of thing (just did today in fact<g>): void hierarchy_object::foo () { hierarchy_object * parent = parent(); // oops! } This can be avoided by using get_parent() which is where I have gone since writing good 'ol hierarchy_object. However, my audience here is different and I will be assimila..., er, adapt. ;)
Your example above is a good argument in favor of
create_stream( address )
for consistency with
create_ssl_stream( stream )
(or however it ends up being called.)
Except that one might then expect create_stream(stream) to be valid, which it is not. :)
An OpenSSL stream would make a terrific example, by the way. I've recently dealt with one of these, too. ;-)
This kind of substitutability is the essence of what I am working for in this design and OpenSSL is part of my plan, but it is a bit higher level than were I am currently. ;)
I'm not sure. net::poll is specifically intended to preserve the internal structure of your library. You only need to defer dispatching the callbacks until net::poll is called (unless net::async_poll is in effect.)
I am not sure I understand your async_poll suggestion (sorry about that<g>). I agree that net::poll() would fit with a common desire to have single threaded network programs, but might complicate things where the program is a GUI. Integrating with the GUI loop is a study in compromise (at least for Windows).
It also gives you the freedom to make net::poll the primary model since it maps very naturally to select/epoll. But you aren't forced to do that.
I like the idea of net::poll() for some uses, but it doesn't fit what I often need (GUI integration). It does fit with select/epoll especially on platforms where the number of objects that can go in an fd_set is > 64. I don't know if it was clear from previous posts, but I do have in mind a higher level net::poll() like library. The reason I prefer that approach to net::poll() is that not all async activities are network related. A timer comes to mind here. Also, the GUI event loop. Also, just plain "do this next, but not now" queuing. I would want the ability to have all that deliver out of the same pump as network completion callbacks. My current proposal does not include explicit support for this. With the general async facility I am describing, net::poll() would be relegated to only some pure network-only, apps that absolutely insist on the select w/no threads approach.
I do see room for this approach in cases where the main thread wants to be a network-only thread and the queue approach feels too heavy. I think that this would fit in my proposal as a different concrete network-derived class as long as the abstraction is the same: sync and async (with other rules TBD).
No, I don't believe that you need another network class for that. :-)
I agree, but others don't. Some folks don't want any background threads; just a single thread doing one uber select/epoll call (again, for platforms that can handle it). The only way I can see to accommodate that is a different concrete network object. The ones I would write initially would probably not fit this desire, but again, the interface and behavior contract probably can.
While I agree to some extent, the user must know the context in which callbacks will be made. Or at least, they need to know a set of rules to follow that will keep their code working for all implementations.
That's the whole idea. When using net::poll the context is the thread that called net::poll. When using async_poll (your current modus operandi), the context is an unspecified background thread. The user is in control of the context.
I am specifically concerned with the hypothetical protocol library author here more than the application author. If I want to write an SSL stream, for example, I need to know how my use of the real stream will behave and what I am allowed to do in the callback context. While the application layer may make the final call, that call cannot invalidate the contract assumed by the protocol library author.
Forcing a single-threaded view of the network on the user imposes its own penalty. At the top-most level, the programmer should have some (hopefully small<g>) number of concrete network objects to pick amongst. Once chosen, all mid-level libraries need to know that their expectations of behavior are still going to be met. At the bottom, we do what seems best to implement the abstraction on a given platform.
I don't understand this paragraph, sorry.
Sorry back at you for that confusing paragraph. This is basically the same concern as above, about protocol libraries. - At the app layer, the developer sometimes wants to choose single threaded(!) vs. don't care but deliver it on this thread please vs. use whatever thread context is best + use my quad CPU's please + minimize context switches (aka "give me your best shot, I can take it"). - Middle level protocol libraries (like SSL or HTTP) must not be presented with different behaviors from the abstract interfaces. If they followed the rules (TBD), they should continue to work regardless of a choice made by the application developer. - On the bottom, someone gets to (re)implement this abstraction in various ways for different platforms and/or different run-time models as necessary.
Yes, makes sense. I view the problem from the other side: what are the semantics of an asynchronous read/write with size 0? Answer: exactly the same as those of read/write_later.
Or one could answer: illegal. :)
Question: why keep *_later and duplicate functionality then?
Clarity? Grep-ability? Perhaps. It's a bit on the fluffy side<g>, so I won't loose any sleep either way. My inclination would be to make read w/size=0 illegal because it might catch an error closer to the origin and read_later() would be used where that was your goal: not now, later.
Passing NULL is good enough for async_read, but the same can't be used with async_write to choose between copy/trust-the-buffer-will-be-there semantics.
True enough.
Again, I'm not opposed to manual buffer management.
Glad to (re)hear it. :)
It has its uses. However in my experience so far the buffer management isn't much fun, is error prone, and in the common case is not optimized to be more efficient than the automatic case. In some cases a naive/straightforward manual buffer management scheme can be significantly less efficient.
I would be happy to entertain ideas on how to provide both kinds of buffer management, especially if there is a way to eliminate as much cost as possible (in terms of "if checks" and code linkage) when automatic is never used. Again, thanks for all the time and thought energy. Best regards, Don __________________________________________________ Do You Yahoo!? Tired of spam? Yahoo! Mail has the best spam protection around http://mail.yahoo.com

Don G wrote:
Under the address-centric model, the form of the address determines the network to use. The library is free to maintain several network objects under the hood, ...
There is the question of what network types does an application need. It is not likely they need all of them. So, the app must pre-register the types it wants and only those address forms will work.
I would prefer for a fixed set of networks (mainly ip/ipv6 to be available from start maybe lazily created on first reference). And registering a network creation function to the registry would let the registry use weak references to networks and all objects spawned from a network holds strong references in that way the network is destroyed when the last object spawned from it goes away. The network creation function could be passed along with an address so an address could be used across dll boundaries where each dll has its own registry.
For the record, I've never needed such a map. There are so few places that accept or connect, and then, so few different network objects, that it just hasn't happened. For example:
void tcp_tab_page::on_click_go () { net::address_ptr addr = tcp_->new_address(url); session_mgr->start(addr); }
The method existed in a "TCP" page of a tabbed dialog, which of course knows that connections proceed over TCP. It created an address object and called into network-agnostic code to proceed.
Wouldn't you need to store the settings and then start the applications from the stored settings and isn't the map needed then?
The "self contained address as a string" model also has another advantage: it allows you to take a legacy design such as std::fopen and enhance it with network capability in a backward-compatible way.
While I still agree with the jist of your argument, I am not so sure everyone would appreciate their apps growing by sizeof(http_lib) + sizeof(ftp_lib) + sizeof(uucp_lib) + sizeof(gopher_lib) + ... to support this approach. :)
They could be dynamically loaded.
Why I prefer tcp:/host:port instead of scheme://host? Let's get back to enhancing std::fopen in a backward- compatible way. I'd expect fopen on http://www.example.com to return the data stream obtained by the corresponding GET query, for obvious reasons.
I'm not sure I understand the difference you are drawing between ":/" and "://". The way I read the URL/I syntax, many options will work. Here is one that would be unambiguous
- ipv4:tcp:http://www.boost.org ipv4 denotes network choice; scheme-specifics follow: tcp denotes stream as opposed to datagram, and again, details follow http denotes how to talk say vs. HTTPS or FTP
- ipv4:http://www.boost.org:80 tcp can be assumed as long as protocol doesn't have udp as well (like echo or discard<g>).
I think the syntax of the address should be kept simple to use, explain and parse and having several ways of expressing the same thing in general isn't a good idea. And in that case we also would need canonicalization to be able to compare the addresses. Ie ipv4:tcp:http://www.boost.org shold be equal to ipv4:http://www.boost.org:80. But i guess they both could be canonicalized to something like tcp:/66.35.250.210:80.
I'm not sure. net::poll is specifically intended to preserve the internal structure of your library. You only need to defer dispatching the callbacks until net::poll is called (unless net::async_poll is in effect.)
I am not sure I understand your async_poll suggestion (sorry about that<g>).
As I understood it async_poll would start up a reasonable set of threads starting doing net::poll and dispatching events for the library in its own thread pool without the user having control over the pool.
I agree that net::poll() would fit with a common desire to have single threaded network programs, but might complicate things where the program is a GUI. Integrating with the GUI loop is a study in compromise (at least for Windows).
I see no reason whatso ever to have a windows gui program using the library to be single threaded. It would be easy enough to spawn a single io thread polling and handling the notifications and posting results to be processed by the gui. I have only gotten into trouble trying to integrate this kind of polling in the eventloop of a gui application and always moved to an simple thread model posting notifications to the gui thread.
I like the idea of net::poll() for some uses, but it doesn't fit what I often need (GUI integration). It does fit with select/epoll especially on platforms where the number of objects that can go in an fd_set is > 64.
There still is some arbitrary limit albeit higher isn't there. The FD_SETSIZE can be set to a large value before including winsock (althoug some lsp makes presumptions about the size).
I agree, but others don't. Some folks don't want any background threads; just a single thread doing one uber select/epoll call (again, for platforms that can handle it). The only way I can see to accommodate that is a different concrete network object. The ones I would write initially would probably not fit this desire, but again, the interface and behavior contract probably can.
I don't really get why that couldn't that be accomodated whitout another network class?
Passing NULL is good enough for async_read, but the same can't be used with async_write to choose between copy/trust-the-buffer-will-be-there semantics.
A buffer lifetime flag for the read case?
I would be happy to entertain ideas on how to provide both kinds of buffer management, especially if there is a way to eliminate as much cost as possible (in terms of "if checks" and code linkage) when automatic is never used.
I really don't see how some if checks and linking against std::vector<char> could bear any costs in terms of performance of a network library. /Michel

Hi Michel, Don G wrote:
There is the question of what network types does an application need. It is not likely they need all of them. So, the app must pre-register the types it wants and only those address forms will work.
I would prefer for a fixed set of networks (mainly ip/ipv6 to be available from start maybe lazily created on first reference). And registering a network creation function to the registry would let the registry use weak references to networks and all objects spawned from a network holds strong references in that way the network is destroyed when the last object spawned from it goes away. The network creation function could be passed along with an address so an address could be used across dll boundaries where each dll has its own registry.
I have to admit that I avoid DLL/.so's whenever possible, so this approach is not one I had considered. I don't see any problem with the application registering network objects inside main or near there<g>, that way only the networks one needs to support are linked.
Wouldn't you need to store the settings and then start the applications from the stored settings and isn't the map needed then?
At most, the network settings we store are some port overrides. So we just read them as we create the appropriate network object. Following that, we have "mTcp", "mSerial", etc. and which is needed is known by context. This is just my experience. A map would come into play with the address-tells-all approach. More on this in my reply to Peter. :)
While I still agree with the jist of your argument, I am not so sure everyone would appreciate their apps growing by sizeof(http_lib) + sizeof(ftp_lib) + sizeof(uucp_lib) + sizeof(gopher_lib) + ... to support this approach. :)
They could be dynamically loaded.
True, but they can also be created by the application as well. The application developer (as opposed to the library developer) generally knows what kinds of pieces are needed and can register them in code. Or use some more elaborate mechanism such as dynamic loading, but that just pushes the "link" out to distribution building time. The app developer must still know.
I think the syntax of the address should be kept simple to use, explain and parse and having several ways of expressing the same thing in general isn't a good idea. And in that case we also would need canonicalization to be able to compare the addresses. Ie ipv4:tcp:http://www.boost.org shold be equal to ipv4:http://www.boost.org:80. But i guess they both could be canonicalized to something like tcp:/66.35.250.210:80.
I agree that one form is desirable, and the simpler the better. Peter talks more about this, so I will respond over there.
I agree that net::poll() would fit with a common desire to have single threaded network programs, but might complicate things where the program is a GUI. Integrating with the GUI loop is a study in compromise (at least for Windows).
I see no reason whatso ever to have a windows gui program using the library to be single threaded. It would be easy enough to spawn a single io thread polling and handling the notifications and posting results to be processed by the gui. I have only gotten into trouble trying to integrate this kind of polling in the eventloop of a gui application and always moved to an simple thread model posting notifications to the gui thread.
The desire to eliminate threads from code is not restricted to servers<g>. There is a long history of using the windows message queue (via PostMessage) for handling networking that pre-dates threads just as select and signal-driven I/O pre-date threads for Unix. [FWIW: the "right" way is PostMessage-to-hidden-window, not splicing up the GetMessage/DispatchMessgae loop, and no polling is required] I see this as a perfectly reasonable approach, especially when the needs are modest. Even a complex GUI app (at least for X, Windows and Mac) is typically single-threaded and asynchronous. Network notification can be fairly simply added to such a program w/o forcing multi-threaded coding. Again, this is the "general async" stuff that is not in the network interfaces I have proposed.
I like the idea of net::poll() for some uses, but it doesn't fit what I often need (GUI integration). It does fit with select/epoll especially on platforms where the number of objects that can go in an fd_set is > 64.
There still is some arbitrary limit albeit higher isn't there. The FD_SETSIZE can be set to a large value before including winsock (althoug some lsp makes presumptions about the size).
I hadn't noticed that tid bit in select's docs before, but you are correct: it can be set higher, and it probably could cause issues with some drivers. On a side note: the product I work on a the job has run into issues related to specific network drivers when using DuplicateHandle on a socket as prescribed in MSDN, so this kind of issue is real. :(
I agree, but others don't. Some folks don't want any background threads; just a single thread doing one uber select/epoll call (again, for platforms that can handle it). The only way I can see to accommodate that is a different concrete network object. The ones I would write initially would probably not fit this desire, but again, the interface and behavior contract probably can.
I don't really get why that couldn't that be accomodated whitout another network class?
I am presupposing that one network impl will make heavy use of worker threads, and deliver callbacks as I described elsewhere. The same impl cannot also be fully single threaded. :) Of course, what I have been saying as well is that one can use the general async facility to get callbacks queued to a specific (single) thread, thereby hiding the worker threads. The issue is that some don't find this detail insignificant: they want to eliminate the worker threads altogether.
I would be happy to entertain ideas on how to provide both kinds of buffer management, especially if there is a way to eliminate as much cost as possible (in terms of "if checks" and code linkage) when automatic is never used.
I really don't see how some if checks and linking against std::vector<char> could bear any costs in terms of performance of a network library.
I'm just curious to see if someone has some simple ideas on automatic buffering that doesn't require massive overhead when unused. It wasn't that all if-checks had to be eliminated, or that some extra code might get linked w/o being used. What I want to avoid is large chunks coming in via iostream (for example) that in many uses will be dead code. Best, Don __________________________________________________ Do You Yahoo!? Tired of spam? Yahoo! Mail has the best spam protection around http://mail.yahoo.com

Hi Don! Don G wrote:
I would prefer for a fixed set of networks (mainly ip/ipv6 to be available from start maybe lazily created on first reference). And registering a network creation function to the registry would let the registry use weak references to networks and all objects spawned from a network holds strong references in that way the network is destroyed when the last object spawned from it goes away. The network creation function could be passed along with an address so an address could be used across dll boundaries where each dll has its own registry.
I have to admit that I avoid DLL/.so's whenever possible, so this approach is not one I had considered. I don't see any problem with the application registering network objects inside main or near there<g>, that way only the networks one needs to support are linked.
I still think that at least ip/ipv6 should be on by default (and implemented in such a way that the lazilly initialize themselves so the cost is low of having them active at all times). I have no problem with user registering the some odd net providers or their own user defined. One should be aware that several dlls could load the libary simoltaneously and the library should preferably cater for that or at least not break down. Maybe just set some rules.
Wouldn't you need to store the settings and then start the applications from the stored settings and isn't the map needed then?
At most, the network settings we store are some port overrides. So we just read them as we create the appropriate network object. Following that, we have "mTcp", "mSerial", etc. and which is needed is known by context. This is just my experience. A map would come into play with the address-tells-all approach. More on this in my reply to Peter. :)
So you have a switch somewhere like switch(transport) { case serial: return mSerial; case tcp; return mTcp; } Or something to that matter that corresponds to the map? Or maybe you don't provide the same services/protocols over two networks, in that case the context would be clear from my_fancy_serial_protocl_handler use mSerial and from my_cool_tcp_handler us mTcp?
I see no reason whatso ever to have a windows gui program using the library to be single threaded. It would be easy enough to spawn a single io thread polling and handling the notifications and posting results to be processed by the gui. I have only gotten into trouble trying to integrate this kind of polling in the eventloop of a gui application and always moved to an simple thread model posting notifications to the gui thread.
The desire to eliminate threads from code is not restricted to servers<g>. There is a long history of using the windows message queue (via PostMessage) for handling networking that pre-dates threads just as select and signal-driven I/O pre-date threads for Unix. [FWIW: the "right" way is PostMessage-to-hidden-window, not splicing up the GetMessage/DispatchMessgae loop, and no polling is required]
That was the method i was referring to when saying post notifiactions to the gui thread. And of course eliminating threads isn't important for regular apps as well. But one additional io thread and a hidden window and some post message calls and all your code is serialized and a very simple model without chopping up the event loop.
I see this as a perfectly reasonable approach, especially when the needs are modest. Even a complex GUI app (at least for X, Windows and Mac) is typically single-threaded and asynchronous.
Yes there hasn't been much sense in threading gui apps that heavily and keep the model simple.
I hadn't noticed that tid bit in select's docs before, but you are correct: it can be set higher, and it probably could cause issues with some drivers. On a side note: the product I work on a the job has run into issues related to specific network drivers when using DuplicateHandle on a socket as prescribed in MSDN, so this kind of issue is real. :(
I don't envy you having go through that I have luckily been able to avoid these kinds of problems.
I am presupposing that one network impl will make heavy use of worker threads, and deliver callbacks as I described elsewhere. The same impl cannot also be fully single threaded. :) Of course, what I have been saying as well is that one can use the general async facility to get callbacks queued to a specific (single) thread, thereby hiding the worker threads. The issue is that some don't find this detail insignificant: they want to eliminate the worker threads altogether.
I dont think threads can't be avoided as I see it at least not when hitting an internal limit of the notfication mechanism choosen eg 64 for windows and 1024 for Linux FC3. Otherwise polling would have to be done in some roundrobin way over the available handle sets and that would introduce latency.
I'm just curious to see if someone has some simple ideas on automatic buffering that doesn't require massive overhead when unused. It wasn't that all if-checks had to be eliminated, or that some extra code might get linked w/o being used. What I want to avoid is large chunks coming in via iostream (for example) that in many uses will be dead code.
Agreed. I think eg that the stream_buf interface is to complex and arcane to fit at this level even if eg Jeff Garland proposes it. /Michel

Hi Michel, Mixing two replies here, just for fun :)
So basically layer 0 should support this portable subset.
Well, I would mostly agree, but I don't think everyone will. One of the goals several people have expressed is the ability to use level 0 wrappers in non-portable ways.
Isn't that just saying the handle should be accessible. And you should be able to attach and release an underlying handle from the objects at this level.
I believe so, at level 0 - the socket wrapper classes. As you probably noticed, the interfaces I propose do not expose this handle and indeed they cannot as it may/probably won't be what is expected if it even exists.
I still think that at least ip/ipv6 should be on by default (and implemented in such a way that the lazilly initialize themselves so the cost is low of having them active at all times). I have no problem with user registering the some odd net providers or their own user defined. One should be aware that several dlls could load the libary simoltaneously and the library should preferably cater for that or at least not break down. Maybe just set some rules.
I guess I am still focused on the user creating network objects and using them directly since that is how I use this where it was born. I will have to think further on this because, while this textual address centric approach sounds reasonable, I am concerned by so much happening without direct control. In some environments (from my experiences with plug-ins and ActiveX mostly), it is important to know when things can be started and at what point they must end. The lazy init is good for starting, but the stopping is more troublesome. I cannot tell you how frustrating it was to debug threading issues during global destruction! :( Also, the mapping from user entered form into some of the cryptic forms we have discussed would be more tedious to me than just using the right object. ;) Even in config files this seems a bit hard to understand and document. As a user of a product, I would not react well to this stuff. Imagine configuring your proxy server setting: proxy=tcp:/proxy.boost.com:80 I would expect it to be: proxy=http://proxy.boost.com Pushing the multi-network detail to the user seems like a mixed blessing. Like I said: more thinking...
So you have a switch somewhere like switch(transport) { case serial: return mSerial; case tcp; return mTcp; }
Or something to that matter that corresponds to the map? Or maybe you don't provide the same services/protocols over two networks, in that case the context would be clear from my_fancy_serial_protocl_handler use mSerial and from my_cool_tcp_handler us mTcp?
Most of our UI's make the context obvious for the user's benefit and so makes the code switch-less as well. A common sequence goes like this: - app init creates mTcp and sets some stuff in it - user says, dial 555-1212 on modem X, so we create mSerial (just one modem dial at a time<g>) - user goes to the TCP page and enters an address and hits the "Do Foo" button. - because of the context, we know that mTcp is the one to use. - user goes back to dial page and presses the "Doo Foo" button. - again by context we use mSerial. The contexts are usually different methods invoked by the GUI framework based on the current page the user is working with. We try to avoid having the user enter arcane syntax addresses, so "Do Foo" is where we establish the scheme/protocol as well. The map would be important in this kind of app if the user entered such addresses directly. It would also come up if certain things were in config files (they haven't in my experience). Other kinds of apps may run into this if they need to expose multiple networks at that level.
That was the method i was referring to when saying post notifiactions to the gui thread. And of course eliminating threads isn't important for regular apps as well. But one additional io thread and a hidden window and some post message calls and all your code is serialized and a very simple model without chopping up the event loop.
Yes. The "generic async library" I keep mentioning is where this is handled w/o the user A) being called in the wrong thread or B) the user manually doing any posting. In pseudo code: class foo { public: foo (net::stream_ptr s) : strm_(s) { ch_.open(); strm_->async_connect(ch_.bind_async_call(&on_connect)); } // implicit: ~foo () { ch_.close(); } private: net::stream_ptr strm_; channel ch_; void on_connect () // called in same thread as ctor { strm_->async_write("Hello world!", ..., ch_.bind_async_call(&on_written)); } void on_written () // also called in ctor's thread { ... } }; There is a lot that could be explained about channel. I posted this some weeks ago as Asynchronicity or something like that, but I haven't focused much on it. The current thread (app main in this case) must cooperate for the channel to open; the thread must declare its intent to deliver queued messages (boost::function<> objects) by creating a different object (I called it a nexus in my post, but that was just a first crack at renaming the concept from the code at work). The channel class exists to allow ~foo (or some other impulse) to cancel delivery of any messages that might be in the queue. Imagine if "delete foo" happened with the call to on_connect already in the queue. Yuk. So, channel solves this problem. The channel approach does complicate usage in this case, and it could be improved by exposing the concept from the network layer: class foo { public: foo (net::stream_ptr s) : strm_(s) { strm_->set_async_context(); // to this thread strm_->async_connect(&on_connect); } private: net::stream_ptr strm_; void on_connect () // same thread as set_async_context { strm_->async_write("Hello world!", ..., &on_written); } void on_written () // also by set_async_context thread { ... } }; This just adds some (useful<g>) coupling between "net" and the general async library.
I dont think threads can't be avoided as I see it at least not when hitting an internal limit of the notfication mechanism choosen eg 64 for windows and 1024 for Linux FC3. Otherwise polling would have to be done in some roundrobin way over the available handle sets and that would introduce latency.
Without threads the network would have a capacity limit of FD_SETSIZE, but even with threads the system has a limit. I found it trying to stress test my thread pool solution<g>. So, if the capacity limit of FD_SETSIZE is acceptable for the application, and threads are to be avoided, then I can see their argument. I think it is a bit premature to form conclusions (without measuring/optimizing), but it is a valid gut feel. Best, Don __________________________________________________ Do You Yahoo!? Tired of spam? Yahoo! Mail has the best spam protection around http://mail.yahoo.com

Don G wrote:
Also, the mapping from user entered form into some of the cryptic forms we have discussed would be more tedious to me than just using the right object. ;) Even in config files this seems a bit hard to understand and document. As a user of a product, I would not react well to this stuff. Imagine configuring your proxy server setting:
proxy=tcp:/proxy.boost.com:80
I would expect it to be:
proxy=http://proxy.boost.com
Except that many proxies are on 8080 or 3128. I see that you are slowly moving away from your network-centric scheme after you've already convinced me of its merits. ;-)

Don G wrote:
Without threads the network would have a capacity limit of FD_SETSIZE, but even with threads the system has a limit. I found it trying to stress test my thread pool solution<g>. So, if the capacity limit of FD_SETSIZE is acceptable for the application, and threads are to be avoided, then I can see their argument.
FD_SETSIZE only applies to select, and nobody uses select except in "quick and dirty" servers (as I did recently). The idea of an explicit poll() call is to allow the implementation to use a single-threaded dispatch mechanism (select/epoll/kqueue), if one is available. Without poll() you are forced to use worker thread(s) and a callback queue to support a single-threaded application, and this can be considerably less efficient than epoll/kqueue (or select in the < FD_SETSIZE case) (in theory). Of course, if the threaded model can be demonstrated to achieve comparable performance, there would be no need to complicate the interface with poll().

Don G wrote:
So basically layer 0 should support this portable subset.
Well, I would mostly agree, but I don't think everyone will. One of the goals several people have expressed is the ability to use level 0 wrappers in non-portable ways.
Isn't that just saying the handle should be accessible. And you should be able to attach and release an underlying handle from the objects at this level.
I believe so, at level 0 - the socket wrapper classes. As you probably noticed, the interfaces I propose do not expose this handle and indeed they cannot as it may/probably won't be what is expected if it even exists.
Are you agreeing that we should have a level 0 close to the socket api supporting a portable subset or are you giving in to pressure ;).
I guess I am still focused on the user creating network objects and using them directly since that is how I use this where it was born. I will have to think further on this because, while this textual address centric approach sounds reasonable, I am concerned by so much happening without direct control. In some environments (from my experiences with plug-ins and ActiveX mostly), it is important to know when things can be started and at what point they must end. The lazy init is good for starting, but the stopping is more troublesome. I cannot tell you how frustrating it was to debug threading issues during global destruction! :(
I can see the troubles you are afraid of. But having weak_refernces in the registry would get the objects destroyed as soon as there is no strong references to it, so if you have destroyed all objects associated with a network you should be home free. But I guess some nasty circual references or whatever could keep the object alive. Btw, arent you using shared ptr for the network object so essentially you have the same problem today?
Also, the mapping from user entered form into some of the cryptic forms we have discussed would be more tedious to me than just using the right object. ;) Even in config files this seems a bit hard to understand and document. As a user of a product, I would not react well to this stuff. Imagine configuring your proxy server setting:
proxy=tcp:/proxy.boost.com:80
I would expect it to be:
proxy=http://proxy.boost.com
It would be quite easy to write a function that maps uris to our address notation. And usually you provide some kind of gui which handles this.
The contexts are usually different methods invoked by the GUI framework based on the current page the user is working with.
We try to avoid having the user enter arcane syntax addresses, so "Do Foo" is where we establish the scheme/protocol as well.
ok.
The map would be important in this kind of app if the user entered such addresses directly. It would also come up if certain things were in config files (they haven't in my experience). Other kinds of apps may run into this if they need to expose multiple networks at that level.
I think probalbly what you want to do, is to use the same protocol implementation over several different transports ie tcp/named pipes/serial or whatever and it's here the address and factory comes to play. I think as I have expressed before if you actually know the type you should be able to use concrete classes directly such as tcp_stream/accpetor/connector since they could expose more functionality than the generic net_stream.
Yes. The "generic async library" I keep mentioning is where this is handled w/o the user A) being called in the wrong thread or B) the user manually doing any posting.
In pseudo code:
class foo { public: foo (net::stream_ptr s) : strm_(s) { ch_.open(); strm_->async_connect(ch_.bind_async_call(&on_connect)); }
// implicit: ~foo () { ch_.close(); }
private: net::stream_ptr strm_; channel ch_;
void on_connect () // called in same thread as ctor { strm_->async_write("Hello world!", ..., ch_.bind_async_call(&on_written)); }
void on_written () // also called in ctor's thread { ... } };
There is a lot that could be explained about channel. I posted this some weeks ago as Asynchronicity or something like that, but I haven't focused much on it.
The current thread (app main in this case) must cooperate for the channel to open; the thread must declare its intent to deliver queued messages (boost::function<> objects) by creating a different object (I called it a nexus in my post, but that was just a first crack at renaming the concept from the code at work).
The channel class exists to allow ~foo (or some other impulse) to cancel delivery of any messages that might be in the queue. Imagine if "delete foo" happened with the call to on_connect already in the queue. Yuk. So, channel solves this problem.
Ok I think I need some more explaining in these concepts and some better names than nexus and channel. But basically you have an event_sink (channel) and a event_source(nexus) that posts completions to the sink. And if the event_sink goes out of scope notifications is'nt delivered. I have implemented this with having synchrounous/blocking close and deletion that would deque and completion whit a error stating the operation has been cancelled. But agreed it sometimes can be tricky to get this right and don't get spurious completions after deletion.
The channel approach does complicate usage in this case, and it could be improved by exposing the concept from the network layer:
class foo { public: foo (net::stream_ptr s) : strm_(s) { strm_->set_async_context(); // to this thread strm_->async_connect(&on_connect); }
private: net::stream_ptr strm_;
void on_connect () // same thread as set_async_context { strm_->async_write("Hello world!", ..., &on_written); }
void on_written () // also by set_async_context thread { ... } };
This just adds some (useful<g>) coupling between "net" and the general async library.
And also might have consequences on switching/overhead and performance I think.
Without threads the network would have a capacity limit of FD_SETSIZE, but even with threads the system has a limit.
Unfortunately computers are discrete and bounded machines with limits on the resources at all levels, but we do our best to hide these limits from the programmer and user so he can sail away and believe there is infinite memory and sockets available, dont' we ;). /Michel

Hi Michel,
Are you agreeing that we should have a level 0 close to the socket api supporting a portable subset or are you giving in to pressure ;).
Even my proposal used sockets internally, so yes, there should be a layer that minimally encapsulates them and does some smoothing of irregularities (in particular EAGAIN vs. EWOULDBLOCK). I just don't think that is the right layer to do any high level work.
I can see the troubles you are afraid of. But having weak_refernces in the registry would get the objects destroyed as soon as there is no strong references to it, so if you have destroyed all objects associated with a network you should be home free. But I guess some nasty circual references or whatever could keep the object alive.
This might solve the problem, but would produce thrashing if you transition from 1-to-0 a lot.
Btw, arent you using shared ptr for the network object
Yes (or its equivalent)
so essentially you have the same problem today?
No. The owners of the network object are ultimately part of an object hierarchy. The root object takes everything out with it.
It would be quite easy to write a function that maps uris to our address notation. And usually you provide some kind of gui which handles this.
Agreed. But it would also be "easy" to do what I propose ;) This is an issue that I would still like to ponder, but I don't believe it belongs below the app. Perhaps a library could implement this and be reusable, but the mechanism I propose should probably not be thinking this way. It seems to complicate object layering and things like that. At least to me.
I think probalbly what you want to do, is to use the same protocol implementation over several different transports ie tcp/named pipes/serial or whatever and it's here the address and factory comes to play.
Indeed. :)
I think as I have expressed before if you actually know the type you should be able to use concrete classes directly such as tcp_stream/accpetor/ connector since they could expose more functionality than the generic net_stream.
But the need to do so is not there. Things flow immediately into generic code once the text has been converted to address object. Again, if there is some TCP-specific stuff (eg: nagel), it can be in the abstract interface. If the user needs to know if something is supported or not and wants to take different actions depending, that is somewhat different. But again, I would like this to be part of the abstraction so that we don't effectively throw away its benefits. A consistent focus on abstracting behavior seems (to me) a better approach.
Ok I think I need some more explaining in these concepts and some better names than nexus and channel.
A very close analogy would be channel=HWND, nexus=GetMessage_loop.
But basically you have an event_sink (channel) and a event_source(nexus)
Not exactly. The nexus (bad name, I know) is the queue, not the source.
that posts completions to the sink. And if the event_sink goes out of scope notifications is'nt delivered.
Yes.
I have implemented this with having synchrounous/ blocking close and deletion that would deque and completion whit a error stating the operation has been cancelled. But agreed it sometimes can be tricky to get this right and don't get spurious completions after deletion.
Oh yes: tricky! ;) The general async library I must obviously propose soon<g> solves this with the concept of a channel. It is the proxy by which one answers the "are you still there?" question. The channel is connected to a queue (a "nexus") and that is the queue to which it will post boost::function<> objects in channel::async_call. This call is thread-safe. The channel user is decoupled from the entity that services the queue, which I also feel is very important to this solution. Anyway, without going any deeper (yet), I hope that clarifies things.
Unfortunately computers are discrete and bounded machines with limits on the resources at all levels, but we do our best to hide these limits from the programmer and user so he can sail away and believe there is infinite memory and sockets available, dont' we ;).
Yes, indeed. This sub-thread of the discussion was really an exploration of how to appease those that insist on the possibility of a single-threaded implementation (not just interface). I have no need for such an implementation, but am willing to explore the possibility as long as it doesn't sacrifice the larger objective of abstraction. Best regards, Don __________________________________________________ Do You Yahoo!? Tired of spam? Yahoo! Mail has the best spam protection around http://mail.yahoo.com

Don G wrote:
I can see the troubles you are afraid of. But having weak_refernces in the registry would get the objects destroyed as soon as there is no strong references to it, so if you have destroyed all objects associated with a network you should be home free. But I guess some nasty circual references or whatever could keep the object alive.
This might solve the problem, but would produce thrashing if you transition from 1-to-0 a lot.
I guess you could checkout a strong reference.
Btw, arent you using shared ptr for the network object
Yes (or its equivalent)
so essentially you have the same problem today?
No. The owners of the network object are ultimately part of an object hierarchy. The root object takes everything out with it.
Ok! I haven't really caught this, but essentilly the network object isn't a singleton or eqivalent it is an arbitrary grouping of streams/addresses/acceptors togeteher with some dispatching and threading mechanism? I saw your not with something like: network ptr = new ipv4_network; and network ptr = new ipv4_network_kq; This also explains why you think single threaded select should be one network, since the dispatching and threading policy is inherently specified by the network as well. Seems like quite a complex beast with a lot of responsibilities ;). But I guess the base network class only impements the factory parts and then holds references to dispatchers, pools and such things. One could also ask if <factory, dispatching, threading> policies isn't somewhat orthogonal and could be broken out and handed to the root object as policies/interfaces whatever. so you could specify something like network = new network_implementation<ip4, kqueue, single>; and from there on using the network as you propose, this could reduce some of the coding needed and could make it easier to test approaches.
I think as I have expressed before if you actually know the type you should be able to use concrete classes directly such as tcp_stream/accpetor/ connector since they could expose more functionality than the generic net_stream.
But the need to do so is not there. Things flow immediately into generic code once the text has been converted to address object. Again, if there is some TCP-specific stuff (eg: nagel), it can be in the abstract interface.
If possible I dont think tcp specific things should be in the generic interface, having an generic interface whit a lot of specifics for certain implementations. It clutters the interface both with the actual methods and additional method/methods to test for support for the feature.
Ok I think I need some more explaining in these concepts and some better names than nexus and channel.
A very close analogy would be channel=HWND, nexus=GetMessage_loop.
Ok.
But basically you have an event_sink (channel) and a event_source(nexus)
Not exactly. The nexus (bad name, I know) is the queue, not the source.
If my dim memory serves me a "nexus" is some kind of meeting point/connection link or similar?
I have implemented this with having synchrounous/ blocking close and deletion that would deque and completion whit a error stating the operation has been cancelled. But agreed it sometimes can be tricky to get this right and don't get spurious completions after deletion.
Oh yes: tricky! ;)
But also very needed if you use completion ports combined with user buffering since the os owns the buffers until the completion is dequed. So I had to bite the bullet ;).
The general async library I must obviously propose soon<g> solves this with the concept of a channel. It is the proxy by which one answers the "are you still there?" question. The channel is connected to a queue (a "nexus") and that is the queue to which it will post boost::function<> objects in channel::async_call. This call is thread-safe.
The channel user is decoupled from the entity that services the queue, which I also feel is very important to this solution. Anyway, without going any deeper (yet), I hope that clarifies things.
Yes thanks. /Michel

Don G wrote:
Yes. The "generic async library" I keep mentioning is where this is handled w/o the user A) being called in the wrong thread or B) the user manually doing any posting.
Sorry if this is covered somewhere, but would the user have any say in what goes down if an async_read and an async_write on the same socket complete more or less simultaneously? (In the presence of multiple worker threads) Sometimes the application logic calls for serializing the callbacks and sometimes it would be better to allocate each callback to a different thread, possibly allowing them to run in parallel. I have recently had to deal with synchronization/starvation issues caused by this kind of design (simultaneous pending async_read and _write on the same socket) in an application using Windows' OverlappedCompletionPort mechanisms. If the callbacks for the read and write operations both race to lock the same resource (e.g. the session object mutex), one of the completed operations will obviously have to wait for the other. This wastes one precious working thread, because it is just sitting there waiting while it could be serving other connections instead. I tried to alleviate this waste by transferring the "completion-token" to the first thread. Once the first thread was done with its first callback (e.g 'on_read'), it would proceed to handle the transferred callback (e.g 'on_written') before returning itself to the pool. This made the situation much better, but somehow tended to cluster related operations, since each thread would do comparatively more work on one socket before allowing another session's callbacks to be processed. It made better use of the threads, but possibly on the expense of fairness. Mats

Don G wrote:
The question is why do you need multiple network objects at the client side.
My sad story: Over the past couple years I've had the joy of writing some ActiveX controls that run in IE (please don't shoot; they were the good kind<g>). In doing so, I have been best served by not having any active stuff running behind the scenes, especially threads. The desire to keep a library of pure objects has flavored my choices here.
When an ActiveX object dtor is called, I want _all_ activity related to that instance to stop. Other instances may be held by other threads and I don't want all interfaces to be thread-safe. Anyway, that is a big part of why I avoid designs that have significant apparatus globally managed.
This is a good point. I'll describe how I think your model is supposed to operate, because I can't reconcile it with some of your later statements, though. The network object owns the pending callback queue and the worker threads. When this object is destroyed, all activity is cancelled, pending callbacks are lost or delivered, and the worker threads are stopped.
So, at one level, the serial line is just a stream. Over that stream, one can layer an entire network model, including "me" and "thee" addressing. :)
Yes. You do know that this applies for every stream, of course. You can have a network_over_stream adaptor and layer a network over a TCP connection, a named pipe or over stdin/stdout. This doesn't make any of these streams networks, and neither is a communication port a network. The network-over-stream is a good example that demonstrates the strength of your network-centric design. Addresses are naturally network-dependent and do not have a meaning outside of the context of a particular network. This of course leads to the obvious question: which network gives me a stream over COM1? LPT1? A named pipe? An arbitrary UNIX file descriptor?
I'm not sure I understand the difference you are drawing between ":/" and "://". The way I read the URL/I syntax, many options will work. [...]
No, the problem is that you are abusing the URI syntax. :-) An Universal Resource Identifier is universal. It is not context-dependent and completely identifies a resource. Your addresses are network-dependent and cannot be used outside of the context of their network. They may look like URIs, but they are not.
I'm not sure. net::poll is specifically intended to preserve the internal structure of your library. You only need to defer dispatching the callbacks until net::poll is called (unless net::async_poll is in effect.)
I am not sure I understand your async_poll suggestion (sorry about that<g>).
async_poll is basically what your library does at the moment. It acts as if there were an implicit async_poll call after a network is created. In a network-centric model, BTW, poll and async_poll would be member functions of the network class.
I agree that net::poll() would fit with a common desire to have single threaded network programs, but might complicate things where the program is a GUI. Integrating with the GUI loop is a study in compromise (at least for Windows).
So you just use async_poll, just as you do now.
It also gives you the freedom to make net::poll the primary model since it maps very naturally to select/epoll. But you aren't forced to do that.
I like the idea of net::poll() for some uses, but it doesn't fit what I often need (GUI integration). It does fit with select/epoll especially on platforms where the number of objects that can go in an fd_set is > 64.
I don't know if it was clear from previous posts, but I do have in mind a higher level net::poll() like library. The reason I prefer that approach to net::poll() is that not all async activities are network related. A timer comes to mind here. Also, the GUI event loop. Also, just plain "do this next, but not now" queuing. I would want the ability to have all that deliver out of the same pump as network completion callbacks.
I can't fit this into my understanding of how your library is organized. Callback queues and worker threads are network-specific. You can't layer a higher-level poll facility on top of these. I must be missing something.
My current proposal does not include explicit support for this. With the general async facility I am describing, net::poll() would be relegated to only some pure network-only, apps that absolutely insist on the select w/no threads approach.
[...]
I agree, but others don't. Some folks don't want any background threads; just a single thread doing one uber select/epoll call (again, for platforms that can handle it). The only way I can see to accommodate that is a different concrete network object. The ones I would write initially would probably not fit this desire, but again, the interface and behavior contract probably can.
My interpretation is somewhat different than yours. I believe that some folks want to use select/epoll whenever this model is more efficient, not just because it's uber or single-threaded.
That's the whole idea. When using net::poll the context is the thread that called net::poll. When using async_poll (your current modus operandi), the context is an unspecified background thread. The user is in control of the context.
I am specifically concerned with the hypothetical protocol library author here more than the application author. If I want to write an SSL stream, for example, I need to know how my use of the real stream will behave and what I am allowed to do in the callback context.
While the application layer may make the final call, that call cannot invalidate the contract assumed by the protocol library author.
It can't invalidate the contract. Your current contract is that callbacks are run in an unspecified thread. With poll, the application layer can specify the thread. I don't see how can this possibly contradict the assumptions of the protocol library.
- At the app layer, the developer sometimes wants to choose single threaded(!) vs. don't care but deliver it on this thread please vs. use whatever thread context is best + use my quad CPU's please + minimize context switches (aka "give me your best shot, I can take it").
Can you give an example of a developer that absolutely insists on a single-threaded implementation (not interface)? I think that the distinction is: invoke callbacks in whichever thread suits you best vs invoke callbacks in _this_ thread please, my application is not reentrant. And regardless of which I've chosen, give me your best shot under these requirements. No need to confine yourself to a single thread/CPU _unless this will be faster_.
- Middle level protocol libraries (like SSL or HTTP) must not be presented with different behaviors from the abstract interfaces. If they followed the rules (TBD), they should continue to work regardless of a choice made by the application developer.
Do you have a specific behavior in mind?

Hi Peter,
When an ActiveX object dtor is called, I want _all_ activity related to that instance to stop. Other instances may be held by other threads and I don't want all interfaces to be thread-safe. Anyway, that is a big part of why I avoid designs that have significant apparatus globally managed.
This is a good point. I'll describe how I think your model is supposed to operate, because I can't reconcile it with some of your later statements, though.
The network object owns the pending callback queue and the worker threads. When this object is destroyed, all activity is cancelled, pending callbacks are lost or delivered, and the worker threads are stopped.
Very close. The network owns the worker threads, which own an object per socket, which own the callback boost::function<> (in the original it was not boost::function<> but its moral equivalent<g>). When the network is destroyed, it stops all worker threads which would wait on any callbacks (they are made by the worker threads). The queue of functions is separate, on a per-thread basis. A very common thing to do was to pass an auto-enqueue wrapper function containing the real function and a ref to the destination queue: // called directly by worker thread (be careful!): strm->async_read(&this_type::method, ...); // called in thread that opened the channel (easy). strm->async_read( channel.bind_async_call(&this_type::method), ...);
So, at one level, the serial line is just a stream. Over that stream, one can layer an entire network model, including "me" and "thee" addressing. :)
Yes. You do know that this applies for every stream, of course. You can have a network_over_stream adaptor and layer a network over a TCP connection, a named pipe or over stdin/stdout.
Of course.
This doesn't make any of these streams networks, and neither is a communication port a network.
I won't debate semantics here (too much<g>): Once the line is up, communications between A and B occur in exactly the same way as they would with TCP/IP. Both ends "listen" for incoming stream connections on multiple ports (each for a different "service"), each can accept one or more datagrams on different ports, again for different services. In a nutshell, they are abstractly identical. The big difference is that the entire network consists of two hosts.
The network-over-stream is a good example that demonstrates the strength of your network-centric design. Addresses are naturally network-dependent and do not have a meaning outside of the context of a particular network.
This of course leads to the obvious question: which network gives me a stream over COM1?
network_ptr com1 = new net_over_stream( new stream_over_serial("com1"));
LPT1?
network_ptr lpt1 = new net_over_stream( new stream_over_lpt("lpt1"));
A named pipe?
network_ptr pnet = new net_over_stream( new pipe_stream("\\\\server\\pipe\\foo"));
An arbitrary UNIX file descriptor?
something like above, but this only works for bidirectional streams.
No, the problem is that you are abusing the URI syntax. :-)
Perhaps, but see next point. ;)
An Universal Resource Identifier is universal. It is not context-dependent and completely identifies a resource.
The resource is located under the primary key of <scheme> as in the syntax: <scheme>:<scheme-specific-part> One must know the scheme in order to understand the rest. If not, please enlighten me on the part of the RFC to which you are referring (I want to fix my understand if it is mislead).
Your addresses are network-dependent and cannot be used outside of the context of their network. They may look like URIs, but they are not.
Please see previous point. Why is this universal: "http://www.boost.com"? It is only universal in the sense that it represents a host on the Internet. That fact must be inferred from "http" as in "oh, http is an Internet thing, and the rest is "user:pswd@host:port/path?query because that's what we all agree on". Which simply means a common lexicon of schemes and their interpretation. Again, unless I am missing something.
async_poll is basically what your library does at the moment. It acts as if there were an implicit async_poll call after a network is created.
I understand your meaning now. It isn't "exactly" what my network object does now (it has no queue).
In a network-centric model, BTW, poll and async_poll would be member functions of the network class.
Certainly true of poll(). The async_poll() is a bit different than what I had considered, so I will try to ponder that further.
I can't fit this into my understanding of how your library is organized. Callback queues and worker threads are network-specific. You can't layer a higher-level poll facility on top of these. I must be missing something.
I did post a couple messages describing what I am proposing with regards to the higher-level library, so I won't repeat much here. Internally, I have no queue. It is roughly like this: void worker_thread::main () { fd_set rs, ws, es; while (!stop) { net->load_balance(); for (sock in my collection) add to appropriate set select(); for (sock in my collection) make progress on I/O & do callbacks } } Give or take some locks (almost never contended) and paranoia. It has exactly the same efficiency as doing this manually because no queue is used internally. The next iteration of the loop will know exactly what I/O is needed because the callback will have issued those requests. That is, unless the callback is queued for later or elsewhere.
My interpretation is somewhat different than yours. I believe that some folks want to use select/epoll whenever this model is more efficient, not just because it's uber or single-threaded.
Given the example above, I don't see how it could be much more efficient beyond the fact that the callbacks are using boost::function<> and copies of those objects might add up. I won't try to put words in to other's mouths, so I will let them speak up as to whether your take or mine is accurate (we'll probably hear that both are from different people<g>).
It can't invalidate the contract. Your current contract is that callbacks are run in an unspecified thread. With poll, the application layer can specify the thread. I don't see how can this possibly contradict the assumptions of the protocol library.
Currently, the contract is that _another_ thread is used; one managed by the network. Meaning, I could spin on a condvar while I waited if I was presenting a blocking interface (and started outside the network worker thread context). This would break with the pure single-threaded network. Of course, the right answer might be to define the semantics as you stated and allow room for this choice in the network impl.
- At the app layer, the developer sometimes wants to choose single threaded(!) vs. don't care but deliver it on this thread please vs. use whatever thread context is best + use my quad CPU's please + minimize context switches (aka "give me your best shot, I can take it").
Can you give an example of a developer that absolutely insists on a single-threaded implementation (not interface)?
I think Iain Hanson is that camp. Could be wrong.
I think that the distinction is: invoke callbacks in whichever thread suits you best vs invoke callbacks in _this_ thread please, my application is not reentrant. And regardless of which I've chosen, give me your best shot under these requirements. No need to confine yourself to a single thread/CPU _unless this will be faster_.
That would be my preference. Except that I see it on a per operation basis so that libraries can make their own choice. I have a protocol at work that uses sync writes and async reads and would be hard pressed to use fully async.
- Middle level protocol libraries (like SSL or HTTP) must not be presented with different behaviors from the abstract interfaces. If they followed the rules (TBD), they should continue to work regardless of a choice made by the application developer.
Do you have a specific behavior in mind?
Yes, see above. :) Best regards, Don __________________________________________________ Do You Yahoo!? Tired of spam? Yahoo! Mail has the best spam protection around http://mail.yahoo.com

Don G wrote:
This doesn't make any of these streams networks, and neither is a communication port a network.
I won't debate semantics here (too much<g>): Once the line is up, communications between A and B occur in exactly the same way as they would with TCP/IP. Both ends "listen" for incoming stream connections on multiple ports (each for a different "service"), each can accept one or more datagrams on different ports, again for different services. In a nutshell, they are abstractly identical. The big difference is that the entire network consists of two hosts.
Yes, I see. I was thinking of a different scenario, however, one in which you communicate with a device over a serial line (not with another host) using a specified protocol. Since serial lines are relatively slow, asynchronous communication is a good thing.
This of course leads to the obvious question: which network gives me a stream over COM1?
network_ptr com1 = new net_over_stream( new stream_over_serial("com1"));
Wait a minute. Which network creates the stream_over_serial? Assume that I don't need the outer network since I'll be communicating with a printer, for example.
LPT1?
network_ptr lpt1 = new net_over_stream( new stream_over_lpt("lpt1"));
Same question. :-)
Your addresses are network-dependent and cannot be used outside of the context of their network. They may look like URIs, but they are not.
Please see previous point.
Why is this universal: "http://www.boost.com"? It is only universal in the sense that it represents a host on the Internet. That fact must be inferred from "http" as in "oh, http is an Internet thing, and the rest is "user:pswd@host:port/path?query because that's what we all agree on". Which simply means a common lexicon of schemes and their interpretation. Again, unless I am missing something.
My point was that different network objects can interpret the same address to mean different things, at least in theory. URIs are supposed to be context-independent.
participants (4)
-
Don G
-
Mats Nilsson
-
Michel André
-
Peter Dimov