Crash: _NSMetadataQueryResultArray objectAtIndex

Hello, sometimes if I use NSMetadataQuery to obervse my file changes on macOS, it crash for this reason, its odd and i have no clue for this problem becuse in my code I never get results using index, anyone help? thanks!

Application Specific Information:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[_NSMetadataQueryResultArray objectAtIndex:]: index (251625) out of bounds (251625)'
terminating with uncaught exception of type NSException
abort() called
Answered by DTS Engineer in 798881022

Thanks for the latest info.

Based on that it looks like you’re using metadataQuery from two different contexts:

  • The main thread, where your getAllResults() method is accessing the results property (this is based on frame 6 of the crash report backtrace I posted earlier).

  • The custom serial operation queue (workerQueue) which is backed by a Dispatch serial queue (dispatchQueue).

That’s a concern. AFAICT this is meant to work, but any sort of concurrency bug in NSMetadataQuery could trigger exactly this crash.

The ‘obvious’ next step would be to do all that work from a single context, and that means your serial queue. I’m imagining code like this:

func getAllResults(…) … {
    let results = dispatchQueue.sync {
        metadataQuery.results
    }
    …
}

Now, if you could reproduce this crash then I’d tell you to go ahead and make this change in a branch, test it, and see if it helps. However, it sounds like you’re debugging this based on crash reports coming in from the field, and that makes things trickier. You’d have to incorporate this change into your mainline product, and that engenders some risk. Specifically, any time you do a sync dispatch from the main thread to a secondary thread you run the risk of deadlock. The code running within your sync closure is small, so I think it’s safe, but there’s always a risk.

If your product has a significant beta test programme, you could make this change in a beta release and see if causes problems in practice.

Of course, it’s still not clear whether this change will actually help prevent the problem )-:

Sorry that I don’t have all the answers here. Concurrency is hard™.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I know the issue is maybe I’m trying to access an element at index is out of the bounds of the array, but how can I avoid this because in my code I never use an array.

sometimes if I use NSMetadataQuery to obervse my file changes on macOS, it crash for this reason

Please post a full crash report. See Posting a Crash Report for hints on how to do that.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I am experiencing the same crash, and have submitted a feedback with a crash report. FB14607400

Well, that’s definitely the same crash as reported by Apple dicker.

The crashing thread looks like this:

Last Exception Backtrace:
0   CoreFoundation      … __exceptionPreprocess + 164 (NSException.m:249)
1   libobjc.A.dylib     … objc_exception_throw + 60 (objc-exception.mm:356)
2   Foundation          … -[_NSMetadataQueryResultArray objectAtIndex:] + 208 (NSMetadata.m:1708)
3   CoreFoundation      … -[NSArray getObjects:range:] + 136 (NSArray.m:261)
4   CoreFoundation      … -[NSArray initWithArray:range:copyItems:] + 240 (NSArray.m:735)
5   Foundation          … static Array._unconditionallyBridgeFromObjectiveC(_:) + 168 (:-1)
6   PpppppNnnnnnFffffff … DocumentBrowserQuery.getAllResults() + 236 (DocumentBrowserQuery.swift:76)

What does the code at line 76 of DocumentBrowserQuery.swift look like?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I think that particular trace is from a slightly older version, and that line was:

return metadataQuery.results.compactMap { ($0 as? NSMetadataItem)?.url }

We have since changed it to the version below, thinking that if a mutation was occurring in another thread that copying it first might avoid the crash (it didn't help):

let results = metadataQuery.results
return results.compactMap { ($0 as? NSMetadataItem)?.url }

Is the issue that results is not a real Swift array, but rather a bridged mutable ObjC array that is getting mutated concurrently in another thread?

If so, how would we avoid this crash? The docs for results say:

Accessing this property implicitly disables updates, and enables updates again once the resulting array is deallocated.

So my assumption was that other threads should not be mutating it while I am accessing it.

Is the issue that results is not a real Swift array

That much is certainly true.

but rather a bridged mutable ObjC array that is getting mutated concurrently in another thread?

It’s not that simple. It’s true your Swift array is operating an on Objective-C array. The tricky thing here is that it’s not a standard NSArray or NSMutableArray, but something unique to NSMetadataQuery. Hence the _NSMetadataQueryResultArray in frame 2.

Remember that, in Cocoa, NSArray is supports subclassing [1] and, while custom subclasses are relatively rare, you do still bump into them from time to time.

I would expect that the bridge into Swift would copy the array to avoid any potential for weird behaviours and, based on frames 5 and 4 in your crash record, I suspect that’s actually what’s happening here. It’s just that the copy is triggering this crash.

When you set up your NSMetadataQuery, what did you set the operationQueue property to?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Instead, NSArray is a class cluster.

We create the queue like this:

dispatchQueue = DispatchQueue(label: "com.***.***.DocumentBrowserQuery",
                                              qos: .utility,
                                              autoreleaseFrequency: .workItem)

workerQueue = OperationQueue()
workerQueue.name = "com.***.***.workerQueue"
workerQueue.qualityOfService = .utility
workerQueue.maxConcurrentOperationCount = 1
workerQueue.underlyingQueue = dispatchQueue

metadataQuery.operationQueue = workerQueue

I attached the entire implementation of our DocumentBrowserQuery to FB14607400, in case that is helpful.

Thanks for the latest info.

Based on that it looks like you’re using metadataQuery from two different contexts:

  • The main thread, where your getAllResults() method is accessing the results property (this is based on frame 6 of the crash report backtrace I posted earlier).

  • The custom serial operation queue (workerQueue) which is backed by a Dispatch serial queue (dispatchQueue).

That’s a concern. AFAICT this is meant to work, but any sort of concurrency bug in NSMetadataQuery could trigger exactly this crash.

The ‘obvious’ next step would be to do all that work from a single context, and that means your serial queue. I’m imagining code like this:

func getAllResults(…) … {
    let results = dispatchQueue.sync {
        metadataQuery.results
    }
    …
}

Now, if you could reproduce this crash then I’d tell you to go ahead and make this change in a branch, test it, and see if it helps. However, it sounds like you’re debugging this based on crash reports coming in from the field, and that makes things trickier. You’d have to incorporate this change into your mainline product, and that engenders some risk. Specifically, any time you do a sync dispatch from the main thread to a secondary thread you run the risk of deadlock. The code running within your sync closure is small, so I think it’s safe, but there’s always a risk.

If your product has a significant beta test programme, you could make this change in a beta release and see if causes problems in practice.

Of course, it’s still not clear whether this change will actually help prevent the problem )-:

Sorry that I don’t have all the answers here. Concurrency is hard™.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

First off, thank you so much for all the info you have provided. This has been incredibly helpful.

I may be able to change getAllResults() to be an async method, in which case I can just do an async dispatch to dispatchQueue instead, which feels pretty safe. I will look into that.

Barring that, what if I manually called disableUpdates() before accessing results? I know that results is already supposed to be doing that automatically, but is that maybe not happening somehow? Or do you think it is properly doing that, and the concurrency issue lies elsewhere?

what if I manually called disableUpdates() before accessing results?

I don’t understand the NSMetadataQuery code well enough to say whether that’ll help or not )-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Just to followup on this, changing the code to only access the NSMetadataQuery.results from its operationQueue seems to have fixed the crash.

Previously we were consistently getting 5-15 crash reports per week for this issue. But in the 17 days since we released the new version, we haven't seen any crashes from this.

since we released the new version, we haven't seen any crashes from this.

Nice! Sometimes my WAGs [1] work out (-:

And I see that you also updated your bug (FB14607400) with that info. Thanks.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] This is one case where I favour US English over British English.

Crash: _NSMetadataQueryResultArray objectAtIndex
 
 
Q