3 Shadow Paging Implementation
7.5 Finale
This thesis has presented an attempt to integrate the notions of concurrency and distribution into a persistent framework in a flexible manner. The resulting architecture enables models of concurrency and distribution to be designed, constructed and executed in a persistent system. Providing concurrency and distribution as an add-on facility instead of building it into the system challenges the convention of related work. Whether or not this approach is superior is highly subjective but is founded on a conviction that a high-level solution delivers increased expressive power, safety and simplicity in the production of complex models.
It is hoped that providing this functionality has increased the expressive and modelling potential of a persistent system and that the work may broaden the appeal of persistence to a wider audience. However what metrics can one use to determine if such an approach is successful? Even if the work here becomes adopted, adapted and widely used, an argument based on popularity must by the same token decide if Cobol and C and systems such as Unix and MS-DOS should be considered successful. Whatever the outcome, the effort will not have been fruitless since the personal rewards gained from undertaking this research and tlie production of this thesis have finally convinced the author that there is a more interesting life beyond the System Manager’s Guide [Mor85-93]. A case of WTFM superseding RTFM.
Appendix A Multithreading in NapierSS
The provision of co-operating concurrency in NapierSS requires a way of expressing concurrent activity at the language level, a synchronisation primitive and a scheduler to control concurrent operation. An important point to note is that because the concurrent activities in this model interact by agreement rather than in conflict there is no need to isolate the effects of one action on the store from another. This means that the introduction of co-operating concurrency into the NapierSS system has no beaiing on the object store architecture and can be fully implemented within the language and abstract machine.
Concurrent expression in this model is provided through an abstract data type that provides a package of procedures that allow the creation and manipulation of sepai*ate threads of control. The thread package is not built into the language but is obtained through the standard environment. The standard environment is a special NapierSS environment that contains many packages of standard functions. The specification of the thread package is given in figure A.I.
type ThreadPack is abstype[ Thread ] ( start proc( proc() Thread ) ; getThreadId proc( -a Thread ) ;
kill, restart,
suspend : proc( Thread )
)
Figure A.l: Thread Package
This abstract data type contains procedures to operate on threads. For some witness type Thread the operations are
This procedure creates a new thread to execute the given void procedure, adds the thread to the list of threads, marks the thread as runnable, and returns an identifier for the thread. The thread completes when the given procedure completes.
• getThreadId : proc( -a Thread )
This procedure returns the identifier of the currently executing thread.
• kill : proc( Thread )
This procedure removes the thread denoted by the given identifier from the list of threads. If the thread is currently executing it is terminated.
• restart : proc( Thread )
This procedure marks the thread denoted by the given identifier as runnable. If the thread is currently executing tlie procedure has no effect.
• suspend : proc( Thread )
This procedure marks tlie thread denoted by the given identifier as suspended. If the thread is currently executing it is immediately suspended.
The thread package enables any number of void procedures to be executed concurrently without a change to the NapierSS language model. Threads can be nested to any depth and a thread will execute in the same environment as its parent. However there is no implicit dependency between a parent and child process; suspension or termination of one does not affect the other. The thread package is not dissimilar to the dynamic processes used in CPS-algol [KraS7]. One novelty of this approach is that in using an abstract data type the witness type cannot be discovered and hence thread ids are unforgable.
Synchronisation of Co-operating Threads
The facility for synchronisation of the threads is provided through a semaphore package shown in figure A.2.
type Semaphore is structure( wait,signal : proc() )
semaphoreGen : proc( int -> Semaphore )
Figure A.2: Semaphore Package
• semaphoreGen ; proc( int -a Semaphore )
This procedure takes an initial value for the semaphore and returns a stiucture containing procedures to operate on the semaphore.
• wait : proc()
The value of the semaphore is decremented. If the new value is less than zero then the current thread is suspended and its dependency on the semaphore is recorded.
• signal : proc()
The value of the semaphore is incremented. If the new value is less than or equal to zero, one of the threads suspended on the semaphore is selected and re-activated.
Dining Philosophers in NapierSS threads
As an example of how the threads are programmed the following listing gives a solution to tlie dining philosophers problem.
getThreadId : proc( -a Thread ) ;
kill,restart,suspend : proc( Thread )
)
type SemaphorePack is structure ( wait,signal : proc() )
type message is bool
use PS() with Library : env in
use Library with Concurrency : env in
use Concunency with threadPackage : ThreadPack ; semaphoreGen : proc( int -a SemaphorePack ) in
begin
let enter = true ; let exit = false let pickup = true ; let putdown = false use threadPackage as X[ Thread ] in begin
let Room = semaphoreGen( 4 )
let room = proc( message : message )
if message = exit then Room( signal )() else Room( wait )()
let forkSemaphore = proc( i : int SemaphorePack ) semaphoreGen( 1 )
let Forks = vector 0 to 4 using forkSemaphore
let forks = proc( i : int ; message : message )
if message = pickup then
Forks( i )( wait )() else Forks( i )( signal )()
let philosopherGenerator = proc( i : int -a Thread )
begin
let philosopher = proc() while true do
begin
! Think
room( enter ) ; ! Enter the room forks ( i,pickup ) ; ! Get one fork
forks( ( i + 1 ) rem 5,pickup ) ; ! Get two forks ! Eat
forks( i,putdown ) ; 1 Put down one fork forks ( ( i + 1 ) rem 5,putdown )
! Put down second fork room( exit ) ! Leave the room
end
! A new philosopher is bom X( start )( philosopher )
end
end let philosophers - vector 0 to 4 using philosopherGenerator end
Appendix B Atomic Transaction Package
The listing below gives a the full description of the atomic transaction package hardwired into the system. The listing is split into 5 sections with the following interpretation
• a section which maintains a binary tree of objects read and written on a per- transaction basis.
• a section which maintains the transaction data structures.
• a section which has code to handle the transaction events. This includes the code that is called from the abstract machine when an object is read or written. The code for the conflict resolution on a commit uses the conflict serializability method described in chapter 4.
• a section which describes the language level interface to the package.
• an example program of transactions on a simple bank account.
! Here are the index (binary tree) types for the pids within a transaction
type pId is int
rec type pidlndex is variant (node : Node; tip ; null)
& Node is structure (key : pid ; left, right : pidlndex)
let nilPidlndex = pidlndex (tip : nil)
rec let pidEnter = proc (k : pid; i : pidlndex > pidlndex) ÎEnter the value into the binaiy tree indexed by key
if i is tip then pidlndex (node : Node (k, nilPidlndex, nilPidlndex)) else case true of
k < i'node (key) : { i'node (left) := pidEnter (k, i'node (left)) ; i } k > i'node (key) : { i'node (right) := pidEnter (k, i'node (right)) ; i }
default : i
let pidLookup = proc (k : pid; i : pidlndex > bool)
Hookup the value in the binary Uee
begin
let head := i