For future reference for anyone else that encountered this problem, my workaround is to wrap all calls to the PhotoKit library with a timeout handler like this:
enum TimeoutError: Error {
case timedOut
}
func withTimeout<T>(_ timeout: TimeInterval, block: @escaping () -> T) throws -> T {
let semaphore = DispatchSemaphore(value: 0)
var result: T? = nil
DispatchQueue.global().async {
result = block()
semaphore.signal()
}
if semaphore.wait(timeout: .now() + timeout) == .timedOut {
// function did not finish in time
throw TimeoutError.timedOut
} else {
// function finished in time
return result!
}
}
A timeout of 5 seconds seems to work well on my machine. If a timeout is thrown, my app will stop processing and try again later. The photolibraryd process still crashes sometimes, but the system seems to recover and the import process in the Photos.app will continue and when my app tries to fetch again it is usually successful.
Not a great solution, but the best I could come up with.
One problem is that sometimes the fetch function (represented by block() in the code above) never returns which results in an orphaned task. I don't think there is a way to kill
this task so it will be orphaned as long as my app is running...