Code for Concinnity

beautiful and elegant solutions


My take on assertions vs. exceptions

There’s been an never ending debate of whether assertions should be converted to exceptions — if an assertion check is useful in Debug build, why remove them in release build? There are some cases where you want to do extensive checking (for e.g. walking over the whole array and detect data corruption) and it’s fine to remove those extensive slow checks during production, but most of the time, the performance difference should really be negligible.

4 kinds of strategies to handle broken assumptions

To fully understand the problem domain, it is useful to clarify the concepts. From my understanding, the strategy to handle broken assumptions can be categorized as follows:

cassert

This is what you get with assert in most languages or Debug.Assert in C#.

  • The most defining characteristic of cassert is that it is removed in production.
  • In theory this can give you a slight performance gain. I find that it’s not useful most of the time — unless you’re dealing with very low level stuffs.
  • If you use cassert, you assume that a violation to your assumption is not going to cause catastrophic consequences. Personally, I find that is a very bold assumption to make. It is basically trying your luck — after violating the assumption, your program has entered undefined behavior land. Allowing the program to continue could result in data loss or the end of the world.
  • This is definitely not suitable for mission critical programs where everything must have clearly defined behavior.

Logger

I heard this idea was introduced in Code Complete. The basic idea is that you still have the checking in release build. If the assumption is broken, you write it to a log and allow the program to continue.

  • The reason behind is that there is no programmer to fix the bug when the assumption is broken, so there is no point showing the error message box to the user. The program can do nothing to fix itself, so it may as well continue. The logging bit is there to help post-mortem troubleshooting.
  • I personally find that it is futile to log things because it rarely gives you enough information to diagnose the problem. Unless you dump the current stack-trace and local variables in the log, a message like “object should not be null!” is not going to help much — you may as well collect a crash dump. If your program is an input-output program, you may as well ask the client to send the input so you can try to reproduce it in a debugger — where you have such a wealth of information at your disposal.
  • Continuing after logging suffers the same problem as cassert — your program’s behavior is no longer defined. As such, it should not be used in any reliable or mission critical software.

Terminator

The Terminator basically means calling terminate()/abort() in case of a broken assumption. Alternatively, you can call a elaborately designed termination routine that has been rigorously tested.

  • The compelling reason to use this strategy is that it’s closest to 100% well defined behavior. Nothing funny should be able to happen. This is particularly desirable if one wrong instruction in your domain can launch a missile or stop a patient’s life support.
  • This is also suitable in unstable environment where a broken precondition could be the result of a full heap corruption.
  • The Terminator is really the correct solution in terms of theoretical, academic discussion. However, it is also the most inflexible solution in terms of writing useful software — terminating the program with an error dialog box is not the nicest thing to do in modern consumer software.

Exceptions

Finally we have good ol’ exceptions.

  • Stack unwinding and the ability to handle them are the defining features of exceptions.
  • Exceptions stops execution of the current routine immediately — this means if you use exceptions in a function, your function is guarded against any undefined behavior (provided you fully defined exceptional cases). The responsibility to maintain integrity of the program is now shifted to the exception handler — the outside trycatch block.
  • Exceptions also offer a high level of flexibility in handling errors — the actual error handling strategy is implemented by your caller. You could call it some sort of Inversion of Control, which is usually a nice thing in terms of architecture.
  • One potential security hole remains: stack unwinding. Unwinding the stack would call the destructors on local variables — which can do anything.
  • From an extremely rigorous perspective, exceptions aren’t really safe. Your broken assumption could be the result of a full heap corruption, in which case it is really not advisable to allow further code execution in stack unwinding. There is also the fact that your caller could ignore the error in a catch block.

So what does it mean?

It means you should always roll your own because there are different needs for different people.

  • A mission critical software must have clearly defined behavior and flexibility in error handling is not something they need. They would probably use a variation of the Terminator pattern.
  • A video game would probably benefit from cassert because they need every last bit of performance they can get, and they can reasonably believe that their audience’s platform will not launch a missile. On the other hand, video games should “push their luck” in the face of broken assumptions — the show must go on. If the game can still reasonably continue after a failed assertions, it should. (Exercise to reader: how about the game saving part? Probably the most devastating thing a game can do is to delete or corrupt your game saves.)

The take home message

I would suggest people should always write their own strategy to handle broken assumptions. Personally I like to start with something called ENSURE which implements exception style error handling by default. The good thing about a hand rolled solution is that you can always change your strategy later. For a few places where you’d like extensive checking, you can use cassert explicitly.

Published by kizzx2, on December 5th, 2010 at 10:46 pm. Filled under: Coding Tags: 3 Comments

Dissecting obscure COM errors

Here’s how I solved these hair-pulling COM errors:

  • 0x800401fd CO_E_OBJNOTCONNECTED Object is not connected to server.
  • 0x8001010e RPC_E_WRONG_THREAD The application called an interface that was marshalled for a different thread.

And this one when I called IGlobalInterfaceTable::GetInterfaceFromGlobal (through CComGITPtr):

  • 0×80070057 E_INVALIDARG The parameter is incorrect.

The basic framework

To make sense of the following code segments, here’s the whole program’s framework to use for the following scenarios:

We use the boost library for threading to make the code less noisy of plumbing work. You don’t really need to grasp them to understand what’s going on.

I have chosen to use the SHDocVw::IShellWindowsPtr interface because it should be present in every Windows environment and simplifies our example without polluting it with the IDL of an example object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#import "C:/Windows/system32/shdocvw.dll"

#include <iostream>
#include <Windows.h>
#include <comutil.h>

#include <boost/thread.hpp>
#include <boost/ref.hpp>

namespace
{
    // Convenient error checking -- these will fail our
    // program with nicely printed error message in case of
    // any COM error
    void PrintHR(HRESULT hr)
    {
        std::wcerr << L"0x" << std::hex << hr << L" " <<
            _com_error(hr).ErrorMessage() << std::endl;
    }

    void EnsureSuccess(HRESULT hr)
    {
        if(FAILED(hr))
        {
            PrintHR(hr);
            exit(EXIT_FAILURE);
        }
    }

    // A simple wrapper with integrated error checking
    void MyCoInitialize()
    {
        HRESULT hr = CoInitialize(NULL);
        EnsureSuccess(hr);
    }

    // This method can be called in a thread to demonstrate
    // multi-threading issues
    void CreateShellWindow(SHDocVw::IShellWindowsPtr & retval)
    {
        // Because we might be in a different thread, we need
        // to call CoInitialize() just to make sure
        MyCoInitialize();
        HRESULT hr = retval.CreateInstance(__uuiof(SHDocVw::ShellWindows));
        EnsureSuccess(hr);
        CoUninitialize();
    }

    // This should print a numeric number
    // On my machine it always printed "1". The meaning of that
    // is not really important.
    void UseShellWindow(const SHDocVw::IShellWindowsPtr & ptr)
    {
        MyCoInitialize();
        std::cout << ptr->Count << std::endl;
        CoUninitialize();
    }

    void Example()
    {
        // Our example code segments would go here...
    }
}

int main()
{
    try
    {
        Example();
    }
    catch(const _com_error & e)
    {
        PrintHR(e.Error());
        return EXIT_FAILURE;
    }

    return 0;
}

0x800401fd CO_E_OBJNOTCONNECTED Object is not connected to server.

This one is pretty straight forward. You get this when you try to use a COM object after you have called CoUninitialize():

How to reproduce

1
2
3
4
5
6
7
8
9
void Example()
{
    MyCoInitialize();
    SHDocVw::IShellWindowsPtr ptr;
    CreateShellWindow(ptr);
    CoUninitialize();

    UseShellWindow(ptr); // ka-boom!
}

0x8001010e RPC_E_WRONG_THREAD The application called an interface that was marshalled for a different thread.

How to reproduce

1
2
3
4
5
6
7
8
9
10
11
12
void Example()
{
    MyCoInitialize();
    SHDocVw::IShellWindowsPtr ptr;

    CreateShellWindow(ptr);

    // This will use another thread to call `UseShellWindow()`
    boost::thread(&UseShellWindow, ptr); // ka-boom

    CoUninitialize();
}

What happened

The error message is pretty obvious — we need to marshal the pointer for thread A if the pointer was created in thread B, in order to use it in thread A.

How to fix

If you use ATL, one very convenient way to marshal them is to use the GIT (Global Interface Pointer) through CComGITPtr.

(The below serves as a quick fix and introduction. Please look into Global Interface Pointer and stuffs if you want to understand what’s going on).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// A GIT cookie is something you can use to retrieve a pointer
// from the GIT
void UseShellWindowFromCookie(DWORD cookie)
{
    MyCoInitialize();
    SHDocVw::IShellWindowsPtr ptr;
    CComGITPtr<SHDocVw::IShellWindows>(cookie).CopyTo(&fred);

    UseShellWindow(ptr);
    CoUninitialize();
}

void Example()
{
    MyCoInitialize();

    SHDocVw::IShellWindowsPtr ptr;
    CreateShellWindow(ptr);

    // This will register our pointer in the GIT and
    // retrieve a GIT cookie in one line
    DWORD cookie = CComGITPtr<SHDocVw::IShellWindows>(ptr).Detach();

    // Spawn a new thread of the GIT aware version of
    // UseShellWindow()
    boost::thread(&UseShellWindowFromCookie, cookie).join();

    CoUninitialize();
}

0×80070057 E_INVALIDARG The parameter is incorrect.

After discovering how easy it is to marshal COM objects across threads, you may sometimes get E_INVALIDARG when inside IGlobalInterfaceTable::GetInterfaceFromGlobal()

Now this one is really really obscure:

How to reproduce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void CreateShellWindowCookie(DWORD & retval)
{
    MyCoInitialize();

    SHDocVw::IShellWindowsPtr ptr;
    CreateShellWindow(ptr);

    retval = CComGITPtr<SHDocVw::IShellWindows>(ptr).Detach();

    CoUninitialize();
}

void Example()
{
    MyCoInitialize();

    // Instead of using a different thread for UseShellWindow()
    // This time around we use a different thread to
    // create the COM pointer
    DWORD cookie;
    boost::thread(&CreateShellWindowCookie, boost::ref(cookie)).join();

    // Let's try to use it from the current thread
    UseShellWindowFromCookie(cookie); // ka-boom!

    CoUninitialize();
}

What happened

This is similar to what happened with CO_E_OBJNOTCONNECTED, only a lot more subtle and obscure this time around.

To understand this, keep this in mind: A COM object is thread aware. A COM object can only ever live in one single thread, even though it can be accessed from another thread (through marshaling).

What that simply means is that when the thread holding the COM object terminates, the COM object becomes invalid. In our example above, CreateShellWindowCookie() was run from a separate thread and finished before we even tried to use it. The actual COM object went down with the thread. Even though we had a cookie, the GIT would fail to find the actual COM object when it tries to do GetInterfaceFromGlobal(). Too bad that it gives such a confusing error message.

To summarize, here are the things to check when you get E_INVALIDARG from GetInterfaceFromGlobal():

  • Maybe the originating thread that called CoCreateInstance() has ended
  • Maybe the originating thread has already called CoUninitialize()
  • Maybe the actual object has already been terminated by someone Releaseing it.
  • Maybe your GIT cookie has already been used. Strangely enough, it seems like the GIT cookie can only be used to get the interface back once.

How to fix

There are 2 basic approaches:

  • Do not create any COM objects from threads other than the main thread (which is guaranteed to last through the program’s life time)
  • Have an elaborate inter-thread communication so that the creator-thread is kept-alive until the COM object’s users are finished with it (This can get really complicated).

To demonstrate, here’s the code that would work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void CreateShellWindowCookie(DWORD & retval)
{
    MyCoInitialize();
    SHDocVw::IShellWindowsPtr ptr;
    CreateShellWindow(ptr);

    retval = CComGITPtr<SHDocVw::IShellWindows>(ptr).Detach();

    // Sleep 10 seconds
    Sleep(10 * 1000);

    CoUninitialize();
}

void Example()
{
    MyCoInitialize();

    // Create a cookie from another thread
    // We do not wait for it to finish
    DWORD cookie;
    boost::thread t(&CreateShellWindowCookie, boost::ref(cookie));

    // We wait 3 seconds
    Sleep(3 * 1000);

    // Now try to use it from cookie
    UseShellWindowFromCookie(cookie); // OK!

    CoUninitialize();
}

There you go

Hopefully this saved you from some hair-pulling experiences :)

Published by kizzx2, on December 4th, 2010 at 3:40 pm. Filled under: Uncategorized Tags: , , , , , No Comments