diff --git a/GALERA_VERSION b/GALERA_VERSION index 6bc6244c8..3deb1ad1c 100644 --- a/GALERA_VERSION +++ b/GALERA_VERSION @@ -1,5 +1,5 @@ GALERA_VERSION_WSREP_API=26 GALERA_VERSION_MAJOR=4 -GALERA_VERSION_MINOR=26 +GALERA_VERSION_MINOR=27 GALERA_VERSION_EXTRA= GALERA_EPOCH= diff --git a/SConstruct b/SConstruct index d55f513c7..7c3e54055 100644 --- a/SConstruct +++ b/SConstruct @@ -165,7 +165,7 @@ static_ssl = ARGUMENTS.get('static_ssl', None) install = ARGUMENTS.get('install', None) version_script = int(ARGUMENTS.get('version_script', 1)) -GALERA_VER = ARGUMENTS.get('version', '4.26') +GALERA_VER = ARGUMENTS.get('version', '4.27') GALERA_REV = ARGUMENTS.get('revno', 'XXXX') # Attempt to read from file if not given diff --git a/debian/changelog b/debian/changelog index c81a574e7..27fceebb5 100755 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,5 @@ -galera-4 (26.4.26) UNRELEASED; urgency=medium +galera-4 (26.4.27) UNRELEASED; urgency=medium * Galera 4 release - -- Codership Oy Mon, 16 Feb 2026 18:09:55 +0200 + -- Codership Oy Tue, 12 May 2026 12:52:12 +0300 diff --git a/gcache/src/GCache_seqno.cpp b/gcache/src/GCache_seqno.cpp index 222fe0060..589b19ba6 100644 --- a/gcache/src/GCache_seqno.cpp +++ b/gcache/src/GCache_seqno.cpp @@ -220,8 +220,9 @@ namespace gcache old_gap = new_gap; seqno_t const start (idx - 1); - seqno_t const end (seqno - start >= 2*batch_size ? + seqno_t const s_end (seqno - start >= 2*batch_size ? start + batch_size : seqno); + seqno_t const end (std::min(s_end, seqno_locked - 1)); #ifndef NDEBUG if (params.debug()) { @@ -274,6 +275,9 @@ namespace gcache loop = (end < seqno) && loop; + /* Stop if we hit the seqno_locked boundary - no more progress possible */ + if (loop && seqno_released + 1 >= seqno_locked) loop = false; + #ifndef NDEBUG if (params.debug()) { diff --git a/gcache/tests/gcache_top_test.cpp b/gcache/tests/gcache_top_test.cpp index 6a3bc45d9..3a837f2db 100644 --- a/gcache/tests/gcache_top_test.cpp +++ b/gcache/tests/gcache_top_test.cpp @@ -269,6 +269,8 @@ START_TEST(top_level_page_caching_locking) // test that caching in pages work "seqno_min: %" PRId64 " (expected 4)", gc.seqno_min()); gc.seqno_unlock(); + /* Re-release them now as all the locks are removed. */ + gc.seqno_release(12); ps.wait_page_discard(); // pages 2 and 3 should be discarded ck_assert_msg(ps.total_pages() == 2, "total_pages %zu (expected 2)", ps.total_pages()); @@ -284,6 +286,102 @@ START_TEST(top_level_page_caching_locking) // test that caching in pages work } END_TEST + +/* + * Verify that seqno_release() does not free buffers that are locked for IST. + */ +START_TEST(top_level_seqno_lock_protects_ist_buffers) +{ + log_info << "\n#\n# top_level_seqno_lock_protects_ist_buffers\n#"; + const char* const dir_name = ""; + size_t const bh_size = sizeof(gcache::BufferHeader); + size_t const page_size = (8 + bh_size)*3; + + gu::Config cfg; + GCache::register_params(cfg); + cfg.set("gcache.dir", dir_name); + cfg.set("gcache.size", 0); // turn off ring buffer + cfg.set("gcache.page_size", page_size); + cfg.set("gcache.keep_pages_size", 10 * page_size); +#ifndef NDEBUG + cfg.set("gcache.debug", DEBUG); +#endif + + GCache gc(nullptr, cfg, dir_name); + const PageStore& ps(gc.page_store()); + ck_assert_msg(ps.page_size() == page_size, + "ps.page_size: %zu (expected %zu)", + ps.page_size(), page_size); + + std::vector buf; + + mark_point(); + + /* + * 1. Populate 5 pages + */ + for (size_t page_count(1); page_count <= 5; page_count++) + test_caching_fill_page(gc, buf, page_count); + ck_assert_msg(ps.total_pages() == 5, + "total_pages %zu (expected 5)", ps.total_pages()); + + /* + * 2. Assign seqnos + */ + for (size_t seqno(1); seqno <= buf.size(); seqno++) + gc.seqno_assign(buf[seqno - 1], seqno, 0, false); + ck_assert(gc.seqno_min() == 1); + + /* + * 3. Lock seqno 1 + */ + gc.seqno_lock(1); + + /* + * Release up to seqno 5. This MUST NOT free buffers + * at or above seqno_locked (1). + */ + gc.seqno_release(5); + + /* DIAGNOSTIC: check BH_is_released flag after seqno_release */ + for (int i = 0; i < 5; ++i) + { + BufferHeader* const bh = ptr2BH(buf[i]); + ck_assert_msg(!BH_is_released(bh), + "buffer %d (seqno %ld) released despite seqno_lock(1)!", + i, (long)bh->seqno_g); + } + + /* + * Now simulate IST sender reading: seqno_get_buffers() should + * return all 5 buffers since they are protected by the lock. + * Without the seqno_locked guard in seqno_release(), this returns 0. + */ + std::vector got(5); + size_t const n = gc.seqno_get_buffers(got, 1); + ck_assert_msg(n == (size_t)5, + "seqno_get_buffers returned %zu, expected 5. " + "seqno_release() freed locked IST buffers!", + n); + + for (int i = 0; i < 5; i++) + { + ck_assert_msg(got[i].seqno_g() == (seqno_t)(i + 1), + "buffer %d has wrong seqno, expected %d", + i, i + 1); + } + + gc.seqno_unlock(); + + /* Release all remaining buffers so pages can be discarded */ + gc.seqno_release(buf.size()); + ps.wait_page_discard(); + + mark_point(); +} +END_TEST + + Suite* gcache_top_suite() { Suite* s = suite_create("gcache::top-level"); @@ -292,6 +390,7 @@ Suite* gcache_top_suite() tc = tcase_create("test"); tcase_add_test(tc, top_level_page_caching); tcase_add_test(tc, top_level_page_caching_locking); + tcase_add_test(tc, top_level_seqno_lock_protects_ist_buffers); suite_add_tcase(s, tc); return s; diff --git a/scripts/build.sh b/scripts/build.sh index 4faa165b1..72d8fac7e 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -5,7 +5,7 @@ set -eux # $Id$ # Galera library version -VERSION="26.4.26" +VERSION="26.4.27" get_cores() {