Camel.Exception

From Evolution

This page has moved. Please see http://live.gnome.org/Evolution/Camel.Exception

Contents

[edit]

Camel.Exception

Many camel functions take exceptions as the last argument. This works similarly to the way CORBA exceptions work, and is used as a mechanism to indicate failure in an operation. It should not be used for normal operating return codes.

The structure of an exception is simple.

struct _CamelException {
        ExceptionId id;
        char *desc;
};

Exceptions consist of an error code and a description text. The description text must be translated into the local locale, and in utf8, when the exception is set.

For function calls that require an exception you have to pass it an allocated and initialised exception pointer, or NULL. Some functions will not operate reliably if they are passed a NULL exception, for others it is a convenient way to not care about the result, or test the result using other means. If a function requires a non-NULL exception, you will get a run-time warning along the lines of:

*camel-warning*: camel_exception_get_id called with NULL parameter.
[edit]

Defining Exceptions

The exception types are defined in the file camel/camel-exception-list.def. Although currently only a couple of return codes are checked specifically in client code. This is because most users of functions only need to know if it failed or worked.

CAMEL_EXCEPTION_NONE
Or 0, indicates that no exception is set. The function camel_exception_is_set should be used instead, since it checks if the exception pointer is NULL first.
CAMEL_EXCEPTION_USER_CANCEL
Indicates that the user cancelled the operation, e.g. through a cancellation notifcation on a Camel.Operation.
CAMEL_EXCEPTION_SYSTEM
Indicates a system-related error occured, normally the descriptive text will contain at least in part, the output of g_strerror().

The other exceptions are rarely or never tested outside of the camel library, so see the exception definition file to see what they are. For semantic reasons decent error codes should be used, but currently they are not very well defined, and not very well used. This is something to consider in the future.

[edit]

Allocating

An exception can be allocated dynamically using these functions. If they are allocated dynamically they must always be freed after they are finished with. As a good coding practice you should always ensure you free them from the same function they were allocated in.

CamelException *camel_exception_new(void);
void camel_exception_free(CamelException *ex);

An example:

   CamelException *ex;

        ex = camel_exception_new();
        ...
        if (camel_exception_is_set(ex))
                printf("Got error: %s\n", ex->desc);
        camel_exception_free(ex);

Exceptions can also be allocated on the stack. If this is done, then the exception has to be initialised in one of two ways. Either by calling camel_exception_init, or initialising its contents to CAMEL_EXCEPTION_INITIALISER, or infact, just initialising it to { 0 } will work.

#define CAMEL_EXCEPTION_INITIALISER { 0, NULL }

void camel_exception_init(CamelException *ex);

An example:

   CamelException ex = CAMEL_EXCEPTION_INITIALISER;

        ...
        if (camel_exception_is_set(&ex))
                printf("Got error: %s\n", ex.desc);
        camel_exception_clear(&ex);

Note that if the exception is not transfererred, it needs to be cleared before exit, so any resources are freed.

[edit]

Setting

These rather poorly named interfaces are used for setting the exceptions. Really, setv should be called set, set shouldn't exist, and setv should take a varags va_list instead. But for historical reasons this was never fixed up.

void camel_exception_set(CamelException *ex, ExceptionId id, const char *desc);
void camel_exception_setv(CamelException *ex, ExceptionId id, const char *format, ...);

So basically, don't bother with camel_exception_set, and just use camel_exception_setv, and that works as it appears, like printf.

[edit]

Testing

These functions are used to access the content of the exception. These have an advantage over just dereferencing the exception in that the exception pointer may be NULL. In which case, a run-time warning will be generated, but the code will continue to run (if you are testing an exception, it implies that the exception pointer must be supplied).

ExceptionId camel_exception_get_id(CamelException *ex);
const char *camel_exception_get_description (CamelException *ex);

This macro is used to test if an exception is set. If the function has no other mechanism to return an error code (e.g. an object return), then you should check the exception after every function invocation. This is quite important, as a failed exception at one function may cascade its failure to follow-on function invocations in unpredictable ways.

#define camel_exception_is_set(ex) (camel_exception_get_id (ex) != CAMEL_EXCEPTION_NONE)

Even this macro seems poorly designed, it may as well be a function if it's going to call a function anyway, or it could be simplified to (ex && ex.id != 0).

[edit]

Other operations

There is also a function to clear exceptions. It resets the exception id to CAMEL_EXCEPTION_NONE, and frees the description and sets it to NULL.

void camel_exception_clear(CamelException *ex);

This can be used in client code that doesn't care if an operation failed, or implementation code that is performing follow-on processing, or when the result of a stack-allocated exception is no longer needed.

And finally there is a transfer function, which copies one exception to another. This will clear the destination, then move the src to the destination, and then clear the source. It will not free the source.

void camel_exception_xfer(CamelException *ex_dst, CamelException *ex_src);

This is useful when you want a function to be able to receive a NULL exception pointer, but still need an exception for error detection. You can allocate a local exception on the stack, call your processing, and then transfer the exception to the original incoming exception; camel_exception_xfer will operate properly if the destination is NULL, and merely clear the source exception; and no more work is required.

For example:

my_foo(CamelObject *o, CamelException *ex)
{
        CamelException x = { 0 };

        bob_foo(o, &x);
        if (camel_exception_is_set(&x)) {
                /* do something */
                camel_exception_xfer(ex, &x);
        }
}

In this case, ex may be passed as NULL, but the exception test will still work.

[edit]

The New Exception (IMAPX)

The code on the notzed-disksummary-branch has some new exception utilities that provide 'real' Java-like exceptions to C code, implemented using longjmp. It may, or it may not, make sense to migrate to this exception system to the rest of camel for future code. But it is being described here as the IMAPX code uses it quite heavily and it has no documentation. At their base object, the exceptions are compatible, but additional macro's and functions extend the functionality somewhat.

Remember, none of this is used outside of IMAPX.

[edit]

Throwing exceptions

Exceptions are thrown using one of two functions. Either one to throw an existing exception, or one that is created dynamically using the supplied printf format and arguments. The use of these functions is pretty straight forward.

void camel_exception_throw_ex(CamelException *ex) __attribute__ ((noreturn));
void camel_exception_throw(int id, char *fmt, ...) __attribute__ ((noreturn));

camel_exception_throw_ex must be given an exception allocated with camel_exception_new, and not one allocated on the stack.

[edit]

Catching exceptions

Exceptions must be caught in a CAMEL_TRY block. The block must end with a CAMEL_DONE macro, with a single CAMEL_CATCH section. If you forget the CAMEL_DONE section, you will get numerous and meaningless compiler warnings, so don't forget it.

This is explained easist with an example:

char *only_run_odd(int count)
{
        char * volatile res = NULL;

        CAMEL_TRY {
                if (count & 1)
                        res = g_strdup_printf("count is '%d'", count);
                else
                        camel_exception_throw(CAMEL_EXCEPTION_SYSTEM, "non-odd count: %d", count);
        } CAMEL_CATCH(e) {
                printf("Got exception: %s\n", e->desc);
        } CAMEL_DONE;
 
        return res;
}

Not a very useful example, but I guess it should make some sense, the real power of course is that the catch will catch exceptions thrown at lower levels. See the imapx code for more useful examples.

Once you've caught an exception, you can cascade it to higher levels using camel_exception_throw_ex. Also note that if your code doesn't allocate any local data on the stack, then it doesn't need to catch the exception at all. Unfortunately because C doesn't have garbage collection, it means you can rarely get away without having a local CATCH to clean up resources, but the possibility is there.

Another important point is the use of volatile. Because C optimisation may temporarily or completely registerise a given automatic variable (i.e. local declaration), it may be possible that a variable assignment may be completely lost if a longjmp is invoked. The above example does not demonstrate this, but a more complete one may:

{
        CamelFolder *folder = NULL;
        CamelStore *store = NULL;

        CAMEL_TRY {
                store = camel_session_get_store(session, uri);
                folder = camel_store_get_folder(store, "foo");
                camel_folder_append_message(folder, msg);
                camel_object_unref(folder);
                camel_object_unref(store);
        } CAMEL_CATCH(x) {
                if (store)
                        camel_object_unref(store);
                if (folder)
                        camel_object_unref(folder);
        } CAMEL_DONE;
}

Note that this is NOT a real camel-api example, as these functions will not throw exceptions, but take them as a final argument, it is just an example to show how the code might appear if they did.

Now, optimisation may mean store never reaches the stack location assigned for store by the time camel_folder_append_message is invoked. If that then throws an exception, the catch block will lose the reference to store. Hence, both folder, and store must be marked as volatile pointers, as in the previous example.

[edit]

Controlling flow

There are also some other macros which are useful in other special cases and to fill out the API, although they are not used in imapx currently.

CAMEL_DROP
Used when you want to call return from within a CAMEL_TRY block. Generally you want to avoid this, but it is here for completeness.
CAMEL_IGNORE
If there is no CAMEL_CATCH section, then you must use a CAMEL_IGNORE to end the CAMEL_TRY block instead of using CAMEL_DONE.
[edit]

Mapping between exception interfaces

The throw exception code and the normal exception code may be intermixed. Mapping a given api function entry point between each type is pretty simple. This allows for example, the IMAPX code to use these cleaner exceptions internally, but still present a fully-compatible api that conforms to the existing exception framework.

[edit]

Current to throw

Converting a function using a normal exception mechanism to one using throw semantics is simple. An exception must be allocated from the heap, and if set, it can then be thrown using camel_exception_throw_ex.

e.g.

somefuncA(void) { /* throws exception */
        CamelException *ex = camel_exception_new();

        folder = camel_store_get_folder(store, "name", 0, ex);
        if (camel_exception_is_set(ex))
                camel_exception_throw_ex(ex);
        ...
}
[edit]

Throw to current

Converting the other way around is even simpler:

somefuncB(CamelException *ex)
{
        CAMEL_TRY {
                somefuncA();
        } CAMEL_CATCH(x) {
                camel_exception_xfer(ex, x);
        } CAMEL_DONE;
}