Hi Chris,
That makes perfectly sense to me.
If I want to check that user 2 does not update a stale (which was updated by user 1) version; I should use a sort of pessimistic locking scheme, as described on the wiki. For instance:
<code> testTriggerAVersionConflictByUsingVersionLogic |firstSession secondSession mockObjectRefByFirstSession mockObjectRefBySecondSession versionBeforeSessionBeginOfObjectReffedBySecondSession| [firstSession := (MagmaRemoteLocation host: 'localhost' port: self databasePort) newSession. firstSession connectAs: 'firstSessionUser'. firstSession commit: [ firstSession root at: 'mockObject' put: (SomeMockDomainObject new aField: 'aField content'; yourself)].
secondSession := (MagmaRemoteLocation host: 'localhost' port: self databasePort) newSession. secondSession connectAs: 'secondSessionUser'.
mockObjectRefByFirstSession := firstSession root at: 'mockObject'. "User 1 is loading the object" mockObjectRefBySecondSession := secondSession root at: 'mockObject'. "User 2 is loading the same version of the object as User 1"
firstSession begin. "1. User 1 begins a transaction." mockObjectRefByFirstSession aField: 'aField updated by user 1'. "3. User 1 changes Object A." mockObjectRefByFirstSession incrementVersion. firstSession commit. "4. User 1 commits his transaction."
versionBeforeSessionBeginOfObjectReffedBySecondSession := mockObjectRefBySecondSession version. "retrieve version of object right before beginning transaction (which does a refresh of the data of the object) - so also updates the version field, thats why we have to get the version right before beginning the transaction" self assert: 0 equals: versionBeforeSessionBeginOfObjectReffedBySecondSession. secondSession begin. "2. User 2 begins a transaction." self assert: 1 equals: mockObjectRefBySecondSession version. "new version silently got merged in" "since versions are not equal => user 2 has been updating a stale version of object; so abort and do not commit any changes; and signal a ConcurrentModificationException to the client". mockObjectRefBySecondSession aField: 'aField updated by user 2'. "5. User 2 changes Object A." secondSession abort.
self assert: 'aField updated by user 1' equals: ((firstSession root at: 'mockObject') aField). self assert: 'aField updated by user 1' equals: ((secondSession root at: 'mockObject') aField). self assert: 'aField updated by user 1' equals: (mockObjectRefBySecondSession aField).
] ensure: [[firstSession commit: [ firstSession root removeAll]. firstSession disconnect] ensure: [secondSession disconnect]]
</code>
Are there any options in Magma provided to simulate such behavior automatically ?
Thanks again.
Kind Regards,
Bart
On Thu, Dec 10, 2009 at 8:44 PM, Chris Muller asqueaker@gmail.com wrote:
Hi, the should: raise: MagmaCommitConflictError in
#testTriggerACommitConflictUsersCommitAfterEachOtherButWithTheSameVersionOfObjectInitiallyLoaded :
self should: [secondSession commit. "6.7. User 2 commits his
transaction"] raise: MagmaCommitConflictError.
fails because secondSession crossed a transaction boundary (via its #begin) after firstSession's commit. Second session would see the results of firstSession's commit, and therefore there is no conflict. A commit-conflict would have occurred if secondSession had sent #begin BEFORE firstSession's commit..
Regards, Chris
On Tue, Dec 8, 2009 at 2:18 PM, Bart Gauquie bart.gauquie@gmail.com wrote:
Dear all,
I've been trying out the MagmaCommitConflictError in Magma. I'm
experiencing
following which I do not understand. I've attached a changeset with a test case reproducing the behaviour I do not understand. If I follow the steps outlined at: http://wiki.squeak.org/squeak/2636 a MagmaCommitConflictError is thrown. (see testTriggerACommitConflictSequenceAsDescribedOnWiki and a little modification on that: testTriggerACommitConflictLoadObjectBeforeStartingATransaction) If I however execute
testTriggerACommitConflictUsersCommitAfterEachOtherButWithTheSameVersionOfObjectInitiallyLoaded
|firstSession secondSession mockObjectRefByFirstSession mockObjectRefBySecondSession| [firstSession := (MagmaRemoteLocation host: 'localhost' port: self databasePort) newSession. firstSession connectAs: 'firstSessionUser'. firstSession commit: [ firstSession root at: 'mockObject' put: (SomeMockDomainObject new aField: 'aField content'; yourself)].
secondSession := (MagmaRemoteLocation host: 'localhost' port: self databasePort) newSession. secondSession connectAs: 'secondSessionUser'.
mockObjectRefByFirstSession := firstSession root at: 'mockObject'. "User
1
is loading the object" mockObjectRefBySecondSession := secondSession root at: 'mockObject'.
"User 2
is loading the same version of the object as User 1"
firstSession begin. "1. User 1 begins a transaction." mockObjectRefByFirstSession aField: 'aField updated by user 1'. "3. User
1
changes Object A." firstSession commit. "4. User 1 commits his transaction."
secondSession begin. "2. User 2 begins a transaction." mockObjectRefBySecondSession aField: 'aField updated by user 2'. "5. User
2
changes Object A."
self should: [secondSession commit. "6.7. User 2 commits his
transaction"]
raise: MagmaCommitConflictError.
firstSession refresh. self assert: 'aField updated by user 1' equals: ((firstSession root at: 'mockObject') aField). self assert: 'aField updated by user 1' equals: ((secondSession root at: 'mockObject') aField). self assert: 'aField updated by user 1' equals: (mockObjectRefBySecondSession aField).
] ensure: [[firstSession commit: [ firstSession root removeAll]. firstSession disconnect] ensure: [secondSession disconnect]]
It failes and I dont understand why. The test is what you get in a
typical
web application. User 1 has a session on the database. User 2 has another session. Both opened an item. Both are editing it and then first user1 commits his changes. User 2 wants to commit his changes but did not see
the
updated data of User 1. The test failes at : self should: [secondSession commit. "6.7. User 2 commits his
transaction"]
raise: MagmaCommitConflictError. => MagmaCommitConflictError is not thrown. Which is something I do not understand. User 2 is trying to commit a change on an object some other session already changed. So User 2 is trying to update a stale version.
Is
there a reason why no MagmaCommitConflictError is thrown? Furthermore, if you proceed on the failing MagmaCommitConflictError; refresh the firstSession; i notice that the changes made by Session2 effectively got committed; which surprises me even more. Because that means that the
changes
made by User 1 got overridden by the changes of User 2 without User 2
even
knowing. If I read the information on the wiki, this behaviour is correct since
Magma
will only detect if during a transaction (after begin has been called)
some
other session has committed changes on the object being changed. Off
course
this is not the kind of behaviour you have if you're developing web applications since there, the transactions are very short. Is there a way to detect if an object was updated by another transaction, but not within my own transaction; so that stale updates do not happen. I suppose there is some version on a persisted object you can check and if
the
last committed version of an object is higher than the version in the current session, this also means a magmacommitconflicterror? But I havent found any info about that yet? Thanks for any help. Kind Regards, Bart -- imagination is more important than knowledge - Albert Einstein Logic will get you from A to B. Imagination will take you everywhere - Albert Einstein Learn from yesterday, live for today, hope for tomorrow. The important
thing
is not to stop questioning. - Albert Einstein The true sign of intelligence is not knowledge but imagination. - Albert Einstein Gravitation is not responsible for people falling in love. - Albert
Einstein
Magma mailing list Magma@lists.squeakfoundation.org http://lists.squeakfoundation.org/mailman/listinfo/magma