Resend because of scrubbing of list ... 

On Tue, Dec 8, 2009 at 9: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


Attachment:

'From Pharo1.0rc1 of 19 October 2009 [Latest update: #10493] on 8 December 2009 at 9:10:53 pm'!
TestCase subclass: #BMTTryingToUnderstandMagmaCommitConflictsTest
instanceVariableNames: 'magmaServerConsole'
classVariableNames: ''
poolDictionaries: ''
category: 'BkbagMagmaTesting-Model-Magma-IntegrationTests'!
Object subclass: #SomeMockDomainObject
instanceVariableNames: 'aField'
classVariableNames: ''
poolDictionaries: ''
category: 'BkbagMagmaTesting-Model-Magma-IntegrationTests'!

!BMTTryingToUnderstandMagmaCommitConflictsTest methodsFor: 'tests' stamp: 'TestRunner 12/8/2009 20:19'!
testTriggerACommitConflictLoadObjectBeforeStartingATransaction
|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."
secondSession begin. "2. User 2 begins a transaction."

mockObjectRefByFirstSession aField: 'aField updated by user 1'. "3. User 1 changes Object A."
firstSession commit. "4. User 1 commits his 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.

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]]


! !

!BMTTryingToUnderstandMagmaCommitConflictsTest methodsFor: 'tests' stamp: 'TestRunner 12/8/2009 20:17'!
testTriggerACommitConflictSequenceAsDescribedOnWiki
|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'.

firstSession begin. "1. User 1 begins a transaction."
secondSession begin. "2. User 2 begins a transaction."

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"

mockObjectRefByFirstSession aField: 'aField updated by user 1'. "3. User 1 changes Object A."
firstSession commit. "4. User 1 commits his 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.

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]]


! !

!BMTTryingToUnderstandMagmaCommitConflictsTest methodsFor: 'tests' stamp: 'TestRunner 12/8/2009 20:47'!
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]]


! !

!BMTTryingToUnderstandMagmaCommitConflictsTest methodsFor: 'running' stamp: 'TestRunner 12/8/2009 19:49'!
setUp
super setUp.
self ensureRepositoryCreated.
magmaServerConsole :=
MagmaServerConsole new
open: self databasePath;
processOn: self databasePort;
yourself.

! !

!BMTTryingToUnderstandMagmaCommitConflictsTest methodsFor: 'running' stamp: 'TestRunner 12/8/2009 16:21'!
tearDown
super tearDown.
magmaServerConsole shutdown.! !

!BMTTryingToUnderstandMagmaCommitConflictsTest methodsFor: 'helper' stamp: 'TestRunner 12/8/2009 16:20'!
databasePath
^'tryingToUnderStandMagmaCommitConflicts'! !

!BMTTryingToUnderstandMagmaCommitConflictsTest methodsFor: 'helper' stamp: 'TestRunner 12/8/2009 16:26'!
databasePort
^55789

! !

!BMTTryingToUnderstandMagmaCommitConflictsTest methodsFor: 'helper' stamp: 'TestRunner 12/8/2009 16:21'!
ensureRepositoryCreated
((FileDirectory forFileName: '') directoryExists: self databasePath)
ifFalse:
[MagmaRepositoryController
create: self databasePath
root: Dictionary new]. ! !


!SomeMockDomainObject methodsFor: 'accessing' stamp: 'TestRunner 12/8/2009 16:24'!
aField
^ aField! !

!SomeMockDomainObject methodsFor: 'accessing' stamp: 'TestRunner 12/8/2009 16:24'!
aField: anObject
aField := anObject! !

BMTTryingToUnderstandMagmaCommitConflictsTest removeSelector: #testTriggerACommitConflict!
BMTTryingToUnderstandMagmaCommitConflictsTest removeSelector: #testTriggerACommitConflict2!

!BMTTryingToUnderstandMagmaCommitConflictsTest reorganize!
('tests' testTriggerACommitConflictLoadObjectBeforeStartingATransaction testTriggerACommitConflictSequenceAsDescribedOnWiki testTriggerACommitConflictUsersCommitAfterEachOtherButWithTheSameVersionOfObjectInitiallyLoaded)
('running' setUp tearDown)
('helper' databasePath databasePort ensureRepositoryCreated)
!