Hello, I newbie in magma. I want to test the begin/rollback on a transaction. I think that the rollback action is made by the abort message. But when I run this test sometime it fail.
aMagmaSession := MagmaSession openLocal: 'C:\Squeak 3.8\Squeak3.8-current-win-full\test'. aMagmaSession connectAs: 'test'. aMagmaSession begin. self deny: (aMagmaSession root includesKey: 'xxxXxxx'). aMagmaSession root at: 'xxxXxxx' put: 'hola'. self assert: (aMagmaSession root includesKey: 'xxxXxxx'). aMagmaSession abort. self deny: (aMagmaSession root includesKey: 'xxxXxxx'). "this assertion sometimes fails" aMagmaSession disconnect.
Am I doing everything wright? Do I need to make a garbageCollect?
Thanks.
Hi Juan,
Thank you for the code example, the answer is easy. I have put line numbers for reference.
1> aMagmaSession := MagmaSession 2> openLocal: 'C:\Squeak 3> 3.8\Squeak3.8-current-win-full\test'. 4> aMagmaSession connectAs: 'test'. 5> aMagmaSession begin. 6> self deny: (aMagmaSession root includesKey: 'xxxXxxx'). 7> aMagmaSession root at: 'xxxXxxx' put: 'hola'. 8> self assert: (aMagmaSession root includesKey: 'xxxXxxx'). 9> aMagmaSession abort. 10> self deny: (aMagmaSession root includesKey: 'xxxXxxx'). "this 11> assertion 12> sometimes fails" 13> aMagmaSession disconnect.
This is a wonderful example because it touches several "features" of Magma which are not obvious the first time.
The first is that the root is strong-cached in the session (preventing it from being GC'd) ONLY if you are inside a transaction. Normally, Magma never wants to strong-reference anything so the size of the cached model is strictly in the hands of the user program. However, it is so common to write:
mySession commit: [ mySession root at: 'hello' put: 'world' ]
that if Magma did not strong-cache the root while in a transaction, then it could be garbage-collected anytime after at:put: and before the commit finishes. The result is it looks like the commit sometimes does not work. So you would have to write:
| root | root := mySession root. mySession commit: [ root at: 'hello' put: 'world' ]
So, for convenience, Magma hard-references the root when in a transaction so it won't be GC'd so that scripts like the first one will work.
Now, in line 9 the transaction is aborted, so the strong-reference to the root is dropped, but still, Magma will check its weak cache when the root is asked for in line 10 because if its there its much faster than going to get it again from the server. Sometimes, a GC has occurred by that time and sometimes not. If it has, it will not find it in its weak cache and so re-retrieves from the server, the state of which is without the key 'xxxXxxx'.
But wait, there's more (I told you this was a good example!). Even notwithstanding the above, the #abort in line 9 will not do what you want unless you change a preference.
The preference (get this) is called #refreshPersistentObjectsEvenWhenChangedOnlyByMe.
aMagmaSession refreshPersistentObjectsEvenWhenChangedOnlyByMe: true
No joke, the default is false and that is why line 9 will not refresh the model unless you set it to true, because the root object was not changed by anyone else, it was changed only by you, so Magma is will not wipe out your "work".
The nature of some applications may want to have a dynamic view of the database but many other applications have requirements that insist the users work not be disturbed upon crossing a transaction boundary. Crossing a transaction boundary is a #begin, #commit, or #abort. The default to this preference is false because it is more conservative with the users content and also performs better.
#abort is not "undo", although by setting this preference to true it can be used for a very rudimentary, one-level undo. Instead, abort is meant for transaction control. There is considerable flexibility in Magma's transaction control by way of three different "commit strategies" which may be used depending on how you want the program to work.
For example, do you just want the user to work on the domain model (through the UI) and the program takes care of the transactions invisibly? Or, do you want to provide the user a "Save" button (commitAndBegin) that they can do incremental commits anytime? A "Refresh" button (abort) may be just the user wants to see the latest model while getting ready to "attach" their changes they worked on for two hours.
Other factors are how often the domain changes or whether there is a lot collaboration needed by multiple users on the same exact objects. To get the most out of Magma, all of these strategies should be considered and then decide which one works best. Here is a description of all three:
http://minnow.cc.gatech.edu/squeak/5605
Welcome to Magma, Chris
Hi Chris and magma people.
Sorry I didn't answer the last mail you sent to me. I wanted to learn more about Magma and make more tests before migrate all my data. Now I'm testing transactions. Juan Burella, who wrote the former message is working with me and he made the tests I'm attaching. We noticed some behaviour with transactions that we couldn't understand. In test1TransactionCommit a key is added and the removed. The first time the test is run, if pass. But the seconds it fails because the key is found, and it was supossed to be deleted from the server session (and the files). Perhaps we won't need this functionallity though the user application won't add new keys. But it should work. The other problematic test is test2NestedTransactionAbort. The general idea is 1) begin a transaction, add an element without commiting 2) begin another transaction inside the first, remove this element 3) abort the inner transaction 4)commit the outer. In other words, edit an object, edit an object inside the first, cancel this change but save the first. But after the inner transaction abort, the outer raises an error "No transaction to commit" Evidently, we are doing something wrong, but we can't find it. Would you give us a hint?
Thanks
(To run the test look at the class side. #path sets the pathname for the database. #initialize creates the repository #finalize delete it)
Norberto
Hi,
Sorry I didn't answer the last mail you sent to me. I wanted to learn more about Magma and make more tests before migrate all my data.
An excellent idea with any technology. Find out about it, make sure it works the way you need first. :)
In test1TransactionCommit a key is added and the removed. The first time the test is run, if pass. But the seconds it fails because the key is found, and it was supossed to be deleted from the server session (and the files).
For local-connections, you need to use #disconnectAndClose, not just #disconnect, otherwise the files don't get closed and you will not be able to delete them between tests.
In this test1TransactionCommit I see:
aServerSession := (MagmaRepositoryController openedOn: aFullyQualifiedPathString) session.
This is not a proper way to use Magma. You should *never never never* access the servers session, it is used by the system and mettling it is asking for trouble. If you need multiple locally-connected sessions you can use:
mySecondSession := MagmaSession repositoryController: (MagmaRepositoryController openedOn: aFullyQualifiedPathString)
For this code:
self shouldnt:[ aMagmaSession begin. self deny: ( aRoot includesKey: 'otra'). aRoot at: 'otra' put: 'hola'. self assert: ( aRoot includesKey: 'otra'). aMagmaSession commit. ] raise: MagmaCommitError. self assert: ( aRoot includesKey: 'otra').
since it is using a local connection and only one session, you should never get a MagmaCommitError, so this test will always pass. Also, you probably want to disconnect the session before that last assert (aRoot includesKey: 'otra') because, otherwise, it is only really testing Dictionary>>#at:put:, not Magma. You probably want to test *re-getting* the root from the database has the 'otra' key.
The other problematic test is test2NestedTransactionAbort. The general idea is 1) begin a transaction, add an element without commiting 2) begin another transaction inside the first, remove this element 3) abort the inner transaction 4)commit the outer. In other words, edit an object, edit an object inside the first, cancel this change but save the first. But after the inner transaction abort, the outer raises an error "No transaction to commit" Evidently, we are doing something wrong, but we can't find it. Would you give us a hint?
Ah, the "nested transactions" are not that sophisticated. You cannot abort partial changes, #abort cancels ALL LEVELS of transactions.
The nested transactions are merely to allow code to be more flexible, so you can have three separate operations:
BankAccount>>#deposit: anAmount MagmaSessionRequest signalCommit: [ balance := balance + anAmount ]
BankAccount>>#withdraw: anAmount MagmaSessionRequest signalCommit: [ balance := balance - anAmount ]
and
BankAccount>>#transfer: anAmount to: anotherAccount MagmaSessionRequest signalCommit: [ self withdraw: anAmount. anotherAccount deposit: anAmount ]
Earlier versions of Magma would signal an MagmaUserError, "Already in a transaction" if you tried to use transfer:to:. But all three of these operations are independently needed, and they all need their own commit. So with the "nested transactions" feature is nice that #transfer:to: does not need to worry whether the session is already in a transaction.
I hope this helps!
- Chris
PS - I suggest never to override #finalize. It is on the class side here so maybe its ok since it will never get called by the system but you might want to rename that.
magma@lists.squeakfoundation.org