Freitag, 30. Januar 2009

Kill productivity now!

Kill productivity now by browsing through this thread: What's your favorite programmer cartoon?

I'm happy to see the great Exploits of a mom (a.k.a. Little Bobby Tables) cartoon at the top. Of course xkcd is your place to go for more of that brand of humour.

Oh and if you understand German don't forget this classic IBM ad with the wonderful line "Where are the web designers?".

Deconstructing Steve Jobs

Tom Junod has written a very unusual article for Esquire called Steve Jobs and the Portal to the Invisible.

It's not average tech writing but a deep-field view of the way Jobs works and thinks.

One really gripping and essentially heartbreaking element is Jobs bashing the Amazon Kindle with the argument that people don't read books anymore anyway when his biological sister Mona Simpson is a writer. Kudos to Junod for writing such a holistic and insightful piece.

Sonntag, 25. Januar 2009

Toughest bug

Some time ago a StackOverflow thread got started with the interesting question about the toughest bug one had fixed. I added this incident:

"This was on Linux but could have happened on virtually any OS. Now most of you are probably familiar with the BSD socket API. We happily use it year after year, and it works.

We were working on a massively parallel application that would have many sockets open. To test its operation we had a testing team that would open hundreds and sometimes over a thousand connections for data transfer. With the highest channel numbers our application would begin to show weird behavior. Sometimes it just crashed. The other time we got errors that simply could not be true (e.g. accept() returning the same file descriptor on subsequent calls which of course resulted in chaos.)

We could see in the log files that something went wrong, but it was insanely hard to pinpoint. Tests with Rational Purify said nothing was wrong. But something WAS wrong. We worked on this for days and got increasingly frustrated. It was a showblocker because the already negotiated test would cause havoc in the app.

As the error only occured in high load situations, I double-checked everything we did with sockets. We had never tested high load cases in Purify because it was not feasible in such a memory-intensive situation.

Finally (and luckily) I remembered that the massive number of sockets might be a problem with select() which waits for state changes on sockets (may read / may write / error). Sure enough our application began to wreak havoc exactly the moment it reached the socket with descriptor 1024. The problem is that select() works with bit field parameters. The bit fields are filled by macros FD_SET() and friends which DON'T CHECK THEIR PARAMETERS FOR VALIDITY.

So everytime we got over 1024 descriptors (each OS has its own limit, Linux vanilla kernels have 1024, the actual value is defined as FD_SETSIZE), the FD_SET macro would happily overwrite its bit field and write garbage into the next structure in memory.

I replaced all select() calls with poll() which is a well-designed alternative to the arcane select() call, and high load situations have never been a problem everafter. We were lucky because all socket handling was in one framework class where 15 minutes of work could solve the problem. It would have been a lot worse if select() calls had been sprinkled all over of the code.

Lessons learned:
  • even if an API function is 25 years old and everybody uses it, it can have dark corners you don't know yet

  • unchecked memory writes in API macros are EVIL

  • a debugging tool like Purify can't help with all situations, especially when a lot of memory is used

  • Always have a framework for your application if possible. Using it not only increases portability but also helps in case of API bugs

  • many applications use select() without thinking about the socket limit. So I'm pretty sure you can cause bugs in a LOT of popular software by simply using many many sockets. Thankfully, most applications will never have more than 1024 sockets.

  • Instead of having a secure API, OS developers like to put the blame on the developer. The Linux select() man page says
    "The behavior of these macros is undefined if a descriptor value is less than zero or greater than or equal to FD_SETSIZE, which is normally at least equal to the maximum number of descriptors supported by the system."
    That's misleading. Linux can open more than 1024 sockets. And the behavior is absolutely well defined: Using unexpected values will ruin the application running. Instead of making the macros resilient to illegal values, the developers simply overwrite other structures. FD_SET is implemented as inline assembly(!) in the linux headers and will evaluate to a single assembler write instruction. Not the slightest bounds checking happening anywhere.

To test your own application, you can artificially inflate the number of descriptors used by programmatically opening FD_SETSIZE files or sockets directly after main() and then running your application."