Skip to content

Commit

Permalink
[CLIENT-3324] Add "commit_status" and "abort_status" attributes to Ae…
Browse files Browse the repository at this point in the history
…rospikeError exceptions (#721)

Add aerospike.{COMMIT_ALREADY_ABORTED,ABORT_ALREADY_COMMITTED} constants
  • Loading branch information
juliannguyen4 authored Feb 12, 2025
1 parent 11a8311 commit 2bad34c
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
path = aerospike-client-c
# url = [email protected]:aerospike/aerospike-client-c.git
url = https://github.com/aerospike/aerospike-client-c.git
branch = CLIENT-3307
branch = stage
14 changes: 8 additions & 6 deletions aerospike-stubs/aerospike.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -295,15 +295,17 @@ QUERY_DURATION_LONG_RELAX_AP: Literal[2]

COMMIT_OK: Literal[0]
COMMIT_ALREADY_COMMITTED: Literal[1]
COMMIT_VERIFY_FAILED: Literal[2]
COMMIT_MARK_ROLL_FORWARD_ABANDONED: Literal[3]
COMMIT_ROLL_FORWARD_ABANDONED: Literal[4]
COMMIT_CLOSE_ABANDONED: Literal[5]
COMMIT_ALREADY_ABORTED: Literal[2]
COMMIT_VERIFY_FAILED: Literal[3]
COMMIT_MARK_ROLL_FORWARD_ABANDONED: Literal[4]
COMMIT_ROLL_FORWARD_ABANDONED: Literal[5]
COMMIT_CLOSE_ABANDONED: Literal[6]

ABORT_OK: Literal[0]
ABORT_ALREADY_ABORTED: Literal[1]
ABORT_ROLL_BACK_ABANDONED: Literal[2]
ABORT_CLOSE_ABANDONED: Literal[3]
ABORT_ALREADY_COMMITTED: Literal[2]
ABORT_ROLL_BACK_ABANDONED: Literal[3]
ABORT_CLOSE_ABANDONED: Literal[4]

TXN_STATE_OPEN: Literal[0]
TXN_STATE_VERIFIED: Literal[1]
Expand Down
8 changes: 8 additions & 0 deletions doc/aerospike.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1594,6 +1594,10 @@ Transaction Commit Status

Transaction has already been committed.

.. data:: COMMIT_ALREADY_ABORTED

Transaction has already been aborted.

.. data:: COMMIT_VERIFY_FAILED

Transaction verify failed. Transaction will be aborted.
Expand Down Expand Up @@ -1625,6 +1629,10 @@ Transaction Abort Status

Transaction has already been aborted.

.. data:: ABORT_ALREADY_COMMITTED

Transaction has already been committed.

.. data:: ABORT_ROLL_BACK_ABANDONED

Client roll back abandoned. Server will eventually abort the transaction.
Expand Down
12 changes: 12 additions & 0 deletions doc/exception.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ Base Class
``True`` if it is possible that the command succeeded. See :ref:`indoubt`.

.. py:attribute:: commit_status
Set to one of :ref:`mrt_commit_status_constants` when :meth:`~aerospike.Client.commit` raises an exception.

Otherwise, :py:obj:`None` if not set.

.. py:attribute:: abort_status
Set to one of :ref:`mrt_abort_status_constants` when :meth:`~aerospike.Client.abort` raises an exception.

Otherwise, :py:obj:`None` if not set.

In addition to accessing these attributes by their names, \
they can also be checked by calling ``exc.args[i]``, where ``exc`` is the exception object and \
``i`` is the index of the attribute in the order they appear above. \
Expand Down
3 changes: 3 additions & 0 deletions src/include/conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ as_status bins_to_pyobject(AerospikeClient *self, as_error *err,
bool cnvt_list_to_map);

void error_to_pyobject(const as_error *err, PyObject **obj);
void as_error_and_mrt_status_to_pytuple(const as_error *err, PyObject **obj,
PyObject *py_commit_status,
PyObject *py_abort_status);

as_status pyobject_to_astype_write(AerospikeClient *self, as_error *err,
PyObject *py_value, as_val **val,
Expand Down
2 changes: 2 additions & 0 deletions src/include/exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

PyObject *AerospikeException_New(void);
void raise_exception(as_error *err);
void raise_exception_with_mrt_status(as_error *err, PyObject *py_commit_status,
PyObject *py_abort_status);
PyObject *raise_exception_old(as_error *err);
void remove_exception(as_error *err);
void set_aerospike_exc_attrs_using_tuple_of_attrs(PyObject *py_exc,
Expand Down
2 changes: 2 additions & 0 deletions src/main/aerospike.c
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ static struct module_constant_name_to_value module_constants[] = {

{"COMMIT_OK", .value.integer = AS_COMMIT_OK},
{"COMMIT_ALREADY_COMMITTED", .value.integer = AS_COMMIT_ALREADY_COMMITTED},
{"COMMIT_ALREADY_ABORTED", .value.integer = AS_COMMIT_ALREADY_ABORTED},
{"COMMIT_VERIFY_FAILED", .value.integer = AS_COMMIT_VERIFY_FAILED},
{"COMMIT_MARK_ROLL_FORWARD_ABANDONED",
.value.integer = AS_COMMIT_MARK_ROLL_FORWARD_ABANDONED},
Expand All @@ -512,6 +513,7 @@ static struct module_constant_name_to_value module_constants[] = {

{"ABORT_OK", .value.integer = AS_ABORT_OK},
{"ABORT_ALREADY_ABORTED", .value.integer = AS_ABORT_ALREADY_ABORTED},
{"ABORT_ALREADY_COMMITTED", .value.integer = AS_ABORT_ALREADY_COMMITTED},
{"ABORT_ROLL_BACK_ABANDONED",
.value.integer = AS_ABORT_ROLL_BACK_ABANDONED},
{"ABORT_CLOSE_ABANDONED", .value.integer = AS_ABORT_CLOSE_ABANDONED},
Expand Down
24 changes: 14 additions & 10 deletions src/main/client/mrt.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,23 @@ PyObject *AerospikeClient_Commit(AerospikeClient *self, PyObject *args,
as_error err;
as_error_init(&err);

as_commit_status status;
// Set a default value in case the C client does not initialize the status.
as_commit_status status = AS_COMMIT_OK;

Py_BEGIN_ALLOW_THREADS
aerospike_commit(self->as, &err, py_transaction->txn, &status);
Py_END_ALLOW_THREADS

if (err.code != AEROSPIKE_OK) {
raise_exception(&err);
PyObject *py_status = PyLong_FromUnsignedLong((unsigned long)status);
if (py_status == NULL) {
return NULL;
}

PyObject *py_status = PyLong_FromUnsignedLong((unsigned long)status);
if (py_status == NULL) {
if (err.code != AEROSPIKE_OK) {
raise_exception_with_mrt_status(&err, py_status, NULL);
return NULL;
}

return py_status;
}

Expand All @@ -55,20 +57,22 @@ PyObject *AerospikeClient_Abort(AerospikeClient *self, PyObject *args,
as_error err;
as_error_init(&err);

as_abort_status status;
// Set a default value in case the C client does not initialize the status.
as_abort_status status = AS_ABORT_OK;

Py_BEGIN_ALLOW_THREADS
aerospike_abort(self->as, &err, py_transaction->txn, &status);
Py_END_ALLOW_THREADS

if (err.code != AEROSPIKE_OK) {
raise_exception(&err);
PyObject *py_status = PyLong_FromUnsignedLong((unsigned long)status);
if (py_status == NULL) {
return NULL;
}

PyObject *py_status = PyLong_FromUnsignedLong((unsigned long)status);
if (py_status == NULL) {
if (err.code != AEROSPIKE_OK) {
raise_exception_with_mrt_status(&err, NULL, py_status);
return NULL;
}

return py_status;
}
26 changes: 25 additions & 1 deletion src/main/conversions.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
#define PY_EXCEPTION_FILE 2
#define PY_EXCEPTION_LINE 3
#define AS_PY_EXCEPTION_IN_DOUBT 4
#define AS_PY_EXCEPTION_COMMIT_STATUS 5
#define AS_PY_EXCEPTION_ABORT_STATUS 6

#define CTX_KEY "ctx"
#define CDT_CTX_ORDER_KEY "order_key"
Expand Down Expand Up @@ -2389,7 +2391,17 @@ as_status metadata_to_pyobject(as_error *err, const as_record *rec,
return err->code;
}

// TODO: a better name for this is as_error_to_pytuple
void error_to_pyobject(const as_error *err, PyObject **obj)
{
as_error_and_mrt_status_to_pytuple(err, obj, NULL, NULL);
}

// Steals reference to MRT statuses, if non-NULL
// If MRT statuses are NULL, that just means they will be None in the Python tuple
void as_error_and_mrt_status_to_pytuple(const as_error *err, PyObject **obj,
PyObject *py_commit_status,
PyObject *py_abort_status)
{
PyObject *py_file = NULL;
if (err->file) {
Expand All @@ -2414,12 +2426,24 @@ void error_to_pyobject(const as_error *err, PyObject **obj)
PyObject *py_in_doubt = err->in_doubt ? Py_True : Py_False;
Py_INCREF(py_in_doubt);

PyObject *py_err = PyTuple_New(5);
if (py_commit_status == NULL) {
Py_INCREF(Py_None);
py_commit_status = Py_None;
}

if (py_abort_status == NULL) {
Py_INCREF(Py_None);
py_abort_status = Py_None;
}

PyObject *py_err = PyTuple_New(7);
PyTuple_SetItem(py_err, PY_EXCEPTION_CODE, py_code);
PyTuple_SetItem(py_err, PY_EXCEPTION_MSG, py_message);
PyTuple_SetItem(py_err, PY_EXCEPTION_FILE, py_file);
PyTuple_SetItem(py_err, PY_EXCEPTION_LINE, py_line);
PyTuple_SetItem(py_err, AS_PY_EXCEPTION_IN_DOUBT, py_in_doubt);
PyTuple_SetItem(py_err, AS_PY_EXCEPTION_COMMIT_STATUS, py_commit_status);
PyTuple_SetItem(py_err, AS_PY_EXCEPTION_ABORT_STATUS, py_abort_status);
*obj = py_err;
}

Expand Down
19 changes: 15 additions & 4 deletions src/main/exception.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ struct exception_def {
#define NO_ERROR_CODE 0

// Same order as the tuple of args passed into the exception
const char *const aerospike_err_attrs[] = {"code", "msg", "file",
"line", "in_doubt", NULL};
const char *const aerospike_err_attrs[] = {
"code", "msg", "file", "line",
"in_doubt", "commit_status", "abort_status", NULL};
const char *const record_err_attrs[] = {"key", "bin", NULL};
const char *const index_err_attrs[] = {"name", NULL};
const char *const udf_err_attrs[] = {"module", "func", NULL};
Expand Down Expand Up @@ -395,8 +396,16 @@ void set_aerospike_exc_attrs_using_tuple_of_attrs(PyObject *py_exc,
}
}

// TODO: idea. Use python dict to map error code to exception
void raise_exception(as_error *err)
{
raise_exception_with_mrt_status(err, NULL, NULL);
}

// TODO: idea. Use python dict to map error code to exception
// If py_commit_status is NULL, ignore it. Same with py_abort_status
// Steals reference to either status objects
void raise_exception_with_mrt_status(as_error *err, PyObject *py_commit_status,
PyObject *py_abort_status)
{
PyObject *py_key = NULL, *py_value = NULL;
Py_ssize_t pos = 0;
Expand Down Expand Up @@ -428,8 +437,10 @@ void raise_exception(as_error *err)
Py_INCREF(py_value);

// Convert C error to Python exception
// Also add commit or abort status to exception, if needed
PyObject *py_err = NULL;
error_to_pyobject(err, &py_err);
as_error_and_mrt_status_to_pytuple(err, &py_err, py_commit_status,
py_abort_status);
set_aerospike_exc_attrs_using_tuple_of_attrs(py_value, py_err);

// Raise exception
Expand Down
4 changes: 3 additions & 1 deletion test/new_tests/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"msg",
"file",
"line",
"in_doubt"
"in_doubt",
"commit_status",
"abort_status"
],
e.RecordError: [
"key",
Expand Down
6 changes: 4 additions & 2 deletions test/new_tests/test_mrt_functionality.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ def test_commit_fail(self):
}
self.as_connection.put(self.keys[0], {self.bin_name: 1}, policy=policy)
self.as_connection.abort(mrt)
with pytest.raises(e.TransactionAlreadyAborted):
with pytest.raises(e.TransactionAlreadyAborted) as excinfo:
self.as_connection.commit(mrt)
assert excinfo.value.commit_status == aerospike.COMMIT_ALREADY_ABORTED

# Test case 10: Issue abort after issung commit. (P1)
def test_abort_fail(self):
Expand All @@ -100,5 +101,6 @@ def test_abort_fail(self):
}
self.as_connection.put(self.keys[0], {self.bin_name: 1}, policy=policy)
self.as_connection.commit(mrt)
with pytest.raises(e.TransactionAlreadyCommitted):
with pytest.raises(e.TransactionAlreadyCommitted) as excinfo:
self.as_connection.abort(mrt)
assert excinfo.value.abort_status == aerospike.ABORT_ALREADY_COMMITTED

0 comments on commit 2bad34c

Please sign in to comment.