Atributo único de datos básicos con contexts múltiples (subprocesss)

Estoy usando el método descrito en los attributes únicos de Core Data para evitar tener el mismo atributo dos veces (el atributo se llama ID aquí).

Esto funciona bien en un entorno de subprocess único. Utilizo un entorno de subprocesss múltiples y uso el paradigma de un context por subprocess.

El problema es que si dos subprocesss intentan crear simultáneamente un object con el mismo atributo, existe el siguiente problema:

  • El subprocess A crea el object con ID 1 en context1, no hay tal object por lo que se crea
  • El subprocess B crea el object con ID 1 en context2, no hay tal object para que se cree
  • El subprocess A sincroniza el context1-> context2 (usando el código a continuación)

Te encuentras con dos loggings con el mismo ID (1).

Vi este problema cuando probé, por lo que es raro, pero sin duda sucederá con el time.

Por supuesto, hay varias opciones, como GDC, semáforos para evitar que esto suceda, pero antes de ir con una solución compleja, me preguntaba si alguien tiene una solución mejor, o podría recomendar a qué nivel serializar los events.

Usando iOS5 + y ARC.

Utilizo este código para la synchronization de mis contexts:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil]; 

y:

 - (void)backgroundContextDidSave:(NSNotification *)notification { /* Make sure we're on the main thread when updating the main context */ if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(backgroundContextDidSave:) withObject:notification waitUntilDone:NO]; return; } /* merge in the changes to the main context */ for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){ [context mergeChangesFromContextDidSaveNotification:notification]; } } 

Para get un context seguro para subprocesss:

 /** Returns the managed object context for the application. If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application. */ - (NSManagedObjectContext *) managedObjectContextForThread { // Per thread, give one back NSString* threadName = [NSString stringWithFormat:@"%d",[NSThread currentThread].hash]; NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName]; if (existingContext==nil){ existingContext = [[NSManagedObjectContext alloc] init]; [existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]]; [self.threadsDictionary setValue:existingContext forKey:threadName]; [existingContext setMergePolicy:NSOverwriteMergePolicy]; } return existingContext; } 

Creo que encontré la solución, solo usando Core Data.

Ahora estoy usando NSLock para bloquear los contexts para simular algún tipo de transacción:

 [[self managedObjectContextForThread] tryLock]; ... read if ID1 exists ... ... write ID1 ... [[self managedObjectContextForThread] unlock]; 

Esto parece resolver el problema (por ahora).

Tengo un problema relacionado, pero estoy usando un enfoque de datos básicos mucho más nuevo, utilizando jerarquías de context, que deberían haber resuelto problemas de synchronization como este.

En mi caso, tengo un locking interno dentro de CoreData, cuando bash save, y el context CoreData está validando el atributo de unicidad. Mi validation se implementa en el método de validation de properties de KVC:

 // Validate the catalogID for uniqueness. This implementation works fine in the following scenarios: // * end-editing, // * saving a new species entity, // * updating existing species entity. -(BOOL)validateCatalogID:(id *)ioValue error:(NSError * __autoreleasing *)outError { if (*ioValue == nil) return YES; // Let CoreData validate for empty/missing values // Lazily prepare static request and pnetworkingicate to fetch OTHER species catalogID's. We'll reuse these every time we validate the edited species catalogID. static NSFetchRequest *otherSpeciesIDsFetchRequest = nil; static NSPnetworkingicate *otherSpeciesCatalogIDsPnetworkingicate = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ otherSpeciesIDsFetchRequest = [NSFetchRequest fetchRequestWithEntityName:[[self entity] name]]; otherSpeciesCatalogIDsPnetworkingicate = [NSPnetworkingicate pnetworkingicateWithFormat:@"(catalogID == $ID) AND (self != $THIS_SPECIES)"]; }); @synchronized(otherSpeciesIDsFetchRequest) { // Configure the pnetworkingicate with the current catalogID value to validate. otherSpeciesIDsFetchRequest.pnetworkingicate = [otherSpeciesCatalogIDsPnetworkingicate pnetworkingicateWithSubstitutionVariables:@{@"ID":*ioValue, @"THIS_SPECIES":self}]; // instead of fectching entity data, we only COUNT matching items. NSError *error = nil; NSInteger count = [self.managedObjectContext countForFetchRequest:otherSpeciesIDsFetchRequest error:&error]; // DBGLog(@"Check Species ID:%@ uniqueness, found %ld", *ioValue, (long)count); if (count > 0) { [self addValidationError:outError code:ePMXexistingCatalogID message:[NSString stringWithFormat:NSLocalizedString(@"ID '%@' is already used for another species. Use a unique ID", NULL), *ioValue] ]; return NO; } else { if (error!=nil) { [self addValidationError:outError code:ePMXexistingCatalogID message:[NSString stringWithFormat:NSLocalizedString(@"Species ID '%@' cannot be validated. error %@", NULL), *ioValue, error]]; return NO; } } return YES; // this catalogID was not found in any other species entity - unique - valid catalogID. } } 

El crash que estoy viendo se ve así en la stack:

 Crashed Thread: 0 Dispatch queue: com.apple.main-thread Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Codes: EXC_I386_GPFLT Application Specific Information: objc_msgSend() selector name: subentitiesByName Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libobjc.A.dylib objc_msgSend + 23 1 com.apple.CoreData -[NSManagedObjectContext countForFetchRequest:error:] + 235 2 com.IOLR.PlanktoMetrix-II -[PMSpecies(PMExtensions) validateCatalogID:error:] + 559 3 com.apple.CoreData -[NSManagedObject(_NSInternalMethods) _validateValue:forProperty:andKey:withIndex:error:] + 243 4 com.apple.CoreData -[NSManagedObject(_NSInternalMethods) _validatePropertiesWithError:] + 314 5 com.apple.CoreData -[NSManagedObject(_NSInternalMethods) _validateForSave:] + 106 6 com.apple.CoreData -[NSManagedObject validateForUpdate:] + 64 7 com.IOLR.PlanktoMetrix-II -[PMSpecies(PMExtensions) validateForUpdate:] + 73 8 com.apple.CoreData -[NSManagedObjectContext(_NSInternalAdditions) _validateObjects:forOperation:error:exhaustive:forSave:] + 619 9 com.apple.CoreData -[NSManagedObjectContext(_NSInternalAdditions) _validateChangesForSave:] + 410 10 com.apple.CoreData -[NSManagedObjectContext(_NSInternalChangeProcessing) _prepareForPushChanges:] + 208 11 com.apple.CoreData -[NSManagedObjectContext save:] + 247 12 com.IOLR.PlanktoMetrix-II -[BSManagedDocument contentsForURL:ofType:saveOperation:error:] + 918 13 com.IOLR.PlanktoMetrix-II __73-[BSManagedDocument saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke + 411 14 com.apple.AppKit -[NSDocument performAsynchronousFileAccessUsingBlock:] + 57 15 com.IOLR.PlanktoMetrix-II -[BSManagedDocument saveToURL:ofType:forSaveOperation:completionHandler:] + 370 16 com.apple.AppKit __67-[NSDocument autosaveWithImplicitCancellability:completionHandler:]_block_invoke + 1220 17 com.apple.AppKit -[NSDocument continueFileAccessUsingBlock:] + 234 18 com.apple.AppKit __54-[NSDocument performAsynchronousFileAccessUsingBlock:]_block_invoke681 + 125 19 com.apple.AppKit __62-[NSDocumentController(NSInternal) _onMainThreadInvokeWorker:]_block_invoke1807 + 175 

Después de haber resuelto un problema similar, ¿puedes sugerir sobre mi implementación?