Out site uses cookies. Click here for more information.
Accept
Link copied to clipboard
Object identifier notification design pattern

Stopping a server in a graceful manner is important for at least two reasons. Firstly, during the graceful shutdown the server has a possibility to write data to a storage and to notify clients that it is shutting down. Secondly, if there is an error while shutting down, it is very probable that the error will occur while the server is normally running.

Typically the server will crash when a client disconnects and when all the resources related to the client are freed. If another thread is processing a request when the resources are freed, then it will run into a segmentation fault. Moreover, the source of errors originating from two (or more) threads are hard to find.

GPUltra comprises a terminal server to which terminals connect and which display visualized data. We encountered segmentation fault errors during the server shutdown. It took us a longer while to figure out what was the reason. It turned out that an event from a terminal was being processed while all the resources related to the terminal had already been freed. Processing the event crashed the server as it tried to access data which had already been freed. The issue can and at some point will pop up when a terminal disconnects.

Solution

We came up with a solution in which events are processed using terminal identifier and event parameters. This way an event can be processed and at the same the terminal-related resources can be freed. Now, if the event processing needs to access the terminal resources (already freed), it can access via the server which will reply with this terminal doesn't exist and the event processing can be simply canceled.

Let's continue with the terminals and take a look at an example in C++. Let's see how a button pressed event can be handled using the object identifier notification pattern.

The Terminal class represents a connected terminal which visualizes data at a given resolution. The exception TerminalNotFoundException is thrown by the server class when accessing a non-existing terminal. The event handler cancels an event processing when it catches this exception.

Note the synchronization of the access to the field terminals using a mutex a scoped lock. It is very important as without it, we might end up in a race condition and still could refer a terminal which has already been freed.

class Terminal {
public:
    unsigned int getWidth();
    unsigned int getHeight();
};

class TerminalNotFoundException {
};

class TerminalServer {
    map<string,Terminal*> terminals;
    mutex terminalsMutex;

    void checkTerminalExists(const string& terminalId) {
        if (terminals.find(terminalId) == terminals::end()) {
            throw TerminalNotFoundException();
        }
    }

public:
    unsigned int getTerminalWidth(const string& terminalId) {
        scoped_lock lock(terminalsMutex);
        checkTerminalExists(terminalId);
        return terminals[terminalId]->getWidth();
    }

    unsigned int getTerminalHeight(const string& terminalId) {
        scoped_lock lock(terminalsMutex);
        checkTerminalExists(terminalId);
        return terminals[terminalId]->getHeight();
    }

    void removeTerminal(const string& terminalId) {
        scoped_lock lock(terminalsMutex);
        // Free the terminal resources and remove it.
    }
};

The event handler checks if a button is pressed inside the terminal area. Its implementation is straightforward in this situation. It simply gets the terminal resolution using the terminal identifier and if a terminal does not exist (signalled by the exception TerminalNotFoundException), it cancels the processing by returning. If a button is pressed inside the terminal area, the event is processed further.

class ButtonPressedEventHandler {
    TerminalServer& terminalServer;

public:
    void handleEvent(
        const string& terminalId,unsigned int button,
        unsigned int x,unsigned int y
    ) {
        try {
            unsigned int width = terminalServer.getTerminalWidth(terminalId);
            unsigned int height = terminalServer.getTerminalHeight(terminalId);
            if (x >= width || y >= height) {
                // Event outside the terminal display area.
                return;
            }
        } catch (TerminalNotFoundException& ex) {
            // No such terminal - cancel the event processing.
            return;
        }
        // Process the event further.
    }
}

Conclusion

We have shown a design pattern which helps dealing with freeing resources shared among threads. On the other hand, the problem can be solved by waiting for events to be processed before freeing the resources. However, this approach requires additional thread synchronization which complicates the solution and in turn can introduce other hard-to-spot bugs.

We have introduced the object identifier notification design pattern to GPUltra and since then there have been no issues related to freeing resources shared among threads.