No exceptions?

43 views
Skip to first unread message

Bradley Meck

unread,
Mar 23, 2012, 11:29:14 AM3/23/12
to cando...@googlegroups.com
In situations where you are extending a function/method in a way that could cause a need to throw an exception in other languages what is the idea for how to handle this?

```candor
Rect = {
  initialize: (self, w, h) {
    self.w = w
    self.h = h
  }
}

Square = new Rect
Square.initialize = (self, w, h) {
   if (w !== h)
      // ?
   Rect.initialize(self, w, h)
}
```

This example is naive, but it will come up in cases where an interface that originally did not have exceptions / didn't need callbacks to notify exception would suddenly need exceptions. Right now the only way to halt a large nesting of calls seems to have every return checked.

Fedor Indutny

unread,
Mar 23, 2012, 12:57:00 PM3/23/12
to cando...@googlegroups.com
Bradley,

Exceptions are good, but I'm considering Candor to be more like C, than C++.

As I know C works fine w/o exceptions, I'm open to introducing some sort of `abort()` instruction, which will leave candor into C++ land.
Feedback is welcome!

Cheers,
Fedor.

Tim Caswell

unread,
Mar 23, 2012, 1:06:37 PM3/23/12
to cando...@googlegroups.com
I think hard exceptions (like failed asserts in C) are fine for things that are obviously programmer error (like wrong number or types of arguments).  in C these are caught by the compiler, but we don't have strict type checking in candor.  We need some sort of hard assert for this.  I could build it into the candor.io library and implement it in C++. It would be nice if I had a way to get the stack trace, or at least the line that threw the assert.

But the other class of errors that you don't want crashing your server are invalid user input.  The way libuv (The C library behind nodejs and candor.io) handles these errors is to return a non-zero status on any function call that potentially can throw an error.  For async functions, there is a status property in the callback.  It's the responsibility of the programmer to check for this status code and them pull the actual error message out of libuv using the error API.

I've done something like this for candor.io.  It's not pretty, but seems to work ok.  The only sticky point is sync functions that return a value.  Where do I put the status code?  In lua this is done with multiple return values, but I don't think that's a feature we want to add to candor (as much as I love it).

throw = (err) {
  global.prettyPrint(err)
  global.exit()
}

// Used to emit on errors when libuv badness happens
check = (status) {
  if (status) {
    throw(require('uv').lastError())
  }
}

ServerPrototype = {}
ServerPrototype.listen = (self, port, host, callback) {
  if (!host) host = "0.0.0.0"
  check(self.socket:bind(host, port))
  check(self.socket:listen(128, (status) {
    check(status)
    client = new ClientPrototype
    socket = Tcp.create()
    client.socket = socket
    check(self.socket:accept(socket))
    self.onConnection(client)
    check(socket:readStart((nread, chunk) {
      if (nread == -1) {
        err = require('uv').lastError()
        if (err.name == "EOF") {
          client.onEnd()
        } else {
          throw(err)
        }
        return
      }
      if (nread > 0) {
        client.onData(chunk)
        return
      }
    }))
  }))
}

Bradley Meck

unread,
Mar 23, 2012, 2:49:40 PM3/23/12
to cando...@googlegroups.com
I would avoid hard abort(). libgmp last time I checked was doing abort() after encountering errors, which made it prohibitively hard to deal with when you open up fds and cannot close them properly due to aborts. To get around this you would need to spawn up a new thread and pass fds around.

Tim Caswell

unread,
Mar 23, 2012, 3:09:02 PM3/23/12
to cando...@googlegroups.com
Right, this is why I say to never use hard aborts for runtime errors, only for invalid configurations.

And you're correct, we need a story for the more common runtime errors. C does this with return values and callback arguments.

The problem with this C style is that it makes it easy to ignore errors.  If I forget to check the return value of a call then I'll never know it didn't work.  Bad stuff will happen down the road and probably segfault or just do nothing.

Perhaps we could add a constraint on the system that return values can't be ignored.  This is hard without static typing and static analysis, the parser doesn't know if a function will return a value and the runtime is just generated code, we don't want to add in runtime checking and introspection code at every function call.  So that idea probably won't work.

A better convention is maybe an aspect oriented configuration in our libraries where you pass in the error handler to all object creation functions.  Then the library can report errors to this.  Add to this an aspect oriented resource manager and the concept of domains and you can register cleanup functions anytime a resource would need to clean if something down the line blew up.

Just spouting off ideas.  If in doubt we can just take the simple route and require the programmer to check all return values and hope that's good enough.

Bradley Meck

unread,
Mar 23, 2012, 4:27:30 PM3/23/12
to cando...@googlegroups.com
For now return values are probably enough. In the future, an exception handler stack might be more appropriate. This may be too intensive because of handling multiple error handler trees (but is quite similar to domains).

thisCausesAnException(callback) {
  // explicit abort into an exception handler
  callback:abort({reason:'cause I said so'}, {multipleOk: true})
}

doStuff() {
  readAFile(callback() {
    thisCausesAnException()
  } catches(e, x) {
    print('could not read the file')
    // implicit throw, up the error handling chain
    // akin to a rethrow
    abort(e)
  })
} catches(e) {
  print(e.reason);
}

Just a brain dump. Though a while back I had an idea of 'throwing' into functions, basically avoiding the error first problem of node by allowing functions to have exception handlers that could be called without ever calling the function (causes uncaught exception handler to fire if the function has no explicit exception handler). This would avoid error handler overhead unless functions add them during invocation when a function can handle errors, as well as have some sanity when just throwing up the stack.

Cheers,
Bradley

Fedor Indutny

unread,
Mar 27, 2012, 4:30:23 AM3/27/12
to cando...@googlegroups.com
I think you're just trying to apply existing design habbits to a new language.

Handling return values works fine for C and may work fine for Candor too, though it needs to be proved by a practice. The pros of this method is that you always know that code you're calling won't stop execution, which is good for failure-proof applications.

Cheers,
Fedor.

Bradley Meck

unread,
Mar 27, 2012, 11:50:42 AM3/27/12
to cando...@googlegroups.com
Indeed, which is why I think for now we should stick to return values. I'm not convinced that failure proof code that may have side effects on the system is good, but it is one approach.
Reply all
Reply to author
Forward
0 new messages