Shared Libraries are Obsolete

I was around when shared libraries were still a new thing in the UNIX world. At the time, they seemed like a great idea. On multi-user systems like those I worked on at Encore, static linking meant not only having a separate copy of the same code in every program, but having a separate copy even for every user running the same program. The waste of both disk space and memory was a serious concern. Making shared libraries work required a lot of effort from both compiler and operating-system people, but it was well worth it.

Fast forward to the present day. Not only are disk and memory cheaper, but there aren't as many users running copies of the same program on the same machine either. Those savings are neither as big nor as important as they used to be. At the same time, shared libraries have created a whole new world of software maintenance problems. In the Windows world this is called "DLL Hell" but I think the problem is even worse in the Linux world. When every application depends on dozens of libraries, and every one of those libraries is shared, that means dozens of possibilities for an upgraded library to cause a new crash or security failure in your application. Yes, sometimes bugs can be fixed without needing to rebuild applications, but I challenge anyone to show empirical evidence that the fixes are more common than the breakage.

People actually do test their applications against specific combinations of the libraries they depend on. If there are bugs in that combination, they get found and fixed or worked around. Every behind-the-back library upgrade creates a new untested configuration that might be better but is more likely worse. In what other context do we assume that an untested change will "just work"? In what other context should we? Damn few. Applications should run with the libraries they were tested with.

At this point, someone's likely to suggest that strict library versioning solves the problem. It sort of does, so long as the library version includes information about how it was built as well as which version of code, because the same code built differently is still likely to behave differently sometimes. Unfortunately, it just trades one problem for another - dependency gridlock. If every application specifies strict library dependencies, then what do you do when a library changes? If you blindly mass-rebuild applications to update their dependencies, then you haven't solved the "untested combination" problem. If you keep old library versions on the system, then you've thrown away the advantage of having shared libraries in the first place. Either way, you've created a package-management nightmare both for yourself and for distribution maintainers.

Shared libraries still make sense for the very low-level libraries that practically every application uses and that users are already wary of updating, like glibc. If the library maintainer's testing and API-preservation bar is higher than most app developers', that's OK. In almost every other case, you're probably better off with statically linked and tested combinations of apps and libraries. If you want to save some memory, make sure the load addresses are page aligned and do memory deduplication. Otherwise, you're probably just saving less memory than you think at the expense of much more important stability and security.

Update: This post sparked a fairly lively Twitter conversation.

Comments for this blog entry