CBLManager with dispatchQueue

Hi,

I’m relatively new to couch base mobile and integrating it with my application (I’m using Swift for development). I have read about the concurrency support and lot of other related documentations but still couldn’t wrap my mind around what objects should be instantiated in the serial queue. Answers to the questions might be obvious but I couldn’t find any article which provides insight into these.

  1. Should CBLManager.sharedInstance().copy() be also be done within the serial queue which would be set as its dispatchQueue?
  2. Once a manager is initialized, should the database creation be done by dispatch_sync(serial_queue) { //create db }?
  3. I have my database object as public and other threads can use that object. Will other threads using it to create/manipulate documents result in crash? or Should I always send a block to doAsync()?
  4. Should I create my CBLReplication objects as well using dispatch_sync(serial_queue) { //createPullReplication }?

Any help would be greatly appreciated! Thanks!

  1. copy is a call to the original CBLManager, so it should be made on the original thread/queue. After you create the copy, pass it to your serial queue and use it there. Don’t forget to set its dispatchQueue property.
  2. I’m not sure from your example. If that manager is created to be used on that queue, you should only call it on that queue.
  3. Yes.
  4. Same answer as #2.

@jens - Thanks a lot for your quick response.

Further on point #2 - Here is the example of how I have done it. Based on your explanation, my understanding is the below code should create/open the database in the specified queue and should never result in failure/crash.

let serialQ: dispatch_queue_t = dispatch_queue_create(Constants.Identifiers.CBSerialQueue, DISPATCH_QUEUE_SERIAL)

self.manager = CBLManager.sharedInstance().copy()
self.manager.dispatchQueue = serialQ
self.manager.storageType = kCBLForestDBStorage

dispatch_sync(serialQ) { 
    self.manager.registerEncryptionKey(key, forDatabaseNamed: databaseName)
        
    let options: CBLDatabaseOptions = CBLDatabaseOptions()
    options.storageType = kCBLForestDBStorage
    options.encryptionKey = key
    options.create = true
        
    do {
        self.database = try self.manager.openDatabaseNamed(databaseName, withOptions: options)
    }
    catch let error as NSError { //Log error }    
}

Further on point #3 - If I have to modify/fetch/delete a document in the database, should those operations be performed by passing a block to doAsync() method? If yes, can I do a callback to another thread (say for example main thread) with the CBLQueryEnumerator?

Can this fetchAll() method be called on main thread? (assume that the amount of data is very limited in the db)

func fetchAll() -> [Model]? {
    
    guard let query = self.database.createAllDocumentsQuery() else { return nil }
    
    do {
        let models:[Model] = []
        let enumerator = try query.run()
        while let row = enumerator.nextRow() {
            //Store content in models array
        }
        
        return models
    }
    catch let error as NSError {
        throw error
    }
}

Further on point #4 - In order to start and stop a replication from any thread, should these methods be the correct approach?

public func startReplication() {
    self.database.doAsync({
        self.pull = self.database.createPullReplication(syncUrl)
        self.pull.channels = [userChannel]
        self.pull.continuous = true
        self.pull.start()
    })
}

public func stopReplication() {
    self.database.doAsync({
        self.pull.stop()
    })
}

Your first snippet looks correct.

If yes, can I do a callback to another thread (say for example main thread) with the CBLQueryEnumerator?

No, because the enumerator object was created from the serial queue’s CBLManager and is therefore tied to the same queue.

Can this fetchAll() method be called on main thread?

No, for the same reason. self.database is tied to the dispatch queue.

The rule is really simple: you can only use a CBL object on the thread/queue you created it on. There are a couple of exceptions that are called out as such in the doc-comments, primarily of course that the result of CBLManager.copy has no thread/queue assigned yet and can be used on the thread/queue you want to assign it to.

Also, just in general, I would be really wary of objects that run code on multiple threads or queues, such as whatever class your example comes from. It becomes very difficult to keep track of which objects are supposed to be used on which thread, and if you make a mistake you can run into very hard-to-debug problems.

It’s much safer to create a separate object for code that needs to run on a different thread.

I do have some cases where an object runs on two threads, but I make sure to label things. For example, instance variables (properties) and methods used on the background thread might have bg_ prefixed to their names.

Thanks for the clarification, its pretty clear on how to handle the CBL objects in specific queue.

One final question :slight_smile:

In a multi-threaded application environment where OperationQueues handle lot of stuff for processing data, it is expected to use different threads for processing the data. So should I always create a new copy of the CBLManager and related CBL objects per thread and never maintain a common shared instance of CBLManager, CBLDatabase & CBLReplication to perform all the CBL activities?

I’m writing a DAL kind of layer to loosely couple CBL (and CoreData) access from other parts of the application. Application may access CBL from any thread and the plan was to funnel them through a single SerialQueue which uses only one shared instance of the manager, associated databases & replication objects. Thats why you see those example methods wrapped in doAsync().

An NSOperationQueue that can run multiple operations at once uses a parallel dispatch queue. CBL doesn’t support parallel dispatch queues.

@jens - Thanks again! Appreciate the help!

I do have one query over this:
Let’s suppose we are maintaining two instances of CBLManager. One instance is on main thread to handle UI. Another instance is on a separate queue (background) to process long running tasks so as to avoid UI blocking. The application requires authentication also so do we need to have CBLReplication instances (push/pull) with authentication for both of the CBLManager instances ? Or should we create second instance of CBLManager only after authentication is done? What about starting / stopping of replication? Will it be required for both of the CBLManager instances?

Thanks

Only one CBLManager needs to run replication, since both of them point to the same physical database. It doesn’t matter which one; the actual replication is done on a background thread so it won’t block the app. You might have the main-thread CBLManager run the replication in case you want to do any UI work in response to its callbacks.