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
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) !
And yet again it failes.
Trying again to attach the demo test.
...
On Tue, Dec 8, 2009 at 9:22 PM, Bart Gauquie bart.gauquie@gmail.com wrote:
Resend because of scrubbing of list ...
On Tue, Dec 8, 2009 at 9:18 PM, Bart Gauquie bart.gauquie@gmail.comwrote:
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) !
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%C2%A0a%C2%A0MagmaCommitConflictError 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
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
Are there any options in Magma provided to simulate such behavior automatically ?
Not at the object level, but you might be interested in looking at MagmaCounter. I think Gjaller uses this to generate sequence #'s. Probably not necessary for application-level object-locking, but an interesting special-purpose tool available that sometimes is easily forgotten about..
- Chris
magma@lists.squeakfoundation.org