Error Handling
As with all code, it is important that Thunder plugins handle errors gracefully and consistently.
Exceptions
By default, Thunder is compiled with -fno-exceptions
to disable exception support in the framework. This can be changed by enabling the EXCEPTIONS_ENABLE
CMake option. As a result, plugins should never be designed to throw exceptions.
If an exception does occur, the Thunder process will immediately shut down with an error to prevent any further issues and log the following message:
Thunder shutting down due to an uncaught exception.
If the Crash
logging category is enabled, then more information about the faulting callstack will be available (only on debug builds). Thunder will attempt to resolve the callsign of the faulting plugin but this is not always possible.
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: -== Unhandled exception in: NoTLSCallsign [General] ==-
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [000] [0x7ffff7d22cba] /Thunder/install/usr/lib/libThunderCore.so.1 DumpCallStack [74]
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [001] [0x7ffff7e02b84] /Thunder/install/usr/lib/libThunderMessaging.so.1 Thunder::Logging::DumpException(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) [88]
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [002] [0x555555641b35] /Thunder/install/usr/bin/Thunder
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [003] [0x7ffff7aae24c] /lib/x86_64-linux-gnu/libstdc++.so.6
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [004] [0x7ffff7aae2b7] /lib/x86_64-linux-gnu/libstdc++.so.6
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [005] [0x7ffff7aae518] /lib/x86_64-linux-gnu/libstdc++.so.6
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [006] [0x7ffff49820ff] /Thunder/install/usr/lib/thunder/plugins/libThunderTestPlugin.so
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [007] [0x555555664a61] /Thunder/install/usr/bin/Thunder
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [008] [0x55555566b47e] /Thunder/install/usr/bin/Thunder
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [009] [0x555555643b35] /Thunder/install/usr/bin/Thunder
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [010] [0x7ffff7629d90] /lib/x86_64-linux-gnu/libc.so.6
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [011] [0x7ffff7629e40] /lib/x86_64-linux-gnu/libc.so.6 __libc_start_main [128]
[Wed, 05 Jul 2023 10:43:38]:[SysLog]:[Crash]: [012] [0x555555596055] /Thunder/install/usr/bin/Thunder _start [37]
Exception Catching
Danger
This is almost always a bad idea. Catching exceptions at a high level in such a coarse way then continuing will often result in undesired behaviour!
If compiled with the EXCEPTION_CATCHING
CMake option, then Thunder will install high-level exception catching at specific places in the framework. These will catch exceptions coming from plugins and continue execution instead of terminating the entire process. However, be aware this will not catch all exceptions so some exceptions will still result in the framework terminating.
Error Codes
Thunder defines a list of common error codes in Source/core/Portability.h
. Each error code has unique uint32_t ID associated with it. Error codes can be converted to a human-readable string by calling the ErrorToString(uint32_t code)
function:
uint32_t error = Core::ERROR_TIMEDOUT;
printf("Got error code %d (%s)\n", error, Core::ErrorToString(error));
/* Output:
Got error code 11 (ERROR_TIMEDOUT)
*/
COM-RPC Errors
When designing an interface that will be exposed over COM-RPC, all functions should return a Core::hresult
to indicate if the function executed successfully. On success, the function should return Core::ERROR_NONE
.
Any data returned by the function should be stored in an output parameter instead of a return value. This ensures consistency across interfaces. If an error occurs over the COM-RPC transport or during marshalling/umarshalling the data, the most-significant bit will be used to indicate the error code is a COM error.
Core::hresult success = _remoteInterface->MyFunction();
if (success != Core::ERROR_NONE) {
// An error occured, was this a result of the COM link or did the plugin return an error?
if (success & COM_ERROR == 0) {
printf("Plugin returned error %d (%s)\n", success, Core::ErrorToString(success));
} else {
printf("COM-RPC error %d (%s)\n", success, Core::ErrorToString(success));
}
}
JSON-RPC
As with COM-RPC, JSON-RPC methods should return a Core::hresult
value to indicate success or failure. If the JSON-RPC method returns an error code other than Core::ERROR_NONE
, it is treated as a failure.
Note
Some older RDK plugins return a success
boolean in their response to indicate errors. This is not recommended or necessary - simply return the appropriate error code from the method and a valid JSON-RPC error response will be generated.
The returned JSON conforms to the JSON-RPC 2.0 standard. In addition to the Thunder core error code, the response body may contain a JSON-RPC error as defined in the JSON-RPC specification
code | message | meaning |
---|---|---|
-32700 | Parse error | Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text. |
-32600 | Invalid Request | The JSON sent is not a valid Request object. |
-32601 | Method not found | The method does not exist / is not available. |
-32602 | Invalid params | Invalid method parameter(s). |
-32603 | Internal error | Internal JSON-RPC error. |
-32000 to -32099 | Server error | Reserved for implementation-defined server-errors. |
In the below example, an attempt is made to activate a non-existent plugin. The Controller plugin returns ERROR_UNKNOWN_KEY
since to plugin exists with the given callsign.
Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "Controller.1.activate",
"params": {
"callsign": "fakePlugin"
}
}
Response
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": 22,
"message": "ERROR_UNKNOWN_KEY"
}
}
Handling Unexpected COM-RPC Disconnections
From the perspective of a plugin, there are two COM-RPC disconnection scenarios to consider:
- When the out-of-process side of the plugin unexpectedly terminates (either due to a crash or being killed by an external entity such as the linux OOM killer)
- When a client that has registered for notifications crashes unepxectedly.
Whilst the framework can detect unexpected COM-RPC disconnects and handle updating reference counts accordingly, there are actions that should be taken in the plugin code to ensure safety.
Out-Of-Process Disconnection
Warning
It is essential that plugins implement this feature if they are expected to run out-of-process
If your plugin implements an interface that could run out of process, then it is very important the plugin subscribes to the remote connection notification RPC::IRemoteConnection::INotification
. This is a common pattern that you will see across many plugins.
The framework will raise this notification whenever a COM-RPC connection or disconnection occurs. The plugin should check if the disconnected connection belongs to them, and if so take action. Typically, this action will be for the plugin to deactivate itself with a Failure
reason.
Without listening for this notification and taking action, if the out-of-process side of a plugin dies the plugin itself will not be deactivated.
By deactivating with the Failure
reason, the post-mortem handler will kick in. This will dump the contents of some system files to the log (memory information, load averages) to help debugging.
Note
The Deactivated()
function might be called on a socket thread which we do not want to block. As a result, this function should return as quickly as possible and any work that needs doing (e.g. deactivating the plugin) must be done on a separate thread in the main worker pool.
Example:
TestPlugin.h | |
---|---|
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 |
|
TestPlugin.cpp | |
---|---|
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 |
|
Client Crashes
If your plugin provides the ability for client applications to register for notifications, then if the client crashes the plugin should remove any notification registrations that belong to that client.
Whilst this is not strictly necessary (calling a method on a dead client's notification proxy will not cause a crash), it is a good practice to avoid holding on to dead proxy objects. This ensures memory is correctly freed and you don't waste time firing notifications to dead clients.
In the below example, ITestPlugin
has a notification called INotification
and allows client applications can register/unregister for that notification (see TestPluginImplementation.cpp
).
In normal operation, the client will call Register()
when it starts, and Unregister()
when it exits. However, if the client crashes it might not have chance to call the Unregister()
method. Therefore it is up to the plugin to remove the registration manually.
To do this, the plugin should register for ICOMLink::INotification
events from the framework. When the Dangling()
event occurs (indicating we have an interface that is not connected on both ends), the plugin should check to see which interface was revoked. If the interface belongs to the plugin's notification, then unregister that client.
Note
The below example only demonstrates the ICOMLink::INotification
. In the real world, this should be implemented alongside the RPC::IRemoteConnection::INotification
notification shown in the previous example
TestPlugin.h | |
---|---|
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 |
|
TestPlugin.cpp | |
---|---|
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 |
|
TestPluginImplementation.cpp | |
---|---|
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 |
|