Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: DatabaseQueue restores its read/write abilities when an async read-only database access is cancelled. #1716

Merged
merged 2 commits into from
Feb 4, 2025

Conversation

groue
Copy link
Owner

@groue groue commented Feb 4, 2025

This pull request fixes the bug reported in #1715. Thank you @rcarver!

@groue groue added the bug label Feb 4, 2025
@rcarver
Copy link

rcarver commented Feb 4, 2025

Confirming this works in the app, thank you @groue!

@groue
Copy link
Owner Author

groue commented Feb 4, 2025

Confirming this works in the app, thank you @groue!

Thanks for the confirmation 👍

May I ask how you have noticed the bug in the first place, @rcarver? I'm very glad you did, but the bug is only present with DatabaseQueue, I was wondering why an app would use it instead of a DatabasePool (DatabaseQueue is not a mistake, it's just a missed opportunity of using a pool). If you have a few minutes, would you please explain your setup and how the bug was triggered?

@groue groue merged commit 3535a42 into development Feb 4, 2025
8 checks passed
@groue groue deleted the dev/issue-1715 branch February 4, 2025 19:52
@rcarver
Copy link

rcarver commented Feb 4, 2025

@groue of course! Lucky combination of factors I guess :)

I'm building a new app using the experimental GRDB in Sharing which uses ValueObservation to keep state synced. The app is using TCA which makes use of cancellation when dismissing child features. Finally, since the app is brand new I haven't yet explored DB Pool vs Queue, though it does seem like it will move to Pool when I get to it.

Attached is a simple app that recreates the problem, which I believe is:

  • A child feature that observes a single record is added to a list
  • The child starts deleting itself
  • The parent observes the change and updates state to remove the child
  • By removing the child, its in-flight effects are cancelled, which throws the CancellationError
  • The child deletion effect receives CancellationError and leaves the db in readonly mode

GRDB Cancellation.zip

FWIW we found that the issue can also be solved by:

  • Using the sync version of write
  • Wrapping the async write in an Task such that it is not cancelled when the child is dismissed

@groue
Copy link
Owner Author

groue commented Feb 5, 2025

Oh, thank you. I'll just remind that DatabaseQueue serializes all database accesses, when DatabasePool can run parallel reads and writes. With a pool, the read-only parts of an app can run without blocking or any delay, even if some other component of the app performs a (slow) background write. I don't have the slightest idea whether Sharing and TCA support such parallelization.

Anyway, yes we've been lucky :-)

FWIW we found that the issue can also be solved by:

  • Using the sync version of write
  • Wrapping the async write in an Task such that it is not cancelled when the child is dismissed

Yes. GRDB only honors Task cancellation from its async database access methods (try await read, try await write). Task cancellation is ignored from synchronous accesses. And spawning a new Task is a way to avoid inheriting the cancellation of the parent task.

Happy GRDB!

@groue
Copy link
Owner Author

groue commented Feb 5, 2025

Since I discovered an SQLite bug on the way: I reported it here: Bug report: PRAGMA query_only has side effect when compiled, not when executed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants