Hi List,

I tried to put some flesh on the bones and
followed Chucks great hint "I think most of that 
can be handled by handling the 
EOObjectStore.ObjectsChangedInStoreNotification".

Finally I use EditingContextDidSaveChangesNotification,
but I guess the principle is similar. 

Yes, I checked WOnder, but I have not found similar  
things there? Although there is highly elaborated 
inter-osc stuff! Have I overseen something? 

Do not expect too much, I focus the "common problem";
I have neglected the "race condition", since I 
find it very confusing and I'm not sure, whether
I have understood it. *Perhaps* my code also 
solves this issue, since it does not rely
on merging (neither uses editingContextDidMergeChanges() 
nor committedSnapshotForObject()). 

What's the "common" problem? 
Traditional non-ajax form processing: A user opens 
a Form, waits --- waits ---
waits --- waits ---, makes changes and then presses 
her save-button, ie. calls ec.saveChanges(). 
While she was waiting an other user has saved 
other changes to the same eo using another ec within
the same OSC. Her intermediate changes are blown away 
by the second save. Intra-OSC there is no optimistic
locking. Last user wins.

Aim: 
Our lazy user should get a message and her 
changes should be discarded.

The principle of the code: 
Before ec.saveChanges() compare the time of the last 
sleep() of the current session with the time, 
a member of ec.updatedObjects() had 
been saved anywhere in the osc. 
If after the last sleep() there was an ec2.saveChanges() 
which affected one of our ec.updatedObjects()
there is an OL-conflict.

WO 5.2.4.
In the session-constructor I added the observer for
the notification:

/*
I use
EditingContextDidSaveChangesNotification since it
contains the sending ec
*/


        NSNotificationCenter.defaultCenter().addObserver(
        this, 
        new NSSelector(
                "contextDidSaveChangesNoti",
                new Class[] { NSNotification.class }),
        EOEditingContext.EditingContextDidSaveChangesNotification,
        null);


Also in the constructor I set the session as delegate for it's 
ec:

this.defaultEditingContext().setDelegate(this ); 

In the session-class an ivar holds all globalIDs of the EOs saved
by other editing contexts since the last sleep() of the session:

public NSMutableArray eosChangedByOtherECsGIDs=new NSMutableArray();

Another ivar holds the last NSTimestamp of the session's sleep:

public NSTimestamp sessionHadGoneToSleepLastAt;


/**
* Method in session
* Executed when the session gets an 
* contextDidSaveChangesNotification from any
* ec of this OSC. Fetches the gids and
* the actual timestamp in an array
* of dictionaries.
* 
* @param notification
*/

public void  contextDidSaveChangesNoti(NSNotification notification)
{

EOEditingContext sendingEC =(EOEditingContext) 
notification.object();          
        
if (sendingEC == null) return;// pbc
// if the sending ec originates from this session,
// do nothing. 
if (sendingEC.delegate()!= null && 
        sendingEC.delegate().equals(this)) return; 

 // retrieve the array of updated objects
NSArray updatesObjects = (NSArray) notification.userInfo().
valueForKey(EOObjectStore.UpdatedKey);
if  (updatesObjects == null || updatesObjects.count() == 0) 
        {
        return;
        }

// Save the globalIds, and the current time-stamp

int myCount = updatesObjects.count();
Double myNow = new Double( 
                new NSTimestamp().getTime()
                );

        for (int i=0; i<  myCount; i++)
        {
                NSDictionary updatedGID = new NSDictionary
                (
                new Object[]{
                                ((EOGenericRecord) 
                     updatesObjects.objectAtIndex(i)).
                         __globalID(), // private api...
                        myNow
                },
                new String[]
                           {
                                    "globalid", 
                                    "timestamp"
                          }
                        
                );
this.eosChangedByOtherECsGIDs.addObject(updatedGID);
        }

}

/**
* session sleep. delete all notifications
*  
*/

 public void sleep()
 {
         super.sleep();
         sessionHadGoneToSleepLastAt = new NSTimestamp();       
         // forget the notification-list. 
       // we are only interested in changes since
       // the end of the last RR-Loop
        this.eosChangedByOtherECsGIDs.removeAllObjects();
 }

/**
 *  Method in Session
 *  Looks for other savings of myEOToSave since sinceWhen.
 *
 * @param sinceWhen  point of time from which 
 *        monitoring should start. 
 *        Last sleep() of the session.
 * @param myEOToSave EO to check
 * @return "" if everything is ok, else return errormess
 */


public String olCheckWhetherEOHasBeenChanged(
                   NSTimestamp sinceWhen, 
                   EOCustomObject myEOToSave)
{
                        
        if (
                this.eosChangedByOtherECsGIDs == null || 
                this.eosChangedByOtherECsGIDs.count() == 0
                ) 
        {
                return ""; // nothing to do 
        }               
        
        // has the Object been saved before?  
        NSArray args = new NSArray(new Object[]
                                              {
                        myEOToSave.__globalID(),
                        new Double(sinceWhen.getTime())
                        });
        
        EOQualifier mySearch= EOQualifier.
      qualifierWithQualifierFormat(
        "globalid= %@ AND timestamp > %f",args);
        
        NSArray previouslySaved= 
                EOQualifier.filteredArrayWithQualifier(
             this.eosChangedByOtherECsGIDs, 
                                mySearch);
        if (previouslySaved.count() > 0) // hit
                {
                // generate Errormessage
                return "Put your Errormessage here";
              }
         return "";
}

/**
* Method in session.
 * Delegate of EC. Called by the EC before saving
 * completes.
 * Implements a basic intra OSC optimistic-Locking
 * checker. Throws an exception which could
 * be caught on ec.saveChanges(). 
 * @TODO Use an own exception type...
 * 
 * @param context
 */

public void editingContextWillSaveChanges(EOEditingContext context) 
throws Exception
{
   if (this.sessionHadGoneToSleepLastAt==null) return: //pbc
    // check for OL-Errors  
    NSArray myChangedEOs = context.updatedObjects();
    
    
    for (int i=0;i< myChangedEOs.count(); i++)
    {
        
        String myMess = this.olCheckWhetherEOHasBeenChanged(
        this.sessionHadGoneToSleepLastAt, (EOCustomObject) 
        myChangedEOs.objectAtIndex(i)
                        );
        if (!myMess.equals(""))
        {
                throw new Exception(myMess);
        }
    }
}

Further reading:

Mikes Illustration, Race condition:
http://wiki.objectstyle.org/confluence/display/WO/Programming__WebObjects-EOF-Using+EOF-Problems

Ancient efforts:
http://wodeveloper.com/omniLists/eof/2001/January/msg00012.html
http://wodeveloper.com/omniLists/webobjects-dev/2001/January/msg00714.html
http://wodeveloper.com/omniLists/webobjects-dev/2001/December/msg00681.html


Good luck
Frank Ruenagel


> -----Original Message-----
> From: [EMAIL PROTECTED]
> [mailto:[EMAIL PROTECTED]
> pple.com]O
> n Behalf Of Chuck Hill
> Sent: Thursday, August 02, 2007 6:39 PM
> To: Ruenagel, Frank
> Cc: Webobjects-dev@lists.apple.com
> Subject: Re: Concurrency question
> 
> 
> 
> On Aug 2, 2007, at 7:48 AM, Ruenagel, Frank wrote:
> 
> > Hi,
> >
> > perhaps worth noting here that this issue is rather old and  
> > regulary pops up in the list
> > for years now. See the presentation from M.Crawford from 2002:
> >
> > http://conferences.oreillynet.com/presentations/macosx02/ 
> > crawford_eoconflicts.pdf
> 
> What Mike is describing is a bit different.  The common problem is  
> EOF automatically merging changes saved in one EC into all of the  
> other ECs.  That can be handled relatively easily by handling  
> notifications and using an EC delegate.  What Mike is 
> describing is a  
> race condition when both of the ECs are locked and automatic merging  
> is blocked.  This only affect saves in concurrent sessions when the  
> EC for each has been locked.
> 
> 
> Chuck
> 
> 
> > AFAIK there is no good solution (except different EOF-Stacks).
> >
> > In some cases we worked around the problem with comparing
> > snapshots. If a user opens a form the committedSnaphot of the data
> > is cached in a dictionary. If the user makes a ec.saveChanges() we  
> > compare
> > the cached dictionary with the values of the current  
> > committedSnapshotForObject.
> > If there are diffs, any other user meanwhile has changed the data.
> > In a sense we compare the "expected"  committedSnapshotForObject
> > with the "current" committedSnapshotForObject.
> >
> > But this workaround is far from being perfect....
> > Happily the problem does not rise up very often.
> >
> > Regards
> > FR
> >
> >
> >> -----Original Message-----
> >> From: 
> [EMAIL PROTECTED]
> >> [mailto:[EMAIL PROTECTED]
> >> pple.com]O
> >> n Behalf Of Oliver Egger
> >> Sent: Thursday, August 02, 2007 2:36 PM
> >> To: Mike Schrag; Development WebObjects
> >> Subject: Re: Concurrency question
> >>
> >>
> >> hi mike
> >>
> >> what you are describing here relieves my heart!  back in february
> >> (https://sourceforge.net/mailarchive/message.php?msg_id=ce7f98
> >> ec0702010654w560f4a8dxd6420d6dc39580f3%40mail.gmail.com)
> >> i tried to argue why i needed a seperate osc because i was 
> having the
> >> "blow-away-other-changes-in-the-EO" effect but couldn't
> >> explain it so detailed
> >> as you did and didn't even think i would meet once a EOF bug ....
> >>
> >> thanks a lot for that detailed description!
> >> oliver
> >>
> >> On 8/2/07, Mike Schrag <[EMAIL PROTECTED]> wrote:
> >>> My reply was too big with the attachments, so I moved my response
> >>> into the wiki:
> >>>
> >>> http://wiki.objectstyle.org/confluence/display/WO/
> >>> Programming__WebObjects-EOF-Using+EOF-Problems
> >>>
> >>> The last one, "Strange Locking Problems" ... And for the lazy:
> >>> It would appear that there is, in our opinion, some bugs 
> related to
> >>> optimistic locking within a single EOF stack. Essentially what it
> >>> boils down to is that it appears that the update database 
> operation
> >>> that is created as a result of a call to .saveChanges() 
> is backed by
> >>> the EODatabaseContext snapshot and NOT the "working" 
> snapshot inside
> >>> in the EO in the editing context it came from. What this means is
> >>> that while changes are not merged until you .unlock() and .lock()
> >>> under normal circumstances, because the underlying 
> snapshot that EOF
> >>> diffs your changes against on save is the DBC snapshot, it's
> >>> effectively inadvertently "merged" on commit. That is to 
> say that if
> >>> another EC makes changes and saves, then you make 
> different changes
> >>> and save, you will blow away their changes with no sign of an
> >>> optimistic locking exception because your snapshot IS 
> their snapshot
> >>> now (meaning, it looks like just you are overwriting 
> their changes,
> >>> versus the reality of the situation that you are actually
> >> conflicting
> >>> with their changes). After discussing this some, we 
> believe that if
> >>> the update operation used a version of the EO's backing snapshot
> >>> instead that these weird behaviors would be fixed and it
> >> would behave
> >>> exactly like a normal conflicting update if you were in two EOF
> >>> stacks. The current behavior smells of bug, but I'm curious
> >> if anyone
> >>> a dissenting opinion on the topic. It's certainly really 
> complicated
> >>> and nasty down in that code, so it's possible there's some crazy
> >>> justifiable reason for it.
> >>>
> >>> Go to the wiki for the diagrams.
> >>>
> >>> ms
> >>>
> >>> On Aug 2, 2007, at 1:12 AM, Chuck Hill wrote:
> >>>
> >>>> The problem comes in when the modifications are made and saved
> >>>> after your editing context has been locked in the RR loop.  I
> >>>> tricked ;-) Mike into looking at this today with.  Looks like
> >>>> tigers lurk here.  Maybe Mike will comment.
> >>>>
> >>>>
> >>>> Chuck
> >>>>
> >>>>
> >>>> On Aug 1, 2007, at 2:34 PM, Pierre Bernard wrote:
> >>>>
> >>>>> You can simulate OL, by listening to merge notifications. If it
> >>>>> affects a modified object you can later on refuse to save.
> >>>>>
> >>>>> Pierre
> >>>>>
> >>>>>
> >>>>> On Aug 1, 2007, at 7:18 PM, Chuck Hill wrote:
> >>>>>
> >>>>>>
> >>>>>> On Aug 1, 2007, at 5:00 AM, Miguel Arroz wrote:
> >>>>>>
> >>>>>>> Hi!
> >>>>>>>
> >>>>>>>   The contexts are locked. The problem is that it's
> >> not the same
> >>>>>>> context - it's a different context per thread, with
> >> local copies
> >>>>>>> of the same objects.
> >>>>>>>
> >>>>>>>   Synchronizing solves the sample problem, but as my real
> >>>>>>> problem is much more complex than this example, it
> >> starts to get
> >>>>>>> a little... ugly. Also, I still did not full
> >> understand why, the
> >>>>>>> objects in the thread that runs in second are not updated with
> >>>>>>> the data saved by the thread that run first. I 
> suspect that the
> >>>>>>> cause of this is that the objects only receive the
> >> notifications
> >>>>>>> to update themselves after finishing the R-R. Can
> >> anyone confirm
> >>>>>>> this?
> >>>>>>
> >>>>>> Yes, if you are locking properly (and I know you are), the
> >>>>>> changes only get merged at the end of the RR loop.  You
> >> can do it
> >>>>>> yourself by unlocking and relocking the EC.
> >>>>>>
> >>>>>>
> >>>>>>>   About being solving 2 different problems or not, it
> >> depends on
> >>>>>>> the level os abstraction you use to look at it. :)
> >> From my point
> >>>>>>> of view, I'm solving one problem - the problem of concurrent
> >>>>>>> updates. My app has now enough info to solve the problem (OL
> >>>>>>> fields, and how to retry). It would work *if* the UPDATE WHERE
> >>>>>>> data was fetched from the original data in the 
> context, and not
> >>>>>>> in the row data.
> >>>>>>
> >>>>>> It gets more complex than this.  One quick fix is to
> >> have each EC
> >>>>>> in its own EOF stack.  But that can be rather memory expensive.
> >>>>>>
> >>>>>> Chuck
> >>>>>>
> >>>>>>
> >>>>>>
> >>>>>>> On 2007/08/01, at 03:03, Ken Anderson wrote:
> >>>>>>>
> >>>>>>>> Miguel,
> >>>>>>>>
> >>>>>>>> Is your editing context locked before calling incrementIt() ?
> >>>>>>>> I would think that would solve your concurrencyssue her
> >>>>>>>>
> >>>>>>>> If not, just synchronizing the method should solve the
> >>>>>>>> problem.  It may seem inelegant, but you really ARE solving 2
> >>>>>>>> different problems...
> >>>>>>>>
> >>>>>>>> Ken
> >>>>>>>>
> >>>>>>>> On Jul 31, 2007, at 7:41 PM, Miguel Arroz wrote:
> >>>>>>>>
> >>>>>>>>> Hi!
> >>>>>>>>>
> >>>>>>>>>   I'm trying to understand what's the best way to do
> >> something
> >>>>>>>>> here.
> >>>>>>>>>
> >>>>>>>>>   Imagine that I need to get a object from the database,
> >>>>>>>>> modify some attribute based on itself and save it again.
> >>>>>>>>>
> >>>>>>>>>   So, we have the method:
> >>>>>>>>>
> >>>>>>>>>   public void incrementIt() {
> >>>>>>>>>     if( aIsEven() ) {                     // 1
> >>>>>>>>>       setB( b() + 1 );                    // 2
> >>>>>>>>>     }
> >>>>>>>>>     setA( a() + 1 );                      // 3
> >>>>>>>>>     editingContext.saveChanges();         // 4
> >>>>>>>>>   }
> >>>>>>>>>
> >>>>>>>>>   Well, it's easy to solve the problem of update conflicts
> >>>>>>>>> between two different instances of the app. Just tick the OL
> >>>>>>>>> lock for a and b fields, catch the evil expression, refault
> >>>>>>>>> the object, and recalculate (and retry to save).
> >> That's "easy".
> >>>>>>>>>
> >>>>>>>>>   Now, my problem is inside the same instance! Imagine that
> >>>>>>>>> this method runs at the same time and we have the following
> >>>>>>>>> run order, for threads X and Y, with the same object in two
> >>>>>>>>> different contexts (and imagine a = 3):
> >>>>>>>>>
> >>>>>>>>>   X 1
> >>>>>>>>>   Y 1
> >>>>>>>>>   X 3
> >>>>>>>>>   X 4
> >>>>>>>>>   Y 3
> >>>>>>>>>   Y 4
> >>>>>>>>>
> >>>>>>>>>   This will produce wrong results, but it won't cause any
> >>>>>>>>> locking exception. Why?
> >>>>>>>>>
> >>>>>>>>>   1) Both threads get the object with a = 3.
> >>>>>>>>>   2) Both threads do not run line 2 because 3 is not even.
> >>>>>>>>>   3) The thread X increments a, and saves it. When
> >> saving, the
> >>>>>>>>> object at thread Y will have it's 'a' attribute updated,
> >>>>>>>>> assuming both objects are in the same coordinator.
> >>>>>>>>>   4) The thread Y increments a again, and saves it. N
> >>>>>>>>> optimistic locking exception will be thrown, because the
> >>>>>>>>> coordinator snapshot was updated in the last commit, so the
> >>>>>>>>> SELECT FOR UPDATE will run OK.
> >>>>>>>>>
> >>>>>>>>>   This will cause a to be 5, but b did not increment as it
> >>>>>>>>> should. The problem is that, when saving, we are 
> basing OL on
> >>>>>>>>> the row snapshot (that is updated during the 
> process) and not
> >>>>>>>>> to the original value of the object when it was loaded into
> >>>>>>>>> the editing context.
> >>>>>>>>>
> >>>>>>>>>   Well, this may be solved using the "classic" Java
> >>>>>>>>> "syncronized" stuff, and locks and all that stuff.
> >> But this is
> >>>>>>>>> a bit stupid. I already solved the problem with OL for the,
> >>>>>>>>> theoretically, more difficult case of managing several app
> >>>>>>>>> instances. Do I have to solve it all over again, in a
> >>>>>>>>> different way, to deal with multiple updates on the same
> >>>>>>>>> instance? Isn't there a way to use OL just like I'm already
> >>>>>>>>> doing?
> >>>>>>>>>
> >>>>>>>>>   Yours
> >>>>>>>>>
> >>>>>>>>> Miguel Arroz
> >>>>>>>>>
> >>>>>>>>> Miguel Arroz
> >>>>>>>>> http://www.terminalapp.net
> >>>>>>>>> http://www.ipragma.com
> >>>>>>>>>
> >>>>>>>>>
> >>>>>>>>>
> >>>>>>>>> _______________________________________________
> >>>>>>>>> Do not post admin requests to the list. They will 
> be ignored.
> >>>>>>>>> Webobjects-dev mailing list
> >> (Webobjects-dev@lists.apple.com)
> >>>>>>>>> Help/Unsubscribe/Update your Subscription:
> >>>>>>>>>
> >> http://lists.apple.com/mailman/options/webobjects-dev/kenlists%
> >>>>>>>>> 40anderhome.com
> >>>>>>>>>
> >>>>>>>>> This email sent to [EMAIL PROTECTED]
> >>>>>>>>
> >>>>>>>
> >>>>>>> Miguel Arroz
> >>>>>>> http://www.terminalapp.net
> >>>>>>> http://www.ipragma.com
> >>>>>>>
> >>>>>>>
> >>>>>>>
> >>>>>>> _______________________________________________
> >>>>>>> Do not post admin requests to the list. They will be ignored.
> >>>>>>> Webobjects-dev mailing list
> >> (Webobjects-dev@lists.apple.com)
> >>>>>>> Help/Unsubscribe/Update your Subscription:
> >>>>>>> http://lists.apple.com/mailman/options/webobjects-dev/chill%
> >>>>>>> 40global-village.net
> >>>>>>>
> >>>>>>> This email sent to [EMAIL PROTECTED]
> >>>>>>>
> >>>>>>
> >>>>>> --
> >>>>>>
> >>>>>> Practical WebObjects - for developers who want to 
> increase their
> >>>>>> overall knowledge of WebObjects or who are trying to solve
> >>>>>> specific problems.
> >>>>>> http://www.global-village.net/products/practical_webobjects
> >>>>>>
> >>>>>>
> >>>>>>
> >>>>>>
> >>>>>>
> >>>>>> _______________________________________________
> >>>>>> Do not post admin requests to the list. They will be ignored.
> >>>>>> Webobjects-dev mailing list
> >> (Webobjects-dev@lists.apple.com)
> >>>>>> Help/Unsubscribe/Update your Subscription:
> >>>>>>
> > http://lists.apple.com/mailman/options/webobjects-dev/webobjects-
> >>>>> lists%40houdah.com
> >>>>>
> >>>>> This email sent to [EMAIL PROTECTED]
> >>>>
> >>>> - - -
> >>>> Houdah Software s. à r. l.
> >>>> http://www.houdah.com
> >>>>
> >>>> HoudahGeo: One-stop photo geocoding
> >>>> HoudahSpot: Powerful Spotlight frontend
> >>>>
> >>>>
> >>>>
> >>>>
> >>>
> >>> --
> >>>
> >>> Practical WebObjects - for developers who want to increase their
> >>> overall knowledge of WebObjects or who are trying to 
> solve specific
> >>> problems.
> >>> http://www.global-village.net/products/practical_webobjects
> >>>
> >>>
> >>>
> >>>
> >>>
> >>> _______________________________________________
> >>> Do not post admin requests to the list. They will be ignored.
> >>> Webobjects-dev mailing list      (Webobjects-dev@lists.apple.com)
> >>> Help/Unsubscribe/Update your Subscription:
> >>> http://lists.apple.com/mailman/options/webobjects-dev/mschrag%
> >>> 40mdimension.com
> >>>
> >>> This email sent to [EMAIL PROTECTED]
> >>
> >>
> >>  _______________________________________________
> >> Do not post admin requests to the list. They will be ignored.
> >> Webobjects-dev mailing list      (Webobjects-dev@lists.apple.com)
> >> Help/Unsubscribe/Update your Subscription:
> >> 
> http://lists.apple.com/mailman/options/webobjects-dev/oliver.egger% 
> >> 40gmail.com
> >>
> >> This email sent to [EMAIL PROTECTED]
> >>
> >  _______________________________________________
> > Do not post admin requests to the list. They will be ignored.
> > Webobjects-dev mailing list      (Webobjects-dev@lists.apple.com)
> > Help/Unsubscribe/Update your Subscription:
> > http://lists.apple.com/mailman/options/webobjects-dev/webobjects% 
> > 40symposion.de
> >
> > This email sent to [EMAIL PROTECTED]
> >  _______________________________________________
> > Do not post admin requests to the list. They will be ignored.
> > Webobjects-dev mailing list      (Webobjects-dev@lists.apple.com)
> > Help/Unsubscribe/Update your Subscription:
> > http://lists.apple.com/mailman/options/webobjects-dev/chill% 
> > 40global-village.net
> >
> > This email sent to [EMAIL PROTECTED]
> >
> 
> -- 
> 
> Practical WebObjects - for developers who want to increase their  
> overall knowledge of WebObjects or who are trying to solve specific  
> problems.
> http://www.global-village.net/products/practical_webobjects
> 
> 
> 
> 
> 
>  _______________________________________________
> Do not post admin requests to the list. They will be ignored.
> Webobjects-dev mailing list      (Webobjects-dev@lists.apple.com)
> Help/Unsubscribe/Update your Subscription:
> http://lists.apple.com/mailman/options/webobjects-dev/webobjec
ts%40symposion.de

This email sent to [EMAIL PROTECTED]
 _______________________________________________
Do not post admin requests to the list. They will be ignored.
Webobjects-dev mailing list      (Webobjects-dev@lists.apple.com)
Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com

This email sent to [EMAIL PROTECTED]

Reply via email to