Ok, I spent the evening scouring every framework and the application to make 100% certain that I am not violating any EOF commandments, and although it is always possible that I missed something somewhere, I am pretty darn sure I am clean. Furthermore, DebugGroupMultithreading isn't spitting out any warnings, so I assume I'm safe there. I'm not using a shared editing context anywhere. As far as I know, my editing contexts never get garbage collected until the session expires (I have two that I use, the default editing context, and an editing context that is nested in the default which is referenced in session and locks and unlocks in awake and sleep). I've managed to come up with a workflow that reliably causes the exception, and over the course of running it dozens of times with different debugging statements, I've learned a few things. I'm loathe to call this a bug in EOF, because there are so many unknowns and complexities, but it sure seems like a good candidate for that label. Here is the scenario:
I have an edit page for a catalog item. The catalog item has one or more KitQuantity's, and each KitQuantity has an InventoryItem:
CatalogItem <-->>KitQuantity<<-->InventoryItem
A KitQuantity is a glorified join table which relates the catalog item to many inventory items, plus keeps a little bit of other information such as how many of each inventory item belong in the catalog item definition. The relevant part here is the piece where I add KitQuantities. If I have an existing catalog item, I load the edit page and pass it the catalog item to be edited. Within the edit page's setCatalogItem method, I get local instances of all the relevant pieces within my nested editing context (_nestedEC) using EOUtilities.localInstanceOfObject/localInstancesOfObjects. I list the KitQuantities on the edit page, and have a delete button beside each one. When the delete button is clicked, the following code is executed:
public WOComponent removeItem() { _nestedEC.deleteObject( _kitQuantity ); _nestedEC.saveChanges(); ec.saveChanges(); setValueForBinding( "Changes Saved", "message" ); return context().page(); }
It works great when I load an existing Catalog Item and delete it's KitQuantity relationships one by one. In the same page I can do a search on inventory items and select inventory items (and a quantity) to add to the catalog item via KitQuantity relationships. I create my KitQuantity using EOUtilities.createAndInsertObject with _nestedEC, set the quantity attribute, addObjectToBothSidesOfRelationshipWithKey for both the CatalogItem and the InventoryItem, saveChanges() on _nestedEC, and then saveChanges on session.defaultEditingContext():
public WOComponent addItemToKit() { _kitQuantity = (KitQuantity) EOUtilities.createAndInsertInstance( _nestedEC, "KitQuantity" ); _kitQuantity.setQuantity( proposedQuantity ); _kitQuantity.addObjectToBothSidesOfRelationshipWithKey( catalogItem, "catalogItem" ); _kitQuantity.addObjectToBothSidesOfRelationshipWithKey( inventoryItem, "inventoryItem" ); localSession.addToMessage( proposedQuantity + " " + inventoryItem.sku() + " added to kit." ); } _nestedEC.saveChanges(); ec.saveChanges(); return context().page(); }
This works great also. The KitQuantities are visibly represented on the page, they are definitely in the database, and I can even print out the snapshot for the items for both editing contexts as they are created, and everything is perfect. If I were to quit the application, restart, and come back to this page and edit this item, I could delete the KitQuantities without a care in the world. But if I immediately start deleting the NEWLY CREATED KitQuantity items, I run into problems. Usually (but not always) the first one deletes ok, but at some point in deleting the remaining ones, I get the following exception:
java.lang.IllegalStateException: recordDeleteForObject: com.webobjects.eoaccess.EODatabaseContext com.webobjects.eoaccess.EODatabaseContext@c5627c failed to find a snapshot for EO with Global ID:_EOIntegralKeyGlobalID[KitQuantity (java.lang.Integer)1216] that has been deleted from er.extensions.ERXECer.extensions.ERXEC@b88745. Cannot delete an object that has not been fetched from the database [2007-04-19 23:06:49 EDT] <WorkerThread9> java.lang.IllegalStateException: recordDeleteForObject: com.webobjects.eoaccess.EODatabaseContext com.webobjects.eoaccess.EODatabaseContext@c5627c failed to find a snapshot for EO with Global ID:_EOIntegralKeyGlobalID[KitQuantity (java.lang.Integer)1216] that has been deleted from er.extensions.ERXECer.extensions.ERXEC@b88745. Cannot delete an object that has not been fetched from the database at com.webobjects.eoaccess.EODatabaseContext.recordDeleteForObject(EODatabaseContext.java:4926) at com.webobjects.eoaccess.EODatabaseContext.recordChangesInEditingContext(EODatabaseContext.java:6059) at com.webobjects.eocontrol.EOObjectStoreCoordinator.saveChangesInEditingContext(EOObjectStoreCoordinator.java:412) at com.webobjects.eocontrol.EOEditingContext.saveChanges(EOEditingContext.java:3226) at er.extensions.ERXEC._saveChanges(ERXEC.java:947) at er.extensions.ERXEC.saveChanges(ERXEC.java:870) at EditKitHelper.removeItem(EditKitHelper.java:77) ...
Here the editing context identified by "b88745" represents the session's editing context, and the exception is thrown when I call saveChanges on the default editing context within the removeItem method. Furthermore, if I print out ec.deletedObjects() right before calling ec.saveChanges (and after _nestedEC.saveChanges), the item to be deleted is there!:
ec.deletedObjects(): (<KitQuantity pk:"1216">)
So it appears that the nested EC is flushing its information to the default ec correctly, and I can tell that the editingContext's snapshots are fine until very shortly before the operation. Just in case there was something funny going on in the EO's, I commented out every line of code from CatalogItem, InventoryItem, and KitQuantity (the three classes being dealt with here), so that effectively they were all blank implementations, and I still get the same result. This would seem to rule out any possibility of EOF commandment violation.
At this point I am over trying to understand what is going on or walk on eggshells to make sure I don't offend EOF's delicate sensibilities. I'm under a deadline and I'm seriously considering just cutting EOF out of the loop and doing the whole thing using straight JDBC, but I hear (and somewhat know from experience) that that approach is a slippery slope to misery, so I thought I would put out a detailed description of what I'm doing just in case anyone sees something glaringly obvious or stupid in my methodology (always a distinct possibility). Any help or thoughts are greatly appreciated.
Thanks, Mark
On Apr 19, 2007, at 4:34 PM, Chuck Hill wrote:
On Apr 19, 2007, at 12:54 PM, Steven Mark McCraw wrote:
Hey Chuck,
/Library/Receipts tells me I'm on the 5.3.3 update.
I have been running with DebugGroupMultithreading for a few days now, and nothing seems to have changed, so I assumed everything was ok there, but it could just be that I set things up wrong. I just added the following lines to my Application constructor:
NSLog.debug.setAllowedDebugLevel(NSLog.DebugLevelInformational); NSLog.allowDebugLoggingForGroups(NSLog.DebugGroupEnterpriseObjects | NSLog.DebugGroupMultithreading);
Should that do it, or is there something else I need to do?
I think that should do it.
Otherwise, I will go through and clean out anything anywhere that overrides a setter or getter and changes the data being set/get, and I'll see if that makes a difference.
Thanks for the suggestions.
The only other things that I can think of that might _possibly_ causing something like this are
- misuse of the shared EC - somehow keeping a reference to an EO after its editing context has been disposed - holding a lock on an EC for too lock and it getting out of synch with the snapshots
Chuck
|