How to execute NSFileCoordinator.coordinate() method without causing a thread lock?

I'm trying to mimic this code I got from Apple's ShapeEdit sample project. The difference is that I use two operation queues -- one to encase the NSFileCoordinator.coordinate() code, and the other as an argument to the coordinate method. I set the maxConcurrentOperationCount to both those queues to 1 because I am performing file operations that have to be done sequentially. If iCloud renames a file and reports it to MetadataQuery, I have to handle the file rename after the file creation. I am trying to figure out how to do this without causing a thread lock, where an operation doesn't finish executing or is not released, so it keeps the other operations behind it from execuing. That is why I am using two operation queues.


I also would like to know where the scope of the file manager variable should be so as not to cause a thread lock because the operation doesn't get released because of variables it received through capturing not being released, thus keeping other operations behind it from even beginning to get executed. I remember documentation about not to do anything inside the coordinate closure except the file process. Is it a good idea to create the variable for the file manager object in the coordinate closure?


There isn't really a need for me to show my code, but I did anyways. I basically explained above what the problem is.


I'm open to any suggestions. I'm quite unsure about what I'm doing. Any help will be appreciated.


Following code segments are edited code from ShapeEdit:


    fileprivate let coordinationQueue: OperationQueue = {
        let coordinationQueue = OperationQueue()
        
        coordinationQueue.name = "com.example.apple-samplecode.ShapeEdit.documentbrowser.coordinationQueue"
        
        return coordinationQueue
    }()


        coordinationQueue.addOperation {

            let readIntent = NSFileAccessIntent.readingIntent(with: templateURL, options: [])

            let writeIntent = NSFileAccessIntent.writingIntent(with: target, options: .forReplacing)
            
            NSFileCoordinator().coordinate(with: [readIntent, writeIntent], queue: self.coordinationQueue) { error in
                if error != nil {
                    return
                }
                
                do {
                    try fileManager.copyItem(at: readIntent.url, to: writeIntent.url)
                    
                    try (writeIntent.url as NSURL).setResourceValue(true, forKey: URLResourceKey.hasHiddenExtensionKey)
                    
                    OperationQueue.main.addOperation {
                        self.openDocumentAtURL(writeIntent.url)
                    }
                }
                catch {
                    fatalError("Unexpected error during trivial file operations: \(error)")
                }
            }
        }


The folowing code segments are my code:


internal let operationQueue: OperationQueue = {
    
    let operationQueue = OperationQueue()
    
    operationQueue.name = "us.gnolaum.IPv4Calculator.operationQueue"
    
    operationQueue.maxConcurrentOperationCount = 1
    
    return operationQueue
    
}()

internal let coordinationQueue: OperationQueue = {
    
    let coordinationQueue = OperationQueue()
    
    coordinationQueue.name = "us.gnolaum.IPv4Calculator.coordinationQueue"
    
    coordinationQueue.maxConcurrentOperationCount = 1
    
    return coordinationQueue
    
}()


        operationQueue.addOperation {
            
            let readIntent = NSFileAccessIntent.readingIntent(with: item.url, options: [])
            
            let destinationURL = self.localDocumentsURL.appendingPathComponent(item.url.lastPathComponent)
            
            let writeIntent = NSFileAccessIntent.writingIntent(with: destinationURL, options: .forReplacing)
            
            NSFileCoordinator().coordinate(with: [readIntent, writeIntent], queue: coordinationQueue) { error in
                if error != nil {
                    return
                }
                
                do {
                    
                    try self.defaultFileManager.copyItem(at: readIntent.url, to: writeIntent.url)
                    
                    print("\(readIntent.url.lastPathComponent) copied to iCloud")
                    
                } catch {
                    
                    print(error.localizedDescription)
                    
                }
                
            }
            
        }

Replies

How to execute NSFileCoordinator.coordinate() method without blocking?

I’m not sure you’re using the term blocking in the standard way. In typical parlance a blocking API is one that has to wait for some operation, like reading from the network, and it blocks the current thread while doing that. For example, the classic BSD

read
system call is blocking; it won’t return until the read is complete or it gets an error [1].

However, the API you’re referring to here,

coordinate(with:queue:byAccessor:)
, is not blocking. It’s says this very clearly in the documentation:

This method lets you asynchronously perform coordinated read or writes … The file coordinator waits asynchronously to get access to the files and then invokes the accessor block on the specified queue.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] You can configure the file descriptor in non-blocking mode and then

read
becomes non-blocking, but it’s not that by default.

I think maybe what I meant to say instead of block was thread lock — where two operations conflict and hang or wait for each other and never finish executing so it keeps the operations behind it from executing.


I also wonder if a variable that an operation receives through capturing would keep the operation from being released?