Changing objects in Magma
Last updated at 6:28 am UTC on 29 March 2018
Magma utilizes a simple transaction api to isolate a batch of object changes that can be saved as a whole or canceled as a whole.
The core messages of the transaction api are:
- refresh - Refresh the local view of objects with updates from other sessions. Non-conflicting local changes are not touched.
- safeRefresh - Update the local view of objects with updates from other sessions. If a local object was changed which was also updated by another session, signal a MagmaTooFarBehindConflict, allowing the application to reconcile the differences as it needs.
- begin - refresh local objects from the repository, open a transaction indicating intent to update the model.
- commit - propose the current state of the object model to the server. If objects changed in this commit were also changed by other sessions since the #begin (above), a MagmaCommitConflictError is signaled providing the collections of conflicting objects organized by the session-ids that updated them).
- abort - Restores all changed objects to their last-read state, followed by a refresh, as above. Sets the transactionLevel to 0 (see "Nested Transactions", below).
These messages are sent to a MagmaSession.
Essentials of transactions
When a program executes any of these messages, it is said to be "crossing a transaction boundary," and is the time when the the local image is updated from the Magma repository. All changes committed by other users since your last crossing immediately become visible to the session. For a commit, all individual changes made in your image are saved to the repository and will become visible to all other users as soon as they cross their next transaction boundary.
It may seem evident that, with unfortunate timing of events, one user would be able to overlay changes made by another user. For example, consider the following scenario:
- User 1 begins a transaction.
- User 2 begins a transaction.
- User 1 changes Object A.
- User 1 commits his transaction.
- User 2 changes Object A.
- User 2, cannot yet see User 1's changes, because she has not crossed a transaction boundary since the commit.
- User 2 commits.
Magma will detect this conflict and signal a MagmaCommitError back to user 2. This is because she tried to change an object that was changed by User 1 since she began her transaction, invalidating her changes. The failed result object has the conflicting objects (Object A) and the name of each user who changed them (User 1). Additionally, User 2 can now see the new Object A, as changed by User 1, because her commit attempt caused a crossing of a transaction boundary.
At this point, User 2 is still in a transaction, and can now see Object A as committed by User 1. She may now commit again, or abort if appropriate.
Nested Transactions
Magma allows you to say:
mySession commit:
[ ...make some changes...
mySession commit: [ ...make some other changes... ] ]
The inner-commit actually does nothing; only the outer-commit writes all changes to the db. This is to support the requirement that a bank withdrawal be its own usable operation, a bank deposit be its own usable operation, but also transfer from one account to another (which uses the withdrawal code followed by deposit code) to only be committed wholly. You can just use #commit: in all three methods and it will only consider the outermost commit the one that really commits to the database.
Concurrency
In terms of updating a shared persistent model, a "conflict" occurs when a session tries to update an old version of an object. Another session changed the object, therefore Magma cannot allow those changes to be overwritten (not to mention that the new changes may no longer be necessary). Persistent objects in an image are updated each time your session issues a #begin, #commit or #abort.
Transactions in the client may be as long as desired, but keep in mind the Magma server will terminate inactive sessions. What happens when the client comes back is, the connection is re-established automatically.
Part of this reconnection process causes the local image to be updated with all changes from the repository, which could even include objects that were changed by that session (with no conflict). Transactions may even span image saves. You may start an image, connect to a Magma repository, begin a transaction, make some changes, save and exit the image (not yet having committed), reboot the image later (objects in the image are brought up to date with changes by other sessions), continue any further changes, and finally commit successfully.
When a MagmaSession encounters a real commit-conflict, a MagmaCommitError is signaled. This error understands a message to give the details of the failure, #result. The answer of #result is a MaFailedCommitResult, which can answer a collection of #commitConflicts, a collection of MagmaCommitConflicts, each detailing the conflicting object and who changed it.