Update

So, still working on the announcement list – finishing the error and resource handling framework for the SimpleCGI server.

However, I had an idea earlier.

Once 7.2.0 is out the next big things are hazard pointers (I hate using other peoples ideas, but as far as I can see, there IS no other method) and a balanced btree with delete.

The balanced btree with delete is a big chunk of work – but it struck me I could probably do as a first step an unbalanced btree with delete. You can get pretty good balancing by hashing the key before insert; and the delete concept is pretty simple, you place a marker in the nodes you’re going to have to affect, like the Harris’ logical delete bit, before you get to work.

try/except in C

Where I’ve been working on a SimpleCGI server for nginx to get a mailing list working (because every mailing list under the sun sucks like a dual-fan octo-core iCore7) I’ve for the first time in a very long time come back to some normal, user-mode software coding.

This of course in C brings you back to the problem of error checking and resource handling.

C is a beautiful – physically beautiful – language. Until you add error checking, and then it’s an appalling, unreadable mess.

This problem is compounded by the need to release in the event of error only those resources so far allocated.

I’m sure we’ve all seen the simple example of this, where a function is a series of if() statements, each calling a resource allocator, and each in its body as you go down the list of ifs() containing more and more resource releasing functions.

A long time ago – a looong time ago – I thought it over and independently came up with an approach which is semantically equivelent to try/except.

The rational is this : a program is the minimal set of instructions required to perform a task. You wouldn’t perform any instructions you did not need, because you do not need them. It is then that if ANY instruction fails, the task cannot be completed. We can of course put in error handling code, but what this would amount to would be alternative ways to perform the same work (if we still wanted to be able to complete the task). This is strange, complicated and in many cases impossible (if you can’t open a file, you can’t open a file).

If an error occurs, we are at that point in what is basically uncharted waters. It is difficult enough to write code which works when it follows and only follows the error free path, let alone also coping with all the incredible number of possible situations which can arise when we begin to consider errors.

It seems to me then that once an error occurs, what you actually want is to stop executing code. You want this because you no longer know what state you’re in, so you therefore cannot know what will actually happen if you were to execute code. However, you also still need to deallocate any resources allocated up to this point; so you need additionally to keep track of resources when they are allocated, so you can free them when things go wrong.

Also, I’d like a nice error stack to be dumped, so I know what when wrong where.

Sounds pretty much exactly like Python to me!

So a loooooooong time ago I implemented a solution to this, but I’ve not used it for a long time and how I write code now is rather different to back then, so I’ve reimplemented it.

What I have now is a thread local variable (TLV), which is called the “error and resource state”. A thread does not need to call an init function because global statics are initialized to zero. Every function has a wrapper, which as the very first thing it does is look at this TLV and if there are any errors in it, and if so, it returns. *No code executes once an error has occurred*.

If however there is no error, the wrapper executes. It calls the underlying function and checks for errors – so we’re now checking ALL return values; not being lazy/coding-time efficient and missing out on checking the return value from malloc, etc. If the call falls, an error is placed into the error+resource state, and the function returns the normal error value.

What we see now is that there is no longer any need to check the return values from functions. THIS IS IMPORTANT, because now we get rid of the enourmous code-noise of checking for errors.

We also see that we get an error stack; if we have a chain of function calls, when one fails, it will dump an error, and then its failure will cause its caller to dump an error, etc, right the way back up to the top.

Additionally, if the function calls allocates resource, it is noted in the error+resource state.

At the top level, when we reach the end of execution (whatever that means – will vary by programme) we dump the error stack (if there is one) and free any resources.

So we see here we also free whatever resources *were* allocated – but because of the wrappers returning on error, if there was an error, we will only have been allocating up to that point; but that’s fine.

The wrappers I’ve now implemented in fact as – and this moves a long way from normal C – as #defines. Since I use a TLV, the arguments don’t change. I simply “#define malloc libw_ansi_malloc”. This is a drastic change.

One problem is macros. You can’t have two conflicting values for the same macro/define, so you really do need a new name and the code using the library has to use that new name.

/****************************************************************************/
static void open_listen_socket( unsigned short int port, int *fd )
{
  struct addrinfo
    addrinfo_hints,
    *addrinfo_list;

  char
    port_string[6];

  LIBW_RETURN_ON_FAILURE();
  // TRD : port can be any value in its range
  assert( fd != NULL );

  memset( &addrinfo_hints, 0, sizeof(addrinfo_hints) );
  addrinfo_hints.ai_family = AF_INET;
  addrinfo_hints.ai_socktype = SOCK_STREAM;
  addrinfo_hints.ai_flags = AI_PASSIVE;

  sprintf( port_string, "%hu", port );
  getaddrinfo( "localhost", port_string, &addrinfo_hints, &addrinfo_list );
  *fd = socket( addrinfo_list->ai_family, addrinfo_list->ai_socktype, addrinfo_list->ai_protocol );
  fcntl( *fd, F_SETFL, O_NONBLOCK );
  bind( *fd, addrinfo_list->ai_addr, addrinfo_list->ai_addrlen );
  freeaddrinfo( addrinfo_list );
  listen( *fd, 5 );

  libw_on_error_push_error( "open_listen_socket", "failed to open listen socket" );

  return;
}

So here we note the following;

1. the sequence of function calls is visually clear
2. every function is checked for an error
3. if an error occurs, no more code is executed
4. any allocated resources are deallocated, regardless of error, at the end of main
5. if an error does occur, it is pushed to the stack, and then this function itself pushes its error to the stack, and so on, until main dumps the error stack and returns