Skip to content

Realm-Queue-refactoring#3463

Merged
marinofaggiana merged 48 commits intomasterfrom
realmQueue
May 9, 2025
Merged

Realm-Queue-refactoring#3463
marinofaggiana merged 48 commits intomasterfrom
realmQueue

Conversation

@marinofaggiana
Copy link
Member

@marinofaggiana marinofaggiana commented May 6, 2025

  1. performRealmRead(_ block: (Realm) throws -> T?) -> T?

Purpose:
This function is designed to safely perform read operations from Realm within a serial queue. It uses the provided block of code to interact with the database and handles any read errors.

How it works:
• realmQueue.sync: Ensures that Realm operations are executed synchronously, preventing concurrency issues between reads.
• Block block(realm): This block is passed as an argument and is executed using the provided Realm instance. The block can return a value of generic type T?.
• Error handling: If an error occurs during the read (e.g., a database access issue), an error log is recorded, and the function returns nil.

Typical use case:
• Safe read operations from Realm: It’s used to retrieve data from Realm without worrying about managing queues or database access errors.

  1. performRealmWrite(sync: Bool = true, _ block: @escaping (Realm) throws -> Void)

Purpose:
This function is used to perform write operations on Realm safely, allowing synchronization or asynchronous execution depending on the sync parameter.

How it works:
• Block executionBlock: The block that contains the write logic. This block is passed as a parameter and performs the write operation inside a Realm write transaction.
• realmQueue.sync or realmQueue.async: Depending on the value of sync, the operation is executed either synchronously (sync) or asynchronously (async). The write operation doesn’t block the UI thread, thanks to the execution queue.
• autoreleasepool: Ensures that temporary objects created during the write are properly deallocated.
• Error handling: If an error occurs during the write, an error log is recorded, but no exception is thrown. The app continues running without crashing.

Typical use case:
• Safe write operations to Realm: It’s used to perform write operations in a safe context, ensuring that concurrency is properly handled.
• Asynchronous or synchronous execution: The function allows you to choose whether to execute the write operation synchronously (where the calling thread waits for completion) or asynchronously (where the operation runs in the background).

Why these functions are useful:
• Safe synchronization of read and write operations: They prevent conflicts between simultaneous operations on Realm, ensuring that data is not read or written concurrently from different threads.
• Automatic error handling: Errors are logged without causing app crashes, allowing them to be handled with logs and debugging, rather than stopping the app.
• Support for both asynchronous and synchronous operations: The function gives you the flexibility to choose how to perform operations based on the app’s needs, improving performance or UI responsiveness.

example:

func addMetadatas(_ metadatas: [tableMetadata]) {
        performRealmWrite { realm in
            for metadata in metadatas {
                realm.create(tableMetadata.self, value: metadata, update: .all)
            }
        }
 }
func getResultMetadata(predicate: NSPredicate) -> tableMetadata? {
        return performRealmRead { realm in
            realm.objects(tableMetadata.self)
                .filter(predicate)
                .first
        }
 }

Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
@marinofaggiana marinofaggiana marked this pull request as draft May 6, 2025 15:52
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
@marinofaggiana marinofaggiana marked this pull request as ready for review May 7, 2025 15:41
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
@marinofaggiana
Copy link
Member Author

When the app transitions to the background, certain operations on Realm—especially writes, but potentially also reads—can become dangerous. Specifically:
• Realm may enter an inconsistent state if a write is in progress while the app is suspended.
• If the app is reactivated quickly (e.g., within a few seconds), an interrupted operation might cause a crash or database corruption.
• Realm operations are not always safe to execute unless you’re sure the environment is still active and stable.

The isAppSuspending variable serves as a central control flag, updated during key points in the app’s lifecycle:
• true → When the app enters the background or is about to be suspended (didEnterBackgroundNotification, etc.).
• false → When the app comes back to the foreground or when a valid background task is explicitly started (handleAppRefreshProcessingTask, etc.).

This flag is then checked inside performRealmRead and performRealmWrite to decide whether to:
• Actually execute the Realm operation (only if the app is in a safe state).
• Skip it (safely and silently) to prevent crashes or unsafe access.

Why It’s a Smart Choice
• 🔒 Safety: Prevents potentially dangerous Realm operations.
• ⚙️ Centralized control: Easy to use throughout the codebase.
• 🧩 Compatible with background tasks: You can safely override the flag when handling BGProcessingTask or similar.
• 💡 Clean and readable: Avoids scattered checks and keeps logic consistent.

What We Avoided
• ❌ Arbitrary timers (DispatchQueue.main.asyncAfter) that are not guaranteed to run in background.
• ❌ Multiple overlapping flags like isAppActive or isBackgroundTaskActive.
• ❌ Blindly blocking all background operations, even valid ones (e.g., inside handleProcessingTask).

Background Execution Control – Design Rationale

To ensure stability and data integrity when the app transitions between foreground and background states, we use two flags:
• isAppSuspending: Set to true during the critical moment when the app is transitioning into a suspended state. While this flag is true, no Realm reads or writes should occur, as any disk access may crash the app or be abruptly interrupted by the system.
• isAppInBackground: Set to true when the app is running in background but still eligible for background execution (e.g., via URLSession, background processing tasks, or other registered capabilities). During this state, asynchronous Realm writes are allowed, but synchronous operations are avoided to prevent deadlocks or delays.

This separation gives us fine-grained control:
• Reads and writes are safely blocked during suspension.
• Asynchronous writes continue when in background, preserving sync and update flows without compromising safety.
• Normal operations resume when the app returns to the foreground.

Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
@marinofaggiana marinofaggiana merged commit 3b15de7 into master May 9, 2025
5 checks passed
@github-project-automation github-project-automation bot moved this from 🏗️ In progress to ☑️ Done in 🤖 🍏 Mobile clients team May 9, 2025
@marinofaggiana marinofaggiana deleted the realmQueue branch May 9, 2025 06:06
@mpivchev
Copy link
Collaborator

mpivchev commented May 9, 2025

Thanks for the extensive explanation <3 @marinofaggiana

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

3 participants