馃憢
I'm working on adding a basic job queue backed by sqlite to an ios application. Right now I'm evaluating using ValueObservation for getting the jobs to run. I have something like this in mind:
/// Reaction to a profile, either a "like" or a "dislike"
struct ProfileReaction {
let reaction: Reaction // .like or .dislike
let profile: UUID
}
/// Feed job queue watches for new scheduled ProfileReaction jobs and tries to execute them. Once the job is executed successfully, it is removed from the database.
final class FeedJobQueue {
private let dbQueue: DatabaseQueue
private var cancellable: DatabaseCancellable?
init(dbQueue: DatabaseQueue) {
self.dbQueue = dbQueue
}
func start() {
let observation = ValueObservation.tracking { db in
return db.execute(sql: "select id, reaction, profile_id from profile_reactions where status != 'running'")
}
let queue = DispatchQueue(label: "app.example.feed_job_queue")
self.cancellable = observation.start(
in: dbQueue,
scheduling: .async(onQueue: queue),
onError: { error in fatalError("profile_reactions couldn't be fetched \(error)") },
onChange: { (reactions: reactions) in
try dbQueue.write { db in
try db.execute(
sql: "update profile_reactions set status = 'running' where id in $0",
arguments: [reactions.map { $0.id }]
)
}
execute(reactions) // on successful execution, the job is removed from profile_reactions, and on fail, it's status changes to `failed`
}
)
}
}
I wonder if the approach above can result in duplicate executions for the same job due to some race condition? And also is it possible to not get a notification about a new job in onChange?
I don't think it's applicable, but I can fill these out if needed :)
GRDB flavor(s): (GRDB, SQLCipher, Custom SQLite build?)
GRDB version:
Installation method: (CocoaPods, SPM, manual?)
Xcode version:
Swift version:
Platform(s) running GRDB: (iOS, macOS, watchOS?)
macOS version running Xcode:
Thank you for your work on GRDB!
Hello @ruslandoga,
So you observe pending reactions and turn them to running. All right.
The code itself contains several errors: you don't return any fetched value from the ValueObservation, and your update won't compile because IN $0 is not a valid way to feed a request with an array.
But these problems in your sample code (there are maybe others) are not what this issue is about. You'll deal with them in due time.
I wonder if the approach above can result in duplicate executions for the same job due to some race condition?
Good question. The code does not do what you want.
Look at this scenario:
Both jobs A and B have been started twice!
To fix this, you can only start jobs that were not already started, by checking their state in the database (inside the transaction that sets them to running - transactions are a great tool to avoid race conditions):
@groue Thank you! Your first scenario is exactly what I was afraid of.
But these problems in your sample code (there are maybe others) are not what this issue is about. You'll deal with them in due time.
Right, sorry about these mistakes :) I'm still getting a hang of GRDB and SQlite APIs.
One thing after the other :-) Happy GRDB!