Jesse,

> On 29. 5. 2020, at 3:15 PM, Jesse Tayler <jtay...@oeinc.com> wrote:
> Well, I don’t know but I think the fact you’d be having this problem points 
> to another problem.

Perhaps it does, but frankly, I can't see that.

> I don’t know your traffic or architecture, but you really should not have 
> that problem at all, I wonder if perhaps you are creating too many separate 
> ECs?

Not really. Unless I am missing something of importance (do please correct me 
if so), the problem can happen whenever

(i) there are at least two different ECs;
(ii) through which different properties of same object are changed;
(iii) and, at least one EC is locked before changes and unlocked after saving 
(otherwise, we'd get the possible solution (a));

in a multi-threaded environment (background threads, 
WOAllowsConcurrentRequestHandling, or both).

> I find myself generally putting the bulk of operations into one EC and 
> sometimes creating others for one-off stuff or just because it is a linear 
> process with clear results.

Well each user would have normally his own EC in his own session; along with 
WOAllowsConcurrentRequestHandling and standard EC locking that would be quite 
sufficient to create the problem, I believe, far as (ii) and (iii) happen, too. 
Normally with Wonder auto-locking (iii) does; as for (ii), it depends on the 
code, but is pretty probable.

Background threads do not change the principle at all; they just might increase 
the probability this happens if they lock their ECs, for they typically run for 
much longer than R/R loop workers.

> Recently I did a lot of invalidating objects based on flags that were likely 
> to require updating from a remote thread, this isn’t a conflict but I did 
> need to ensure those objects were fetched and their contents written over 
> what is in memory.
> 
> In that case, the user operating the session was really the only part that 
> needed instant updates and only in certain circumstances.

Well if your session user's code either updates the changes (through 
unlock/lock? Or in antoher way? If so, how?) or does not make his own changes 
(of other properties of the same object), or does not save after the thread 
does, there's no problem I guess.

> Anyway, I’d think about the problem more broadly since I’m personally 
> confident WO/Wonder has the most logical locking and EC handling that has 
> been honed and crafter over decades and used in all kinds of situations.

I believe the root of the problem is the shared snapshot, which is a WO-level 
quirk (understandable to save memory, but potentially leading to this pitfall).

> That confidence would lead me to at least try and solve your issues in 
> another way perhaps

Perhaps, I'd be grateful for any reasonable way to solve it.

The (a) — shown in the test code below, too — still looks to me as a pretty 
nice and easy solution; I just wonder why Wonder (sorry, couldn't resist) does 
not do that automatically in ERXEC.saveChanges? Would it perhaps cause some 
other grave problems which I can't see myself (but will be bit by them hard if 
I try to do that myself in my own EC subclass)?

To better elucidate, it can be tested by a code similar to this (it's Groovy, 
and a bit tweaked at that; but I believe readable and understandable enough — I 
must admit in those years I use this infinitely better language I essentially 
forgot how to write in pure Java and would take me a small eternity to re-write 
into the darned thing :))

===
    WOComponent test(unlockEC) {
        eo.foo="new" // eo's some EO in defaultEC; assume string attributes 
foo/bar, works with any property same way
        def entity=eo.entityName,key=eo.rawPrimaryKey // for simplicity. Works 
with any way of fetching the object
        Thread.start { // thread just simulates another user changing 'bar' and 
saving in his own EC of his own session
            ERXEC ec=ERXEC.newEditingContext() // locking here irrelevant: 
neither harms nor helps in this case
            def obj=EOUtilities.objectWithPrimaryKeyValue(ec,entity,key)
            obj.bar="new"
            ec.saveChanges()
            println "thread: saved, foo is '$obj.foo' bar '$obj.bar'" // both 
would be new; so far so good
        }
        Thread.sleep(100) // simulate just a bit slow processing of whatever
        if (unlockEC) { // this fixes the problem in a way of (a) mentioned 
below
            eo.editingContext.unlock() // ... for here the changes from the 
thread are merged-in
            eo.editingContext.lock()
        }
        eo.editingContext.saveChanges()
        println "saved, foo is '$eo.foo' bar '$eo.bar'" // if !unlockEC, bar 
would be old, oops!
    }
    WOComponent test { test(NO) } // this action causes the problem (that after 
all's done, bar's again the old one)
    WOComponent testUnlocking { test(YES) } // this sports the 
possible-solution (a) and works properly
===

If you don't like the thread, precisely the same can be done with two actions — 
one with the delay and the other without — triggered by two separate users from 
two separate sessions with WOAllowsConcurrentRequestHandling set; it's the very 
same principle, just a bit more work to set up and test :)

Thanks and all the best,
OC

> 
>> On May 29, 2020, at 9:00 AM, OCsite via Webobjects-dev 
>> <webobjects-dev@lists.apple.com> wrote:
>> 
>> Hi there,
>> 
>> just again, I've been bit in my tender parts by the well-known problem that
>> 
>> 1. thread A locks its EC
>> 2. thread B saves some change into DB
>> 3. thread A saves its own changes (of different properties), which alas as a 
>> side-effect also reverts B's changes ...
>> 
>> ... since A's EC still contains the state before B's changes, and when that 
>> state is compared with the shared snapshot (updated in step 2), the original 
>> outdated object state before B's changes looks like a new change done by A 
>> and thus is saved.
>> 
>> Isn't there some general solution of this problem? I can think of at least 
>> three:
>> 
>> (a) always unlock (and immediately re-lock) EC before saving, preferably 
>> directly in the ERXEC.saveChanges() code (resp. overridden saveChanges of my 
>> own ERXEC subclass, set as er.extensions.ERXEC.editingContextClassName). 
>> That, far as I understand, would merge all the other changes (of which EOF 
>> already well knows due to the notifications, but it can't merge them whilst 
>> the EC is locked).
>> 
>> That would be pretty easy to do, but I am not sure of other dangers which it 
>> possibly might bring? There could be plenty, I am afraid.
>> 
>> (b) implement EC-based non-shared snapshots and use them instead of (or 
>> rather along with) the shared ones to determine what to save into the DB.
>> 
>> That would be considerably more work, again with possible dangers which I 
>> can't quite see now.
>> 
>> (c) invent a scheme with timestamps attached to individual properties in 
>> shapshots and compare them, letting the newest win.
>> 
>> Probably too much at the rube-goldbergish side to be practical.
>> 
>> Has somebody already tried and tested either (a) or (b) or (c) or any other 
>> approach, better than those three, to alleviate this kind of problems with a 
>> success?
>> 
>> Thanks,
>> OC
>> _______________________________________________
>> 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:
>> https://lists.apple.com/mailman/options/webobjects-dev/jtayler%40oeinc.com
>> 
>> This email sent to jtay...@oeinc.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:
https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to