Saving NSDictionary, Enumeration error

  • Hi,

    one of my users recently got an error when trying to save. My application is not document based, and I'm collecting the data to NSDictionaries, then save it with writeToFile: method. I never can reproduce the issue, so hard to find what is the problem.

    I'm collection the data with methods like:

        NSMutableDictionary *tempdict = [[[NSMutableDictionary alloc] init] autorelease];

        [tempdict setObject:... forKey:...];
        [tempdict setObject:... forKey:...];
        [tempdict setObject:... forKey:...];
        .
    .
    .
        return [NSDictionary dictionaryWithDictionary:tempdict];

    and putting those NSDictionaries to realSaveDict, then save:
    [realSaveDict writeToFile:[sPanel filename] atomically:YES]

    Any ideas guys what's going wrong?

    Thanks,

    Tamas

    May 3, 2013 2:44:54 PM: *** Collection <__NSDictionaryM: 0x1c37a300> was mutated while being enumerated.
    May 3, 2013 2:44:54 PM: (
    0  CoreFoundation                      0x98912e9b __raiseError + 219
    1  libobjc.A.dylib                    0x970b352e objc_exception_throw + 230
    2  CoreFoundation                      0x98912a9a __NSFastEnumerationMutationHandler + 282
    3  CoreFoundation                      0x9884afc9 -[__NSFastEnumerationEnumerator nextObject] + 457
    4  CoreFoundation                      0x98884f4c -[NSDictionary countByEnumeratingWithState:objects:count:] + 396
    5  CoreFoundation                      0x98884c68 -[NSDictionary __apply:context:] + 104
    6  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    7  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    8  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    9  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    10  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    11  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    12  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    13  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    14  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    15  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    16  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    17  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    18  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    19  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    20  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    21  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    22  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    23  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    24  CoreFoundation                      0x9883c445 CFPropertyListIsValid + 117
    25  CoreFoundation                      0x9883c2dd _CFPropertyListCreateXMLData + 77
    26  CoreFoundation                      0x9883c280 CFPropertyListCreateXMLData + 32
    27  Foundation                          0x98ca14ae -[NSDictionary(NSDictionary) writeToURL:atomically:] + 242
  • Cab you provide a bit more detail? Is everything happening on to main thread?

    On May 7, 2013, at 14:39, Tamas Nagy <tamas.lov.nagy...> wrote:

    > Hi,
    >
    > one of my users recently got an error when trying to save. My application is not document based, and I'm collecting the data to NSDictionaries, then save it with writeToFile: method. I never can reproduce the issue, so hard to find what is the problem.
    >
    > I'm collection the data with methods like:
    >
    > NSMutableDictionary *tempdict = [[[NSMutableDictionary alloc] init] autorelease];
    >
    > [tempdict setObject:... forKey:...];
    > [tempdict setObject:... forKey:...];
    > [tempdict setObject:... forKey:...];
    > .
    > .
    > .
    > return [NSDictionary dictionaryWithDictionary:tempdict];
    >
    > and putting those NSDictionaries to realSaveDict, then save:
    > [realSaveDict writeToFile:[sPanel filename] atomically:YES]
    >
    > Any ideas guys what's going wrong?
    >
    > Thanks,
    >
    > Tamas
    >
    > May 3, 2013 2:44:54 PM: *** Collection <__NSDictionaryM: 0x1c37a300> was mutated while being enumerated.
    > May 3, 2013 2:44:54 PM: (
    > 0  CoreFoundation                      0x98912e9b __raiseError + 219
    > 1  libobjc.A.dylib                    0x970b352e objc_exception_throw + 230
    > 2  CoreFoundation                      0x98912a9a __NSFastEnumerationMutationHandler + 282
    > 3  CoreFoundation                      0x9884afc9 -[__NSFastEnumerationEnumerator nextObject] + 457
    > 4  CoreFoundation                      0x98884f4c -[NSDictionary countByEnumeratingWithState:objects:count:] + 396
    > 5  CoreFoundation                      0x98884c68 -[NSDictionary __apply:context:] + 104
    > 6  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    > 7  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    > 8  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    > 9  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    > 10  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    > 11  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    > 12  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    > 13  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    > 14  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    > 15  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    > 16  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    > 17  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    > 18  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    > 19  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    > 20  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    > 21  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    > 22  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    > 23  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    > 24  CoreFoundation                      0x9883c445 CFPropertyListIsValid + 117
    > 25  CoreFoundation                      0x9883c2dd _CFPropertyListCreateXMLData + 77
    > 26  CoreFoundation                      0x9883c280 CFPropertyListCreateXMLData + 32
    > 27  Foundation                          0x98ca14ae -[NSDictionary(NSDictionary) writeToURL:atomically:] + 242
  • The data collecting and save happening on the main thread, but the objects providing the data may run on an other - rendering - thread. Maybe its a problem with an object while collecting the save data, and I forgot about copy the value?

    So, in other words, I should always do this:
    [tempdict setObject:[[something copy] autorelease] forKey:…] but I forgot it somewhere and I doing [tempdict setObject:something forKey:…]. Could this be a problem?

    On May 7, 2013, at 3:45 PM, Igor Elland <igor.elland...> wrote:

    > Cab you provide a bit more detail? Is everything happening on to main thread?
    >
    > On May 7, 2013, at 14:39, Tamas Nagy <tamas.lov.nagy...> wrote:
    >
    >> Hi,
    >>
    >> one of my users recently got an error when trying to save. My application is not document based, and I'm collecting the data to NSDictionaries, then save it with writeToFile: method. I never can reproduce the issue, so hard to find what is the problem.
    >>
    >> I'm collection the data with methods like:
    >>
    >> NSMutableDictionary *tempdict = [[[NSMutableDictionary alloc] init] autorelease];
    >>
    >> [tempdict setObject:... forKey:...];
    >> [tempdict setObject:... forKey:...];
    >> [tempdict setObject:... forKey:...];
    >> .
    >> .
    >> .
    >> return [NSDictionary dictionaryWithDictionary:tempdict];
    >>
    >> and putting those NSDictionaries to realSaveDict, then save:
    >> [realSaveDict writeToFile:[sPanel filename] atomically:YES]
    >>
    >> Any ideas guys what's going wrong?
    >>
    >> Thanks,
    >>
    >> Tamas
    >>
    >> May 3, 2013 2:44:54 PM: *** Collection <__NSDictionaryM: 0x1c37a300> was mutated while being enumerated.
    >> May 3, 2013 2:44:54 PM: (
    >> 0  CoreFoundation                      0x98912e9b __raiseError + 219
    >> 1  libobjc.A.dylib                    0x970b352e objc_exception_throw + 230
    >> 2  CoreFoundation                      0x98912a9a __NSFastEnumerationMutationHandler + 282
    >> 3  CoreFoundation                      0x9884afc9 -[__NSFastEnumerationEnumerator nextObject] + 457
    >> 4  CoreFoundation                      0x98884f4c -[NSDictionary countByEnumeratingWithState:objects:count:] + 396
    >> 5  CoreFoundation                      0x98884c68 -[NSDictionary __apply:context:] + 104
    >> 6  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    >> 7  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    >> 8  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    >> 9  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    >> 10  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    >> 11  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    >> 12  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    >> 13  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    >> 14  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    >> 15  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    >> 16  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    >> 17  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    >> 18  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    >> 19  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    >> 20  CoreFoundation                      0x9882aac1 __CFPropertyListIsDictPlistAux + 257
    >> 21  CoreFoundation                      0x98884cd5 -[NSDictionary __apply:context:] + 213
    >> 22  CoreFoundation                      0x9880904a CFDictionaryApplyFunction + 106
    >> 23  CoreFoundation                      0x98809dbe __CFPropertyListIsValidAux + 366
    >> 24  CoreFoundation                      0x9883c445 CFPropertyListIsValid + 117
    >> 25  CoreFoundation                      0x9883c2dd _CFPropertyListCreateXMLData + 77
    >> 26  CoreFoundation                      0x9883c280 CFPropertyListCreateXMLData + 32
    >> 27  Foundation                          0x98ca14ae -[NSDictionary(NSDictionary) writeToURL:atomically:] + 242
  • On May 7, 2013, at 7:04 AM, Tamas Nagy <tamas.lov.nagy...> wrote:

    > The data collecting and save happening on the main thread, but the objects providing the data may run on an other - rendering - thread. Maybe its a problem with an object while collecting the save data, and I forgot about copy the value?

    Copying the value isn't enough! Foundation collections are not thread-safe — you shouldn't access a dictionary (or array or set or string) on one thread while modifying it on another. The only safe way to share these objects is read-only. (There's a whole document in Apple's doc-set about the thread-safety of various Cocoa classes. In general, never assume a class is thread-safe unless the docs explicitly say so.)

    In this specific crash, the presence of "__NSFastEnumerationMutationHandler" in the exception backtrace shows that the enumerator detected that the dictionary had been modified during the enumeration, which is illegal (even on a single thread.)

    —Jens
  • Okay, maybe I misunderstood something, but since I collect the data to an NSMutableDictionary, then turn it to a non-mutable NSDictionary, like this:

    -(NSDictionary)saveData {
    NSMutableDictionary *dict = [[[NSMutableDictionary alloc] init] autorelease]
    [dict setObject:...]
    return NSDictionary dictionaryWithDictionary: [[dict copy] autorelease]];

    so does not this made the dictionary already read-only?

    On May 7, 2013, at 8:06 PM, Jens Alfke <jens...> wrote:

    >
    > On May 7, 2013, at 7:04 AM, Tamas Nagy <tamas.lov.nagy...> wrote:
    >
    >> The data collecting and save happening on the main thread, but the objects providing the data may run on an other - rendering - thread. Maybe its a problem with an object while collecting the save data, and I forgot about copy the value?
    >
    > Copying the value isn't enough! Foundation collections are not thread-safe — you shouldn't access a dictionary (or array or set or string) on one thread while modifying it on another. The only safe way to share these objects is read-only. (There's a whole document in Apple's doc-set about the thread-safety of various Cocoa classes. In general, never assume a class is thread-safe unless the docs explicitly say so.)
    >
    > In this specific crash, the presence of "__NSFastEnumerationMutationHandler" in the exception backtrace shows that the enumerator detected that the dictionary had been modified during the enumeration, which is illegal (even on a single thread.)
    >
    > —Jens
  • On May 7, 2013, at 3:32 PM, Tamas Nagy wrote:

    > so does not this made the dictionary already read-only?

    And what happens when dict is modified by another thread during the call to dict copy?

    You can't solve the problem by layering copy operation on top of copy operation. You have to synchronize access appropriately. Now, make the copy is a good idea, so that you only have to synchronize at that point, and after are free to modify the original while reading the copy.

    --
    Scott Ribe
    <scott_ribe...>
    http://www.elevated-dev.com/
    (303) 722-0567 voice
  • On May 7, 2013, at 2:32 PM, Tamas Nagy <tamas.lov.nagy...> wrote:

    > -(NSDictionary)saveData {
    > NSMutableDictionary *dict = [[[NSMutableDictionary alloc] init] autorelease]
    > [dict setObject:...]
    > return NSDictionary dictionaryWithDictionary: [[dict copy] autorelease]];

    This does indeed create a new immutable dictionary object to return. (Although the last line is doing too much work; -dictionaryWithDictionary: does the same thing as -copy.)

    However, are you sure the value objects in the dictionary are immutable too? Copying is not deep — when you copy a container, the objects stored in the container are merely retained, not copied too. So if you use that dictionary returned from -saveData on another thread, you're probably using the objects stored in it in multithreaded ways.

    I don't have your original message handy anymore, but IIRC the backtrace was a few levels deep in nested enumerations, which makes me think that the mutation exception is happening to a nested dictionary, not the outer one.

    —Jens
  • Thanks Scott, good point. I'll modify the saveData method to collect the data on the background thread.

    On May 7, 2013, at 11:39 PM, Scott Ribe <scott_ribe...> wrote:

    > On May 7, 2013, at 3:32 PM, Tamas Nagy wrote:
    >
    >> so does not this made the dictionary already read-only?
    >
    > And what happens when dict is modified by another thread during the call to dict copy?
    >
    > You can't solve the problem by layering copy operation on top of copy operation. You have to synchronize access appropriately. Now, make the copy is a good idea, so that you only have to synchronize at that point, and after are free to modify the original while reading the copy.
    >
    > --
    > Scott Ribe
    > <scott_ribe...>
    > http://www.elevated-dev.com/
    > (303) 722-0567 voice
    >
    >
    >
    >
  • Hmm, yes, its possible saveData method returns an NSDictionary which contains NSMutableDictionaries if I missed something in my code. Thanks, I'll check it out.

    On May 8, 2013, at 12:13 AM, Jens Alfke <jens...> wrote:

    >
    > On May 7, 2013, at 2:32 PM, Tamas Nagy <tamas.lov.nagy...> wrote:
    >
    >> -(NSDictionary)saveData {
    >> NSMutableDictionary *dict = [[[NSMutableDictionary alloc] init] autorelease]
    >> [dict setObject:...]
    >> return NSDictionary dictionaryWithDictionary: [[dict copy] autorelease]];
    >
    > This does indeed create a new immutable dictionary object to return. (Although the last line is doing too much work; -dictionaryWithDictionary: does the same thing as -copy.)
    >
    > However, are you sure the value objects in the dictionary are immutable too? Copying is not deep — when you copy a container, the objects stored in the container are merely retained, not copied too. So if you use that dictionary returned from -saveData on another thread, you're probably using the objects stored in it in multithreaded ways.
    >
    > I don't have your original message handy anymore, but IIRC the backtrace was a few levels deep in nested enumerations, which makes me think that the mutation exception is happening to a nested dictionary, not the outer one.
    >
    > —Jens
previous month may 2013 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
Go to today