Перейти к содержанию

Обработка ошибок

Обработка исключительных ситуаций

Общие сведения

При работе с Java-клиентом можно встретиться со следующими категориями ошибок:

Типы ошибок
Ошибка на стороне Tarantool Представление ошибки в Java-клиенте Описание
Исключительные ситуации, возникающие при выполнении Lua-кода на сервере приложений Tarantool. Исключения выбрасываются через box.error(...) или error(...) Ошибки передаются в Java-клиент на уровне протокола IProto с использованием специального пакета. В Java-клиенте такие ошибки оборачиваются в исключение типа BoxError

Ошибку данного типа в Java-клиенте можно получить в следующих случаях:

  • Ошибки во время использования TarantoolBoxSpace API
  • Явный вызов box.error(...) или error(...) при выполнении кода хранимой процедуры через TarantoolClient#call(...)
  • Явный вызов box.error(...) или error(...) при выполнении кода через TarantoolClient#eval(...)
  • Иные ошибки, возникающие при работе с TarantoolBoxClient API, TarantoolClient#eval(...), TarantoolClient#call(...), описанные в файле
Ошибки, возвращаемые в виде структур в составе multivalue кортежа без выброса исключений (по аналогии с Go) на стороне Tarantool Ответ от Tarantool преобразуется по общим правилам десериализации Такая ситуация не рассматривается Java-клиентом как исключительная. Ответ с объектом-ошибкой преобразуется в Java-объекты по общим правилам десериализации. При работе с TarantoolCrudClient кортеж-ответ преобразуется в объект типа CrudResponse
- Исключения формируются на стороне Java-клиента Данные исключения генерируются самим Java-клиентом в зависимости от различных ситуаций. Смотрите полный список исключений

Исключительные ситуации на стороне Tarantool

TarantoolBoxClient API

При использовании TarantoolBoxClient можно получить исключение типа BoxError в случае ошибочного использования API.

Пример 1: повторная вставка записи с тем же идентификатором

Создадим space с именем person:

person_space = box.schema.space.create('person', {
    if_not_exists = true,
    format = {
        { 'id', type = 'uuid' },
        { 'value', type = 'string', is_nullable = true }
    }
})
person_space:create_index('pk', { parts = { 'id' } })

Создадим экземпляр TarantoolBoxClient и вставим запись (запись должна появиться в Tarantool):

final TarantoolBoxClient boxClient = TarantoolFactory.box()
    .host("paste-host-name")
    .port(3302)
    .user("paste-username")
    .password("paste-username-password")
    .build();

final TarantoolBoxSpace space = boxClient.space("person");
final List<?> tuple = Arrays.asList(UUID.randomUUID(), "some_value");

final Tuple<List<?>> insertedTuple = space.insert(tuple);

Повторная вставка записи приведет к выбросу исключения CompletionException со ссылкой (caused by) на BoxError:

final List<?> tupleWithNewValue = Arrays.asList(tuple.get(0), "some_value_2");

// stacktrace: 
//    java.util.concurrent.CompletionException: io.tarantool.core.exceptions.BoxError:
//    BoxError{code=3, message='Duplicate key exists in unique index "pk" in space "tt" with 
//    old tuple - [bdf657fc-5779-46d6-aea6-402e4a5eee38, "some_value"] and new tuple -
//    [bdf657fc-5779-46d6-aea6-402e4a5eee38, "some_value"]', 
//    stack=[BoxErrorStackItem{type='ClientError', line=1133, file='./src/box/memtx_tree.cc',
//    message='Duplicate key exists in unique index "pk" in space "tt" with old tuple - 
//    [bdf657fc-5779-46d6-aea6-402e4a5eee38, "some_value"] and new tuple -
//    [bdf657fc-5779-46d6-aea6-402e4a5eee38, "some_value"]', errno=0, code=3, details=null}]}
final Tuple<List<?>> insertedTuple = space.insert(tuple).join();
Пример 2: при удалении записи передан ключ с неверным типом

Будем использовать созданный в Примере 1 space person. Удалим запись, передав неверный ключ (представлен в виде string):

final TarantoolBoxSpace space = boxClient.space("person");

final String uuidAsString = tuple.get(0).toString();

// stacktrace: 
//    io.tarantool.core.exceptions.BoxError: BoxError{code=18, message='Supplied key
//    type of part 0 does not match index part type: expected uuid', 
//    stack=[BoxErrorStackItem{type='ClientError', line=850, file='./src/box/key_def.h',
//    message='Supplied key type of part 0 does not match index part type: expected uuid', 
//    errno=0, code=18, details=null}]}
final Tuple<List<?>> deletedTuple = space.delete(uuidAsString).join();
Важно

Чтобы узнать больше про модуль box, обратитесь к документации

Исключения на стороне Tarantool при вызове TarantoolClient#call(...)

Создадим хранимые процедуры, в которых проверяется тип переданного параметра:

function check_parameter_with_error(param)
    if type(param) ~= "string" then
        error("Parameter must be a string")
    end
    return "Parameter is valid"
end

box.schema.func.create('check_parameter_with_error')
function check_parameter_with_box_error(param)
    if type(param) ~= "string" then
        box.error(box.error.PROC_LUA, "Parameter must be a string")
    end
    return "Parameter is valid"
end

box.schema.func.create('check_parameter_with_box_error')

Вызовем хранимые процедуры из Java:

import java.util.Collections;
import java.util.List;
import java.util.UUID;

final List<String> stringParam = Collections.singletonList("some_value");

// OK 
// TarantoolResponse(data = [Parameter is valid], formats = {})
final TarantoolResponse<List<?>> result =
    boxClient.call("check_parameter_with_error", stringParam).join();

final List<UUID> param = Collections.singletonList(UUID.randomUUID());

// Stacktrace:
//    java.util.concurrent.CompletionException: io.tarantool.core.exceptions.BoxError: 
//    BoxError{code=32, message='Parameter must be a string', 
//    stack=[BoxErrorStackItem{type='ClientError', line=3, 
//    file='[string "function check_parameter_with_box_error(param..."]', 
//    message='Parameter must be a string', errno=0, code=32, details=null}]}
final TarantoolResponse<List<?>> exceptionallyResult =
    boxClient.call("check_parameter_with_error", param).join();
import java.util.Collections;
import java.util.List;
import java.util.UUID;

final List<String> stringParam = Collections.singletonList("some_value");

// OK
// TarantoolResponse(data = [Parameter is valid], formats = {})
final TarantoolResponse<List<?>> result =
    boxClient.call("check_parameter_with_box_error", stringParam).join();

final List<UUID> param = Collections.singletonList(UUID.randomUUID());

// Stacktrace: 
//    io.tarantool.core.exceptions.BoxError: BoxError{code=32, message='Parameter must be a 
//    string', stack=[BoxErrorStackItem{type='ClientError', line=3, 
//    file='[string "function check_parameter_with_box_error(param..."]', 
//    message='Parameter must be a string', errno=0, code=32, details=null}]}
final TarantoolResponse<List<?>> exceptionallyResult =
    boxClient.call("check_parameter_with_box_error", param).join();

Исключения на стороне Tarantool при вызове TarantoolClient#eval(...)

Выполним следующий Lua код через Java-клиент:

if not box.space[space_name] then 
    box.error(box.error.NO_SUCH_SPACE, string.format("Space does not exist: %s", space_name)) 
end
return string.format("Space '%s' exists", space_name)
final String luaCode = "if not box.space[space_name] then "
    + "box.error(box.error.NO_SUCH_SPACE, string.format(\"Space does not exist: %s\", space_name)) "
    + "end return string.format(\"Space '%s' exists\", space_name)";

// OK
// TarantoolResponse(data = [Space 'person' exists], formats = {})
final TarantoolResponse<List<?>> result = client.eval(luaCode, "person").join();

// Stacktrace:
//    java.util.concurrent.CompletionException: io.tarantool.core.exceptions.BoxError: 
//    BoxError{code=36, message='Space 'Space does not exist: unknown_space' does not exist', 
//    stack=[BoxErrorStackItem{type='ClientError', line=1, file='eval', message='Space 'Space does 
//    not exist: unknown_space' does not exist', errno=0, code=36, details=null}]}
final TarantoolResponse<List<?>> exceptionallyResult = client.eval(luaCode, "person").join();

Go-like ошибки

TarantoolCrudClient

TarantoolCrudClient является оберткой над API модуля crud. Большинство методов API возвращают результат в виде кортежа (res, err), где err - объект ошибки. В Java-клиенте результат вызова метода crud API оборачивается в объект типа CrudResponse. С точки зрения протокола IProto, возврат кортежа с объектом-ошибкой не является исключительной ситуацией, но в случае ошибки (err != nil), TarantoolCrudClient самостоятельно выбрасывает исключение типа CrudError (по аналогии сTarantoolBoxClient, который выбрасывает BoxError) для сигнализации, что был возвращен объект ошибки.

В качестве примера создадим на vshard шардированном кластере space person со следующим форматом:

format = {
    { 'id', type = 'uuid' },
    { 'value', type = 'string', is_nullable = true },
    { 'bucket_id', 'unsigned' },
}

Добавим записи в кластер:

import java.util.Arrays;
import java.util.UUID;

final TarantoolCrudClient crudClient = TarantoolFactory.crud()
    .host("router-hostname")
    .port(3301)
    .user("username")
    .password("username-password")
    .build();

final TarantoolCrudSpace space = crudClient.space("person");

final List<?> tuple = Arrays.asList(UUID.randomUUID(), "some_value");

// OK
//    Tuple(
//      formatId = 55, 
//      data = [ca95b6c7-134d-476a-8545-1ce038cb3b18, some_value, 1208], 
//      format = [
//        Field{
//          name='id', 
//          type='uuid', 
//          isNullable=null, 
//          collation='null', 
//          constraint=null, 
//          foreignKey=null
//        }, 
//        Field{
//          name='value', 
//          type='string', 
//          isNullable=null, 
//          collation='null', 
//          constraint=null, 
//          foreignKey=null
//        }, 
//      Field{
//        name='bucket_id', 
//        type='unsigned', 
//        isNullable=null, 
//        collation='null', 
//        constraint=null, 
//        foreignKey=null
//      }
//    ]
//   )
final Tuple<List<?>> insertedTuple = space.insert(tuple).join();

final List<?> tupleWithSameId = Arrays.asList(tuple.get(0), "second_value");

// Stacktrace:
//    java.util.concurrent.CompletionException: io.tarantool.mapping.crud.CrudException: 
//    InsertError: Failed to insert: Duplicate key exists in unique index "pk" in space "person" 
//    with old tuple - [ca95b6c7-134d-476a-8545-1ce038cb3b18, "some_value", 1208] and 
//    new tuple - [ca95b6c7-134d-476a-8545-1ce038cb3b18, "second_value", 1208]
final Tuple<List<?>> exceptionallyTuple = space.insert(tupleWithSameId).join();

Возврат Go-like ошибок через хранимые процедуры

Tarantool допускает возврат Go-like кортежей в возвращаемом значении хранимых процедур. В этом случае пользователь должен позаботиться о правильности преобразования lua и Java типов, основываясь на правилах преобразования.

Рассмотрим следующую процедуру:

function multiply_by_100(input)
    if type(input) ~= "number" then
        return nil, "Input is not a number"
    end
    local result = input * 100
    return result, nil
end

box.schema.func.create('multiply_by_100')
final TarantoolClient client = TarantoolFactory.box()
    .host("hostname")
    .port(3301)
    .user("username")
    .password("username-password")
    .build();

// OK
// TarantoolResponse(data = [10000, null], formats = {})
final TarantoolResponse<List<?>> result = client.call("multiply_by_100", Arrays.asList(100)).join();

// OK
// TarantoolResponse(data = [null, Input is not a number], formats = {})
final TarantoolResponse<List<?>> resultWithError =
    client.call("multiply_by_100", Arrays.asList("100")).join();

В качестве возвращаемых значений могут выступать различные lua-типы, например, массивы:

function prime_factors(input)
    if type(input) ~= "number" or input < 2 then
        return nil, "Input is not a valid number (must be an integer >= 2)"
    end

    local factors = {}
    local divisor = 2

    while input >= divisor do
        while (input % divisor) == 0 do
            table.insert(factors, divisor)
            input = input / divisor
        end
        divisor = divisor + 1
    end

    return factors, nil
end

box.schema.func.create('prime_factors')
import java.util.Arrays;

// OK
// TarantoolResponse(data = [[2, 2, 5, 5], null], formats = {})
final TarantoolResponse<List<?>> result = client.call("prime_factors", Arrays.asList(100)).join();

    // OK
// TarantoolResponse(data = [null, Input is not a valid number (must be an integer >= 2)], 
// formats = {})
    final TarantoolResponse<List<?>> result =
        client.call("prime_factors", Arrays.asList(100)).join();

Исключения, генерируемые Java-клиентом

Типы исключений
Тип исключения Описание
BadGreetingException Выбрасывается в случае неудачного приветствия с узлом Tarantool
BalancerException Базовое исключение, выбрасываемое при неправильной работе балансировщика
NoAvailableClientsException Наследник BalancerException. Выбрасывается в случае когда балансировщик не смог найти ни одного "живого" соединения в пуле соединений
SchemaFetchingException Наследник ClientException. Выбрасывается, когда во время загрузки схем спейсов произошла какая-либо ошибка
ConnectionException Базовое исключение для исключений, связанных с низкоуровневым соединением (интерфейс Connection)
ConnectionClosedException Наследник ConnectionException. Выбрасывается при аварийном закрытии соединения со стороны узла Tarantool или со стороны Java-клиента, в момент, когда соединение к узлу инициировано, но было прервано до момента фактического подключения
CrudException Базовый класс исключений при работе с TarantoolCrudSpace API. Выбрасывается при ошибочной работе с TarantoolCrudSpace API (например, вставка записи с одинаковым ключом)
ServerException Базовый класс исключений для ошибок сервера, таких как устаревшая версия протокола
JacksonMappingException Базовый класс исключений для ошибок сериализации и десериализации типов
NoSchemaException Наследник ClientException. Выбрасывается, когда при вызове TarantoolBoxClient#space(...) space с переданным идентификатором или именем не существует
ShutdownException Наследник ClientException. Класс исключений клиентов, таких как ошибки изящного завершения
PoolException Базовый класс исключений, возникающих при работе пула соединений
PoolClosedException Наследник PoolException. Выбрасывается, при попытке выполнить запросы на прежде закрытом Java-клиенте
TimeoutException Выбрасывается, когда действие клиента превышает заданное время (подключение, приветствие, тайм-аут запроса)