fix: backup concurrency race on Linux - flush thread ignored isSuspended#3774
Conversation
The background PageManagerFlushThread never checked isSuspended(), so it kept writing pages to database files via FileChannel.write() while the backup's FileInputStream.transferTo() was reading those same files. On Linux's CFS scheduler this race caused partial transaction data in backups (FullBackupIT.fullBackupConcurrency failing with count % 500 \!= 0). - Add deferredByDatabase map: when the background thread polls a batch for a suspended database it moves it to the deferred queue instead of flushing, leaving pageIndex intact - Add waitForCurrentFlushToComplete(Database) to wait out any flush that was already in-progress when setSuspended(true) was called - setSuspended(false) now: (1) synchronously flushes deferred batches while still suspended to preserve commit order, (2) removes the suspend flag, (3) re-enqueues any tail batches that arrived during (1) - Replace the one-shot flushPagesFromQueueToDisk(database, 0L) in suspendFlushAndExecute with waitForCurrentFlushToComplete so the backup only starts reading after the last in-flight write completes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 17 |
TIP This summary will be updated as you push new changes. Give us feedback
There was a problem hiding this comment.
Code Review
This pull request implements a deferred flushing mechanism for suspended databases in the PageManagerFlushThread. It introduces a deferredByDatabase map to hold pages during suspension and updates the setSuspended method to process these pages upon resumption. Review feedback identifies a potential NullPointerException in the new waitForCurrentFlushToComplete method due to multiple calls to an atomic reference and warns of potential data loss in the setSuspended method where using queue.offer with a timeout could fail to re-enqueue deferred batches if the queue is full.
| public void waitForCurrentFlushToComplete(final Database database) throws InterruptedException { | ||
| while (nextPagesToFlush.get() != null && database.equals(nextPagesToFlush.get().database)) | ||
| Thread.sleep(1); | ||
| } |
There was a problem hiding this comment.
The waitForCurrentFlushToComplete method is susceptible to a NullPointerException. The nextPagesToFlush.get() call is performed twice in the while condition. If the background flush thread completes and sets the reference to null between these two calls, the second call will return null, causing an NPE when accessing the .database field. It is safer to capture the result of the first call in a local variable.
| public void waitForCurrentFlushToComplete(final Database database) throws InterruptedException { | |
| while (nextPagesToFlush.get() != null && database.equals(nextPagesToFlush.get().database)) | |
| Thread.sleep(1); | |
| } | |
| public void waitForCurrentFlushToComplete(final Database database) throws InterruptedException { | |
| PagesToFlush current; | |
| while ((current = nextPagesToFlush.get()) != null && database.equals(current.database)) | |
| Thread.sleep(1); | |
| } |
There was a problem hiding this comment.
Fixed in 7d881c8 - capture the reference once per iteration: while ((current = nextPagesToFlush.get()) != null && database.equals(current.database))
| final ConcurrentLinkedQueue<PagesToFlush> newDeferred = deferredByDatabase.remove(database); | ||
| if (newDeferred != null) { | ||
| for (final PagesToFlush batch : newDeferred) { | ||
| try { | ||
| queue.offer(batch, 1, TimeUnit.SECONDS); | ||
| } catch (final InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| break; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
In Phase 3 of setSuspended, using queue.offer(batch, 1, TimeUnit.SECONDS) can lead to silent data loss if the queue is full. Since these batches have already been removed from deferredByDatabase and are tracked in pageIndex, failing to re-enqueue them means they will never be flushed to disk. This could lead to data inconsistency as the system assumes these pages are pending flush. Using queue.put(batch) is more appropriate here to ensure the batches are eventually re-enqueued, even if the calling thread has to block briefly.
| final ConcurrentLinkedQueue<PagesToFlush> newDeferred = deferredByDatabase.remove(database); | |
| if (newDeferred != null) { | |
| for (final PagesToFlush batch : newDeferred) { | |
| try { | |
| queue.offer(batch, 1, TimeUnit.SECONDS); | |
| } catch (final InterruptedException e) { | |
| Thread.currentThread().interrupt(); | |
| break; | |
| } | |
| } | |
| } | |
| final ConcurrentLinkedQueue<PagesToFlush> newDeferred = deferredByDatabase.remove(database); | |
| if (newDeferred != null) { | |
| for (final PagesToFlush batch : newDeferred) { | |
| try { | |
| queue.put(batch); | |
| } catch (final InterruptedException e) { | |
| Thread.currentThread().interrupt(); | |
| break; | |
| } | |
| } | |
| } |
There was a problem hiding this comment.
Fixed in 7d881c8 - Phase 3 now retries queue.offer in a loop (mirroring scheduleFlushOfPages) with a WARNING log on each full-queue retry, so no batch is silently dropped. Also added a comment explaining the tail-append ordering and WAL safety.
Code ReviewGood fix for the backup concurrency race. The approach of deferring batches during suspension is sound. A few issues worth addressing before merging: Bug: NPE race in waitForCurrentFlushToComplete (PageManagerFlushThread.java:202) The flush thread sets PagesToFlush current;
while ((current = nextPagesToFlush.get()) != null && database.equals(current.database))
Thread.sleep(1);Bug: DatabaseMetadataException not caught in deferred flush path (PageManagerFlushThread.java:221-224) The main flush path catches Bug: Silent page loss in Phase 3 (PageManagerFlushThread.java:240) The return value of Concern: Phase 3 appends deferred batches to the tail of the queue Deferred batches (committed before suspension was released) are re-enqueued at the back via Minor: Busy-wait with Thread.sleep(1) Both Overall the fix addresses the real root cause. The NPE and the silent page drop are the most urgent items to fix. |
…page loss - waitForCurrentFlushToComplete: capture nextPagesToFlush.get() once per iteration to avoid NPE between null-check and .database access - setSuspended Phase 1: add DatabaseMetadataException catch to mirror the main flush path (file-deleted case) - setSuspended Phase 3: retry queue.offer in a loop instead of fire-and-forget to prevent silent page loss when queue is full; add comment explaining tail-append ordering and WAL safety guarantee Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code ReviewGood fix for the backup concurrency race - the deferred-flush design is the right approach. Note: a prior automated review left some inaccurate observations; this review corrects those. Correction of prior review claims The previous automated review flagged two issues that are NOT bugs in the actual code:
Real issue: InterruptedException in Phase 3 silently drops batches The Suggested fix - label the outer loop and break from it: outerLoop:
for (final PagesToFlush batch : newDeferred) {
while (running) {
try {
if (queue.offer(batch, 1, TimeUnit.SECONDS))
break;
LogManager.instance().log(this, Level.WARNING,
"Page flush queue is full while re-enqueueing deferred batch for database '%s'; retrying", database.getName());
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
LogManager.instance().log(this, Level.SEVERE,
"Interrupted while re-enqueueing deferred batches for database '%s'; remaining batches may be lost",
database.getName());
break outerLoop;
}
}
}Type safety: unconditional cast to Database
if (database == null && pagesToFlush.database instanceof final Database db && isSuspended(db)) {
deferredByDatabase.computeIfAbsent(db, k -> new ConcurrentLinkedQueue<>()).offer(pagesToFlush);
return;
}Minor: Functional for backup scenarios (infrequent), but a Overall The core design is solid: deferring batches during suspension, flushing synchronously in Phase 1, and re-enqueueing Phase 3 stragglers is the right approach. The ordering concern (Phase 3 batches appended behind newer commits) is well-documented and safe via WAL. The |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3774 +/- ##
==========================================
+ Coverage 65.03% 65.13% +0.10%
==========================================
Files 1580 1580
Lines 116275 116313 +38
Branches 24659 24669 +10
==========================================
+ Hits 75615 75758 +143
+ Misses 30350 30241 -109
- Partials 10310 10314 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code ReviewOverall this is a well-motivated fix addressing a real race condition on Linux where the background flush thread was writing pages concurrently with backup reads. The approach (deferred-flush queue per database) is sound. A few issues worth discussing: Bug:
|
…ded (ArcadeData#3774) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ip ci] Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.7 to 42.7.8. Release notes *Sourced from [org.postgresql:postgresql's releases](https://github.com/pgjdbc/pgjdbc/releases).* > v42.7.8 > ------- > > Notable changes: > ---------------- > > * Releases are signed with a new PGP key which is generated at GitHub Actions and stored only there [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3701](https://redirect.github.com/pgjdbc/pgjdbc/issues/3701)) > > Changes > ------- > > * fix: Update release plugin config to use .set(...) for props and inject nexus secrets via props [`@sehrope`](https://github.com/sehrope) ([ArcadeData#3802](https://redirect.github.com/pgjdbc/pgjdbc/issues/3802)) > * update version to 42.7.8 [`@davecramer`](https://github.com/davecramer) ([ArcadeData#3801](https://redirect.github.com/pgjdbc/pgjdbc/issues/3801)) > * change logs for version 42.7.8 [`@davecramer`](https://github.com/davecramer) ([ArcadeData#3797](https://redirect.github.com/pgjdbc/pgjdbc/issues/3797)) > * Fix getNotifications() documentation [`@pdewacht`](https://github.com/pdewacht) ([ArcadeData#3800](https://redirect.github.com/pgjdbc/pgjdbc/issues/3800)) > * fix(deps): update dependency om.ongres.scram:scram-client to 3.2 [`@jorsol`](https://github.com/jorsol) ([ArcadeData#3799](https://redirect.github.com/pgjdbc/pgjdbc/issues/3799)) > * Add configurable boolean-to-numeric conversion for ResultSet getters [`@vwassan`](https://github.com/vwassan) ([ArcadeData#3796](https://redirect.github.com/pgjdbc/pgjdbc/issues/3796)) > * Update CONTRIBUTING.md [`@davecramer`](https://github.com/davecramer) ([ArcadeData#3794](https://redirect.github.com/pgjdbc/pgjdbc/issues/3794)) > * perf: remove QUERY\_ONESHOT flag when calling getMetaData [`@ShenFeng312`](https://github.com/ShenFeng312) ([ArcadeData#3783](https://redirect.github.com/pgjdbc/pgjdbc/issues/3783)) > * test: add bench for batch insert via unnest with arrays [`@lantalex`](https://github.com/lantalex) ([ArcadeData#3782](https://redirect.github.com/pgjdbc/pgjdbc/issues/3782)) > * fix: Change "PST" timezone in TimestampTest to "Pacific Standard Time" [`@simon-greatrix`](https://github.com/simon-greatrix) ([ArcadeData#3774](https://redirect.github.com/pgjdbc/pgjdbc/issues/3774)) > * Use `BufferedInputStream` with `FileInputStream` [`@jgardn3r`](https://github.com/jgardn3r) ([ArcadeData#3750](https://redirect.github.com/pgjdbc/pgjdbc/issues/3750)) > * Fix [ArcadeData#3747](https://redirect.github.com/pgjdbc/pgjdbc/issues/3747): Incorrect class comparison in PGXmlFactoryFactory validation [`@eitch`](https://github.com/eitch) ([ArcadeData#3748](https://redirect.github.com/pgjdbc/pgjdbc/issues/3748)) > * fix: traverse the current dimension to get the correct pos in PgArray#calcRemainingDataLength [`@sly461`](https://github.com/sly461) ([ArcadeData#3746](https://redirect.github.com/pgjdbc/pgjdbc/issues/3746)) > * test: add channelBinding to SslTest [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3665](https://redirect.github.com/pgjdbc/pgjdbc/issues/3665)) > * fix: remove excessive ReentrantLock.lock usages [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3703](https://redirect.github.com/pgjdbc/pgjdbc/issues/3703)) > * test: add ossf-scorecard security scanning [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3695](https://redirect.github.com/pgjdbc/pgjdbc/issues/3695)) > * fix indentation to let CI pass [`@mohitsatr`](https://github.com/mohitsatr) ([ArcadeData#3682](https://redirect.github.com/pgjdbc/pgjdbc/issues/3682)) > * test: extract pgjdbc/testFixtures to testkit project [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3666](https://redirect.github.com/pgjdbc/pgjdbc/issues/3666)) > * fix: make sure getImportedExportedKeys returns columns in consistent order [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3663](https://redirect.github.com/pgjdbc/pgjdbc/issues/3663)) > * feat: use PreparedStatement for DatabaseMetaData.getCrossReference, getImportedKeys, getExportedKeys [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3641](https://redirect.github.com/pgjdbc/pgjdbc/issues/3641)) > * Add "SELF\_REFERENCING\_COL\_NAME" field to getTables' ResultSetMetaData to fix NullPointerException [`@SophiahHo`](https://github.com/SophiahHo) ([ArcadeData#3660](https://redirect.github.com/pgjdbc/pgjdbc/issues/3660)) > > 🐛 Bug Fixes > ----------- > > * fix: avoid IllegalStateException: Timer already cancelled when StatementCancelTimerTask.run throws a runtime error [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3778](https://redirect.github.com/pgjdbc/pgjdbc/issues/3778)) > * fix: avoid NullPointerException when cancelling a query if cancel key is not known yet [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3780](https://redirect.github.com/pgjdbc/pgjdbc/issues/3780)) > * fix: unable to open replication connection to servers < 12 [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3678](https://redirect.github.com/pgjdbc/pgjdbc/issues/3678)) > > 🧰 Maintenance > ------------- > > * chore: fix published project name [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3809](https://redirect.github.com/pgjdbc/pgjdbc/issues/3809)) > * chore: update publish to Central Portal task name after bumping nmcp [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3808](https://redirect.github.com/pgjdbc/pgjdbc/issues/3808)) > * fix(deps): update com.gradleup.nmcp to 1.1.0 [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3807](https://redirect.github.com/pgjdbc/pgjdbc/issues/3807)) > * Revert "fix: Update release plugin config to use .set(...) for props and inject nexus creds via gradle props" [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3803](https://redirect.github.com/pgjdbc/pgjdbc/issues/3803)) > * chore: group com.gradleup.nmcp version updates [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3805](https://redirect.github.com/pgjdbc/pgjdbc/issues/3805)) > * chore: use bump org.apache.bcel:bcel test dependency in testCompileClasspath as well [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3775](https://redirect.github.com/pgjdbc/pgjdbc/issues/3775)) > * Fix typo in PGReplicationStream.java [`@atorik`](https://github.com/atorik) ([ArcadeData#3758](https://redirect.github.com/pgjdbc/pgjdbc/issues/3758)) > * chore: remove JDK versions from the key workflow names [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3759](https://redirect.github.com/pgjdbc/pgjdbc/issues/3759)) > * chore: add GitHub Actions workflow for generating release PGP key [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3701](https://redirect.github.com/pgjdbc/pgjdbc/issues/3701)) > * chore: replace StandardCharsets with Charsets to simplify code [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3751](https://redirect.github.com/pgjdbc/pgjdbc/issues/3751)) > * chore: migrate publish workflow to Central Portal publishing via com.gradleup.nmcp [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3686](https://redirect.github.com/pgjdbc/pgjdbc/issues/3686)) > * chore: adjust the default branch name for ossf scorecard scan [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3697](https://redirect.github.com/pgjdbc/pgjdbc/issues/3697)) > * chore: add top-level read-only permissions for GitHub Actions when missing [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3696](https://redirect.github.com/pgjdbc/pgjdbc/issues/3696)) > * chore: use config:best-practices preset for Renovate [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3687](https://redirect.github.com/pgjdbc/pgjdbc/issues/3687)) ... (truncated) Changelog *Sourced from [org.postgresql:postgresql's changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md).* > [42.7.8] (2025-09-18) > --------------------- > > ### Added > > * feat: Add configurable boolean-to-numeric conversion for ResultSet getters [PR [ArcadeData#3796](https://redirect.github.com/pgjdbc/pgjdbc/issues/3796)]([pgjdbc/pgjdbc#3796](https://redirect.github.com/pgjdbc/pgjdbc/pull/3796)) > > ### Changed > > * perf: remove QUERY\_ONESHOT flag when calling getMetaData [PR [ArcadeData#3783](https://redirect.github.com/pgjdbc/pgjdbc/issues/3783)]([pgjdbc/pgjdbc#3783](https://redirect.github.com/pgjdbc/pgjdbc/pull/3783)) > * perf: use `BufferedInputStream` with `FileInputStream` [PR [ArcadeData#3750](https://redirect.github.com/pgjdbc/pgjdbc/issues/3750)]([pgjdbc/pgjdbc#3750](https://redirect.github.com/pgjdbc/pgjdbc/pull/3750)) > * perf: enable server-prepared statements for DatabaseMetaData > > ### Fixed > > * fix: avoid NullPointerException when cancelling a query if cancel key is not known yet > * fix: Change "PST" timezone in TimestampTest to "Pacific Standard Time" [PR [ArcadeData#3774](https://redirect.github.com/pgjdbc/pgjdbc/issues/3774)]([pgjdbc/pgjdbc#3774](https://redirect.github.com/pgjdbc/pgjdbc/pull/3774)) > * fix: traverse the current dimension to get the correct pos in PgArray#calcRemainingDataLength [PR [ArcadeData#3746](https://redirect.github.com/pgjdbc/pgjdbc/issues/3746)]([pgjdbc/pgjdbc#3746](https://redirect.github.com/pgjdbc/pgjdbc/pull/3746)) > * fix: make sure getImportedExportedKeys returns columns in consistent order > * fix: Add "SELF\_REFERENCING\_COL\_NAME" field to getTables' ResultSetMetaData to fix NullPointerException [PR [ArcadeData#3660](https://redirect.github.com/pgjdbc/pgjdbc/issues/3660)]([pgjdbc/pgjdbc#3660](https://redirect.github.com/pgjdbc/pgjdbc/pull/3660)) > * fix: unable to open replication connection to servers < 12 > * fix: avoid closing statement caused by driver's internal ResultSet#close() > * fix: return empty metadata for empty catalog names as it was before > * fix: Incorrect class comparison in PGXmlFactoryFactory validation Commits * [`9a5492d`](pgjdbc/pgjdbc@9a5492d) chore: fix published project name * [`ca064f8`](pgjdbc/pgjdbc@ca064f8) chore: update publish to Central Portal task name after bumping nmcp * [`3d97bb8`](pgjdbc/pgjdbc@3d97bb8) fix: avoid IllegalStateException: Timer already cancelled when StatementCanc... * [`faa7dfc`](pgjdbc/pgjdbc@faa7dfc) test: move BaseTest4 to testkit module * [`dbf2847`](pgjdbc/pgjdbc@dbf2847) fix(deps): update com.gradleup.nmcp to 1.1.0 * [`9245e26`](pgjdbc/pgjdbc@9245e26) Revert "fix: Update release plugin config to use .set(...) for props and inje... * [`8e833c3`](pgjdbc/pgjdbc@8e833c3) chore: group com.gradleup.nmcp version updates * [`ec5a088`](pgjdbc/pgjdbc@ec5a088) fix: Update release plugin config to use .set(...) for props and inject nexus... * [`c03db58`](pgjdbc/pgjdbc@c03db58) update version to 42.7.8 ([ArcadeData#3801](https://redirect.github.com/pgjdbc/pgjdbc/issues/3801)) * [`50ff169`](pgjdbc/pgjdbc@50ff169) change logs for version 42.7.8 ([ArcadeData#3797](https://redirect.github.com/pgjdbc/pgjdbc/issues/3797)) * Additional commits viewable in [compare view](pgjdbc/pgjdbc@REL42.7.7...REL42.7.8) [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- Dependabot commands and options You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
…p ci] Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.21.0 to 5.22.0. Release notes *Sourced from [org.mockito:mockito-core's releases](https://github.com/mockito/mockito/releases).* > v5.22.0 > ------- > > *Changelog generated by [Shipkit Changelog Gradle Plugin](https://github.com/shipkit/shipkit-changelog)* > > #### 5.22.0 > > * 2026-02-27 - [6 commit(s)](mockito/mockito@v5.21.0...v5.22.0) by Joshua Selbo, NiMv1, Rafael Winterhalter, dependabot[bot], eunbin son > * Avoid mocking of internal static utilities [([ArcadeData#3785](https://redirect.github.com/mockito/mockito/issues/3785))]([mockito/mockito#3785](https://redirect.github.com/mockito/mockito/pull/3785)) > * Bump graalvm/setup-graalvm from 1.4.4 to 1.4.5 [([ArcadeData#3780](https://redirect.github.com/mockito/mockito/issues/3780))]([mockito/mockito#3780](https://redirect.github.com/mockito/mockito/pull/3780)) > * Static mocking of UUID.class corrupted under JDK 25 [([ArcadeData#3778](https://redirect.github.com/mockito/mockito/issues/3778))]([mockito/mockito#3778](https://redirect.github.com/mockito/mockito/issues/3778)) > * Bump actions/upload-artifact from 5 to 6 [([ArcadeData#3774](https://redirect.github.com/mockito/mockito/issues/3774))]([mockito/mockito#3774](https://redirect.github.com/mockito/mockito/pull/3774)) > * docs: clarify RETURNS\_MOCKS behavior with sealed abstract enums (Java 15+) [([ArcadeData#3773](https://redirect.github.com/mockito/mockito/issues/3773))]([mockito/mockito#3773](https://redirect.github.com/mockito/mockito/pull/3773)) > * Add tests for Sets utility class [([ArcadeData#3771](https://redirect.github.com/mockito/mockito/issues/3771))]([mockito/mockito#3771](https://redirect.github.com/mockito/mockito/pull/3771)) > * Add core API to enable Kotlin singleton mocking [([ArcadeData#3762](https://redirect.github.com/mockito/mockito/issues/3762))]([mockito/mockito#3762](https://redirect.github.com/mockito/mockito/pull/3762)) > * Stubbing Kotlin `object` singletons [([ArcadeData#3652](https://redirect.github.com/mockito/mockito/issues/3652))]([mockito/mockito#3652](https://redirect.github.com/mockito/mockito/issues/3652)) > * Incorrect documentation for RETURNS\_MOCKS [([ArcadeData#3285](https://redirect.github.com/mockito/mockito/issues/3285))]([mockito/mockito#3285](https://redirect.github.com/mockito/mockito/issues/3285)) Commits * [`25f1395`](mockito/mockito@25f1395) Add core API to enable Kotlin singleton mocking ([ArcadeData#3762](https://redirect.github.com/mockito/mockito/issues/3762)) * [`ef9ee55`](mockito/mockito@ef9ee55) Avoids mocking private static methods, as well as package-private static meth... * [`d16fcfc`](mockito/mockito@d16fcfc) Bump graalvm/setup-graalvm from 1.4.4 to 1.4.5 ([ArcadeData#3780](https://redirect.github.com/mockito/mockito/issues/3780)) * [`27eb8a3`](mockito/mockito@27eb8a3) Clarify `RETURNS_MOCKS` behavior with sealed abstract enums (Java 15+) ([ArcadeData#3773](https://redirect.github.com/mockito/mockito/issues/3773)) * [`9e5d449`](mockito/mockito@9e5d449) Add tests for Sets utility class ([ArcadeData#3771](https://redirect.github.com/mockito/mockito/issues/3771)) * [`8d9a62f`](mockito/mockito@8d9a62f) Bump actions/upload-artifact from 5 to 6 ([ArcadeData#3774](https://redirect.github.com/mockito/mockito/issues/3774)) * See full diff in [compare view](mockito/mockito@v5.21.0...v5.22.0) [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- Dependabot commands and options You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
…ded (ArcadeData#3774) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ded (ArcadeData#3774) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ded (ArcadeData#3774) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
PageManagerFlushThreadnever checkedisSuspended()in its run loop, so the background thread kept writing pages to database files viaFileChannel.write()while the backup'sFileInputStream.transferTo()was reading those same filesFullBackupIT.fullBackupConcurrencyto fail withcount % 500 != 0(partial transaction in backup)setSuspended(false)now synchronously flushes deferred batches (preserving commit order), then re-enables normal async flushingflushPagesFromQueueToDisk(database, 0L)pre-backup call withwaitForCurrentFlushToComplete(database)to properly wait out any in-progress writeTest plan
FullBackupIT#fullBackupConcurrencypasses (was failing on Linux CI)FullBackupITsuite (6 tests) passes locally🤖 Generated with Claude Code