I’ve been working on a new network layer for my G4 project. (See https://bitbucket.org/ruifig/g4devkit ).
I ended up creating a library with Windows IO Completion ports, which ended up being extremely similar to Boost Asio. So similar, that I considered just dropping my implementation and use Boost Asio instead of reinventing the wheel. But as any good programmer knows, your code is awesome and anyone else’s code sucks, right?
Of course that’s not true. Boost is an amazing library. But since I was trying to keep the project’s dependencies to a minimum, I kept working on my implementation.
The thing is, Boost Asio is well designed, and even in instances where it seems it’s overcomplicating things, for example, by using way too many helper classes, it does so to solve specific problems. Problems you don’t realize are there until you try and create your own asynchronous network library.
I started working on my implementation without looking too much into how Asio works internally. I just wanted a similar interface. How hard can it be? I just need something equivalent to asio::io_service and asio::tcp::socket.
My implementation worked well in the initial stages. I even coded unit tests as I went along. Then, as I tested my new super awesome library with ever more complex unit tests, I started coming across design problems. Soon, I end up realizing that I needed functionality to cancel asynchronous operations at a later time, so I could shutdown cleanly in some situations. Well, Boost Asio does that and more with deadline_timer . “Oh well, I thought”, I just need to code something similar to deadline_timer too.
The “just one more thing I need” list kept growing…
- Why is tcp::acceptor used SOLELY to accept connections, and not manage them ? Why not have a neat server class that nicely aggregates all the connections? Then, while you’re at it, make that a base server class because more OOP can only be a good thing, and have that base class call your virtual methods to get all your goodies?
- It’s best not to try and guess what the application needs. The more the library has to guess, the more it ends up dealing with object lifetime issues, multithreading problems, or deadlocks that happen because you (a) need to protect data; and (b) in some cases you also need to call into application code. Therefore the library can’t guess what the hell you’ll be doing in those callbacks/virtuals.
- Why are most Asio classes are not thread-safe? Surely you want everything to be thread-safe in this age of multi-core madness!
- Not really. Same as above. Better to not try to do any guesswork and leave that to the application.
- This one time at band camp, I tried to make my network library completely thread-safe! That didn’t end up well. When you think everything is fine and your unit tests say so, you proudly decide to put your unit tests in a loop overnight, only to be greeted with a random crash or deadlock hours later. That’s fine. It’s just one more corner case you need to fix, right?
- Why does Boost Asio guarantee that the handlers are called ONCE, and ONLY from a thread in which io_service::run (or run_one(), poll(), poll_one()) is being executed ?
- Consistency. You don’t need to guess what will happen in most cases. A handler is aborted? You still get to know about it the same way you handle non-aborted handlers.
- What the hell do I need io_service::strand for?
- So you don’t end up making a mess trying to create thread-safe handlers.
- So you don’t end up having all your IO threads blocked trying to serve the same connection, just because a specific handler for that connection decided to step out for a pint and leave the doors locked.
Creating non-trivial thread-safe code is always harder than it looks. This was me developing the previous network layer…
In other words, there is no reason to reinvent the wheel. I’m sure I’ll have to add Boost as a dependency eventually, but because I’m stubborn, and I enjoy wasting time creating solutions for problems that Boost Asio already solves, I’ll endure for now.
In the next few posts I’ll dissect a couple of classes I had to create, which are the equivalents of Boost’s strand, call_stack, deadline_timer – and possibly a few more.