nadreamia.com - humanoid - essays - uncrashableapi
I have developed an application programming interface suitable for most consumer application software that is portable across many operating systems, and is uncrashable. I have not yet proven that it is uncrashable, but I shall prove it when I have time to do so.
My API has many advantages, but in this essay, I will only describe the part of the design that makes it easy to guarantee that the API is uncrashable.
The API is separated into three distinct layers. Each layer contains many different independent code modules.
The first layer is the only layer that calls functions from the OS. Although its code is highly OS dependent, It exposes an API that is completely OS independent. It supports many different operating systems including Windows and Unix. It contains functions for all the common operations expected of an operating system. It has functions for allocating memory, creating threads, syncrhonization primitives, and more.
The first layer's API contains only the essential minimum API required for all my applications to run. That is, the first layer provides all the building blocks needed by any of my applications. This increases portability since it asks very little from the underlying OS. Most of the underlying OS's features are completely unused.
The API has strict rules on how other code may call the functions. The distinguishing feature is that if you break the rules, the results are undefined.
An example is the function for unlocking a mutex. If a thread calls this function for a mutex that is not allocated, or it has not locked itself, the result is undefined. This is the same kind of behaviour that is found in the Unix Specification.
Leaving behaviour undefined for when code breaks rules makes it easy to make the first layer very small, and very portable. If code that calls the first layer breaks those rules, it may crash the code running in the first layer.
The first layer's guarantee is this:
As long as you follow all the rules, my API will always work as documented and never crash.
The second layer only depends on the first layer's API. It is therefore completely OS independent. It implements higher level functions that are useful for a lot of code, and especially the third layer. Remember the first layer's API contains only the "essential minimum API required". The second layer, using only the building blocks from the first layer, provides other non-essential but very useful API's that most applications expect from an operating system.
The second layer's guarantee is the same as the first layer's guarantee.
The third layer only depends on the first and second layer's API. It is therefore completely OS independent just like the second layer.
The third layer implements an API that is a superset of the API found in the two layers below. If you write code that only uses the third layer API, it can do all the same things that you could if you'd call the layers below. So if the API is the same, what's the point? The point is that this layer is uncrashable.
Remember in the first layer we had rules on how to call every function. In the third layer, we have those same rules. Now comes the difference. In the first layer, if you broke the rules, the result was undefined. In the third layer, the result is always completely defined. The code in the third layer is completely deterministic. Every possible error by the caller is caught easily by the third layer and simply reported back to the caller.
In the third layer, nothing is undefined. Everything is defined and deterministic.
Of course, the third layer is dependent on the successful working of the underlying 2 layers on which it sits. So if somebody calls a first layer API crashing the code, it could crash the third layer. That is why the third layer has its own private copy of the two underlying layers which it keeps hidden from the outside.
The code in the third layer is written using only the following assumption:
As long as I follow all the rules of the 1st and 2nd layer API's, those API's will always work as documented and never crash.
Using only the above assumption, I was easily able to write a third layer that provides the following guarantee:
My API will always work as documented and never crash.
Notice that this guarantee doesn't require you to "follow all the rules" as the first and second layer's guarantees did. If you try something weird, the third layer will always catch it and report it back to you with no problem. Now that's an uncrashable application programming interface!
The third layer needs to validate all input in order for it to report it back to you without crashing. There is some slowdown due to this validation process, and therefore, this API isn't as fast as it would be without the validation code.
I have estimated the slowdown for a particular example program, and the program ran about 3 times slower. More clearly, when I turned off some of the validation code in the third layer at compile time, my app ran about 3 times faster. The slowdown can be smaller or greater for other applications depending on how often they call the API.
For big and slow programs that make heavy use of the API, a large reduction in speed may be considered unacceptable. But for most of the small applications I wrote that make use of this API, the slow down isn't noticable to the user, so it is just fine.
I have not spent any time optimizing the validation code for speed. I believe a large speed increase can be achieved if some time is spent optimizing it.
My Uncrashable API has not has been released to the public.
My goal was only to write something for my own use that I can depend on and would never crash on me. It has not crashed in the years that I've been using it, so I feel that I have accomplished my goal.
Uncrashable API, by The Humanoid