A special situation to consider when calling VoltDB stored procedures is error handling. The VoltDB client interface catches most exceptions, including connection errors, errors thrown by the stored procedures themselves, and even exceptions that occur in asynchronous callbacks. These error conditions are not returned to the client application as exceptions. However, the application can still receive notification and interpret these conditions using the client interface.
The following sections explain how to identify and interpret errors that occur when executing stored procedures and in asynchronous callbacks. These include:
If an error occurs in a stored procedure (such as an SQL constraint violation), VoltDB catches the error and returns
information about it to the calling application as part of the ClientResponse
class. The
ClientResponse
class provides several methods to help the calling application determine whether the
stored procedure completed successfully and, if not, what caused the failure. The two most important methods are
getStatus()
and getStatusString()
.
If you are using synchronous procedure calls in your application, a response whose status shows a failure
will be turned into a ProcCallException
. Call the exception's getClientResponse()
method to retrieve the ClientResponse
.
If you are using asynchronous procedure calls in your application, the response is always obtained from
the completed CompletableFuture
.
For both synchronous and asynchronous calls, errors that occur locally in the Client2 library may result
in an exception being thrown, for example an IOException
. You should establish appropriate
handlers so that your application is robust.
callProcedureAsync("MyProc", "someArg") .thenAccept((resp) -> processResponse(resp)) .exceptionally((th) -> processException(th)); ... void processResponse(ClientResponse resp) { final byte AppCodeWarm = 1; final byte AppCodeFuzzy = 2; if (resp.getStatus() != ClientResponse.SUCCESS) { System.err.println(resp.getStatusString()); } else { if (resp.getAppStatus() == AppCodeFuzzy) { System.err.println(resp.getAppStatusString()); } myEvaluateResultsProc(resp.getResults()); } } void processException(Throwable th) { System.err.println("Exception: " + th); }
| |
The
| |
If a | |
If you want the stored procedure to provide additional information to the calling application, there are two
more methods to the In the stored procedure, you can use the methods /* stored procedure code */ final byte AppCodeWarm = 1; final byte AppCodeFuzzy = 2; . . . setAppStatusCode(AppCodeFuzzy); setAppStatusString("I'm not sure about that..."); . . . |
One particular error that needs special handling is if a connection or a stored procedure call times out after
is has been sent to the cluster. By
default, the client interface only waits a specified amount of time (two minutes) for a stored procedure to complete. If
no response is received from the server before the timeout period expires, the client interface returns control to your
application, notifying it of the error. The status in the ClientResponse
object will be
CLIENT_RESPONSE_TIMEOUT.
It is important to note that CLIENT_RESPONSE_TIMEOUT does not necessarily mean the procedure call failed. In fact, it is very possible that the procedure may complete and return information after the timeout error is reported. The timeout is provided to avoid locking up the client application when procedures are delayed or the connection to the cluster hangs for any reason.
Similarly, if no response of any kind is returned on a connection (even if no transactions are pending) within the specified timeout period, the client connection will time out. When this happens, the connection is closed, and any open stored procedures on that connection are closed with a return status of CONNECTION_LOST. Unlike a procedure timeout, when the connection times out, the connection no longer exists, so your client application will receive no further notifications concerning pending procedures, whether they succeed or fail.
CONNECTION_LOST does not necessarily mean a pending procedure call failed. It is possible that the procedure completed but was unable to return its status due to a connection failure. The goal of the connection timeout is to notify the client application of a lost connection in a timely manner, even if there are no outstanding procedures using the connection.
There are several things you can do to address potential timeouts in your application:
Change the timeout period by calling either or both the methods procedureCallTimeout()
and
connectionResponseTimeout()
on the Client2Config
object. The default timeout
period is 2 minutes for both procedures and connections. A value of zero or less disables the timeout altogether.
For example, the following client code resets the procedure timeout to 90
seconds and the connection timeout period to 3 minutes, or 180 seconds:
config = new Client2Config() .procedureCallTimeout(90, TimeUnit.SECONDS) .connectionResponseTimeout(3, TimeUnit.MINUTES); client = ClientFactory.createClient(config);
Respond to the timeout error as part of the response to a procedure call. For example, the following code reports the error to the console.
void processResponse(ClientResponse response) { if (response.getStatus() == ClientResponse.RESPONSE_CONNECTION_TIMEOUT) { System.out.println("A procedure invocation has timed out."); return; } if (response.getStatus() == ClientResponse.CONNECTION_LOST) { System.out.println("Connection lost before procedure response."); return; } // ... further non-timeout processing here ... }
Set up a handler to receive the results of any procedure invocations that complete after the client interface times out. See the following Section 6.6.3, “Writing Handlers to Interpret Other Errors” for an example of creating a handler for delayed procedure responses.
Certain types of errors can occur that the ClientResponse
class cannot notify you about
immediately. In these cases, an error happens and is caught by the client interface outside of the normal stored procedure
execution cycle. If you want your application to address these situations, you need to create handlers, which are
each a special type of asynchronous callback that the client interface will notify whenever such errors occur.
Handlers are individually defined for each type of event, by a specific call to a Client2Config
method. The application handler must implement the corresponding interface, as defined in the Client2Notification
class. The types of event that handlers address include:
Notification that an attempt to connect or reconnect to a cluster member has failed. This applies to both initial connection requests from the application, and to automatic reconnection attempts by the Client2 implementation.
Notification that a connection has been established to a particular cluster member.
Notification that the connection to a particular cluster member has become disconnected. Outstanding procedure calls may or may not have completed successfully.
Procedure invocations that time out in the client may later complete on the server and return results. Since the client application can no longer react to this response inline, the client may want a way to process the returned results. A ClientResponse object will be provided as an argument when this notification is called.
Indicates that the procedure-call queue in the Client2 API is nearing its limit. The queue
has two limits: a warning limit and a hard limit. This notification is
triggered when the queue length exceeds the warning limit. If the hard limit is exceeded, further
procedure calls are rejected until the queue length falls sufficiently. The limits can
be set in the Client2Config>
with clientRequestBackpressureLevel()
and ClientRequestLimit
, for warning and hard limits respectively.
Certain unlikely error situations in the Client2 API are not directly associated with a procedure call. By default, a message is printed on standard error. The application can choose to handle the message instead, perhaps writing it to its own log. The application should not attempt to interpret the message; the wording may change without notice.
For the sake of example, the following late-response handler does little more than display a message on standard output. However, in real world applications the handler would take appropriate actions based on the circumstances.
/* * Define a late response handler. */ void myLateResponseHandler(ClientResponse resp, String host, int port) { System.out.printf("A procedure that timed out on host %s port %d" + " has now responded with status %d\n", host, port, resp.getStatus()); } /* * Declare the client configuration, specifying * a username, a password, and the handler. */ Client2Config myconfig = new Client2Config() .username("username") .password("password") .lateResponseHandler(this::myLateResponseHandler); /* * Create the client using the specified configuration. */ Client2 myclient = ClientFactory.createClient(myconfig);
By performing the operations in the order as described here, you ensure that all connections to the VoltDB database cluster use the same credentials for authentication and will notify the status listener of any error conditions outside of normal procedure execution.
Declare a late-response handler. Define the handler before you define
the VoltDB client or open a connection. The handler signature must conform to the interface
defined for this handler in | |
Define the client configuration | |
Create a client with the specified configuration. |