VOSS 3.145.00.10 (beta) is now available for download.
New feature in this release is Persistent Continuation Transactions.
An open VOContinuationTransaction which has committed any number of alternative futures for its set of objects in continuation sub-transactions may now itself be persisted, or persisted & deactivated which removes the live transaction from the image, allowing log-off and shutdown.
Whilst the persisted continuation transaction exists, it and all its objects are persistently set read-only in the name of that transaction. Whilst deactivated it may be re-activated from the Control Panel or by program message sending, to re-create the live open transaction.
Persistent transactions are useful in design or what-if type of applications, where transactions may extend over days of revision and re-working of different saved possible final states, until a final commit is chosen.
Persisting a VOContinuationTransaction stores in the current virtual space (in a private dictionary in the stateDictionary) a virtual object complex which is a representation of the open VOContinuationTransaction, as a VOPersistedContinuationTransaction containing the same virtual objects. The same complex also includes a representation of its owner VOSession and its virtualObjectPark dictionary and contents, as a VOPersistedSession.
By default, every committing VOConSubTransaction persists its parent VOContinuationTransaction within the same commit; this default may be changed by the class method:
VOContinuationTransaction persistConTxnOnSubTxnCommitDefault: aBoolean.
The public instance methods of VOContinuationTransaction offer further options. For example, VOContinuationTransaction>>persistAndDeactivate persists the receiver and removes it from the image, setting persistent read-locks on its VOPersistedContinuationTransaction and contents.
If all open VOContinuationTransactions in an image have been persisted and deactivated, the image may log-off and shutdown if desired.
Whilst a VOContinuationTransaction is active (i.e. exists, open, in the image), but has not yet been persisted, its contents are locked by volatile cache and object table file (.vot) record locks in the same way as in an ordinary transaction. When persisted, the persistent read-locks are additional, but incidental from the point of view of other Process’s transactions (in the same or other images). However, when an open VOContinuationTransaction has been deactivated, it no longer exists in the image and its volatile locks are released; other Process’s transactions may then see such objects, but the persistent read-locks will block them from committing any changes to those objects (or the VOPersistedContinuationTransaction itself).
At the application level, the database may be considered to be in an inconsistent state (this is not something which any DBMS can determine, it is a matter for the application designer and/or user; consider, for example, work in progress in a CAD design for a gearbox or building), and objects which were write-locked in the open VOContinuationTransaction will now be only read-locked and therefore visible to other Processes in their deemed inconsistent state. However, this situation may be managed, since such locks may be tested by the method:
VORefPublic>>isReadOnlyForPersistedTransaction
and/or by checking the startTimestamp (id) of each extant VOPersistedContinuationTransaction in each virtual space returned by the method:
VOManager>>persistedContinuationTransactionsOrIDs
so that other Process’s transactions may choose to see the database as at a specified #dateAndTimeToRead (or equivalent integer #timestampToRead) pre-dating any or all extant persisted continuation transactions. If transaction versioning is globally enabled (set by VOManagerManager>>versioning: aBoolean, or from the Control Panel) then all historical states prior to the continuation transaction(s) may be seen.
Persisted instances of VOPersistedContinuationTransaction are shown in the Control Panel, and may be (re)activated by menu there, or by one of the following messages to the single LocalVOSSServer global instance of VOSSServer.
VOSSServer>> activatePersistedContinuationTransaction: aPersistedContinuationTxn
or
VOSSServer>> activatePersistedContinuationTransactionID: anInteger in: aVOManager
Download here and test-drive this in the tutorial.
Join the forum discussion
VOSS 3.145.00.09 (beta) is now available for download.
This release has changed commit behaviour of sub-transactions within a Continuation Transaction, such that if such a sub-transaction has a non-nil #dateAndTimeToRead (i.e. it sees only read-only historical versions of objects) then, if it is committed, it commits all the objects then locked in its parent Continuation Transaction (including its own objects), whether changed or not - the changed ones as changed, the unchanged ones as at their respective states at that #dateAndTimeToRead - as changes to their current (most recent, read/write) versions. (In the previous release (beta .08) only those objects which the sub-transaction had changed were committed.)
This new behaviour is more intuitively correct and useful, since it commits (”pastes forward”) the sub-transaction’s complete historical view, which could be, for example, an earlier product design configuration etc, with changes, as a new timestamped view. The previously current versions of those objects, now overwritten, still exist in their read-only historical states timestamped at their respective previous commits.
Because a transaction’s #dateAndTimeToRead may be any value, not just some actual commit timestamp, that now-overwritten world view is still there to see as at the appropriate timestamp, and could, if desired, be re-established as current simply by committing that historical view forwards with no changes but a new commit timestamp.
Join the forum discussion
Where do you keep your behavior? Normalised in the application domain objects? In non-domain transaction-performing classes? Some of each? I seem to remember this question being touched on once long ago in Digitalk’s Compuserve forum, but never since.
One of the benefits of a persistent object database is that not only static integrity constraints but arbitrarily complex procedure can be expressed just once in the appropriate application domain object, rather than being scattered and/or replicated in a number of external function oriented procedures - normalisation of procedure, in other words. However, the downside of this is that if an intensively used method is located in a domain class of which there are relatively few instances, then those objects may become locking hotspots which destroy concurrency, even though they themselves may not be changed, merely carrying transactional responsibility.
At the other extreme, locating transaction procedures in non-database objects which call only get & set methods in the domain objects, maybe with simple integrity constraint methods, imposes no additional constraint on concurrency, but at the cost of additional work during application design and modification, to ensure that update transactions are all consistent with each others’ implied integrity constraints.
Design by CRC cards (class, responsibility, collaboration) would seem to identify the theoretical location of transaction responsibility, but how does this work out in practice? Are there common patterns from which guidelines might be found for these design decisions?
Join the forum discussion
Q: “What’s the best way to implement interactive transactions?”
A: The choice is either:
1) Get all user input, then validate and execute in a block transaction forked off in a background process, or
2) Use open-ended transactions, which allow the user to browse and twiddle, retaining all locks until rollback or commit.
1) has the advantage that any number of windows can do this concurrently and also the user interface process is free to do other things whilst the background process is running. However, it does mean that user input cannot be validated field by field as it is input by the user. A separate read-only background block transaction could validate each user input, but then there is the possibility that another user may change objects after the validation transaction has released its locks and before the read-write transaction performs the update.
However, in some applications this may not matter, for example the user may enter a part number, immediately see a description retrieved by a read-only background block transaction, and continue so forth for the transaction as a whole; the specified part description could have been changed, or the part removed from the dictionary, in the meantime by another user, causing a rare error in the update transaction, but something which changes frequently, such as stock-on-hand, should be validated and updated all in the same transaction whilst locks are retained.
2) (Use open-ended transactions, which allow the user to browse and twiddle, retaining all locks until rollback or commit.) Open-ended transactions are normally used only in the user interface process, so if running VOSS in non-interactive mode take care to set up the transaction attributes to handle lock time-outs as intended, (see VOTransaction>>rollbackOnTimeOut: aBoolean). In interactive mode VOSS gives the user a dialog of choices on lock time-out.
Any process can have a tree of nested and sibling sub-transactions. Whenever a transaction is committed or rolled back its whole sub-tree goes the same way. It is therefore possible to create sibling sub-transactions (of ‘Top’, typically) for separate windows which may be separately committed or rolled back under the control of their respective window buttons.
Whenever a process sends a message to a virtual object it is locked on behalf of that process into that process’s current transaction, so each window is responsible for making its transaction current each time that window gets focus. When a transaction commits or rolls back it removes all its objects from its collection of locked objects, but the actual lock on an object, held by the voSession on behalf of its process, is not released until the last remaining transaction which has a lock on it has committed or rolled back. Since all these transaction belong to the same process they share objects, i.e. they don’t wait for locks on objects locked by each other as they would for locks held by transactions belonging to other processes, so care is required if two windows (sharing the same User Interface Process) are sharing access to any objects, they can overwrite each others changes.
A transaction tree can be created in the Control Panel (* indicates the current transaction), or with test code like this:
Smalltalk “set up some global variables”
at: #TxnA put: nil;
at: #TxnB put: nil;
at: #QAZ put: nil.
Then evaluate the following one line at a time updating the Control Panel each time to see the effect
TxnA := VOTransaction newNamed: ‘txnA’.
TxnA superTransaction makeCurrent.
TxnB := VOTransaction newNamed: ‘txnB’.
TxnA makeCurrent.
QAZ := (Array newVirtual: 1) atAllPut: $x.
TxnB makeCurrent.
QAZ “display in workspace (and gets the object into TxnB too)”
TxnB commit.
TxnA makeCurrent.
QAZ
QAZ atAllPut: $y.
TxnA commit.
TxnA := TxnB := QAZ := nil.
