From f79c75cbefea287ef30915a0b14ef25529388709 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 7 May 2026 19:15:01 -0400 Subject: [PATCH] ext/dba: end LMDB cursor iteration on write/delete dba_insert/replace/delete on an LMDB handler started a write txn into LMDB_IT(txn) while dba_firstkey's cursor was still bound to the read txn in that slot, so the next dba_nextkey renewed a freed handle. Close the cursor and abort the read txn before starting the write txn, and bail out of dba_nextkey when no cursor is open. --- ext/dba/dba_lmdb.c | 16 ++++++ .../dba_lmdb_write_during_iteration.phpt | 51 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 ext/dba/tests/dba_lmdb_write_during_iteration.phpt diff --git a/ext/dba/dba_lmdb.c b/ext/dba/dba_lmdb.c index 57e74f320239..f696e97c6e30 100644 --- a/ext/dba/dba_lmdb.c +++ b/ext/dba/dba_lmdb.c @@ -172,6 +172,12 @@ DBA_UPDATE_FUNC(lmdb) int rc; MDB_val k, v; + if (LMDB_IT(cur)) { + mdb_cursor_close(LMDB_IT(cur)); + LMDB_IT(cur) = NULL; + mdb_txn_abort(LMDB_IT(txn)); + } + rc = mdb_txn_begin(LMDB_IT(env), NULL, 0, &LMDB_IT(txn)); if (rc) { php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc)); @@ -243,6 +249,12 @@ DBA_DELETE_FUNC(lmdb) int rc; MDB_val k; + if (LMDB_IT(cur)) { + mdb_cursor_close(LMDB_IT(cur)); + LMDB_IT(cur) = NULL; + mdb_txn_abort(LMDB_IT(txn)); + } + rc = mdb_txn_begin(LMDB_IT(env), NULL, 0, &LMDB_IT(txn)); if (rc) { php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc)); @@ -314,6 +326,10 @@ DBA_NEXTKEY_FUNC(lmdb) MDB_val k, v; zend_string *ret = NULL; + if (!LMDB_IT(cur)) { + return NULL; + } + rc = mdb_txn_renew(LMDB_IT(txn)); if (rc) { php_error_docref(NULL, E_WARNING, "%s", mdb_strerror(rc)); diff --git a/ext/dba/tests/dba_lmdb_write_during_iteration.phpt b/ext/dba/tests/dba_lmdb_write_during_iteration.phpt new file mode 100644 index 000000000000..a5891d5b2d66 --- /dev/null +++ b/ext/dba/tests/dba_lmdb_write_during_iteration.phpt @@ -0,0 +1,51 @@ +--TEST-- +DBA LMDB: writes during cursor iteration end the iteration cleanly +--EXTENSIONS-- +dba +--SKIPIF-- + +--FILE-- + '1', 'b' => '2', 'c' => '3'] as $k => $v) { + dba_replace($k, $v, $db); +} + +var_dump(dba_firstkey($db)); +var_dump(dba_nextkey($db)); + +var_dump(dba_replace('d', '4', $db)); +var_dump(dba_nextkey($db)); +var_dump(dba_nextkey($db)); + +var_dump(dba_firstkey($db)); +var_dump(dba_delete('a', $db)); +var_dump(dba_nextkey($db)); + +dba_close($db); +?> +--CLEAN-- + +--EXPECTF-- +Notice: dba_open(): Handler lmdb does locking internally in %s on line %d +string(1) "a" +string(1) "b" +bool(true) +bool(false) +bool(false) +string(1) "a" +bool(true) +bool(false)