-- TransactionTestImpl.mesa
-- Last edited by
-- Kolling on June 27, 1983 3:38 pm

DIRECTORY

AlpDebug
USING[CrashSystem],
AlpFile
USING[allProperties, Close, Delete, GetSize, Handle, Open, ReadPages, ReadProperties,
standardFile, Unknown, WritePages],
AlpineEnvironment
USING[AccessList, ByteCount, bytesPerPage, FileStore, FileVersion, nullTransID,
nullUniversalFile, nullVolumeGroupID, OwnerName, PageCount, PageNumber,
PageRun, Principal, Property, PropertyValuePair, TransID, UniversalFile,
VolumeGroupID, wordsPerPage],
AlpineInterimDirectory
USING[Error, Open, OpenUnderTrans],
AlpInstance
USING[Create, Handle, OperationFailed],
AlpTransaction
USING[AssertAlpineWheel, Create, CreateOwner, Finish, GetNextVolumeGroup, Handle],
CedarSnapshot
USING[After, Register],
File
USING[Type],
FileIO
USING[Open, STREAM],
FileTypes
USING[tUnassigned],
Inline
USING[LongDivMod],
IO
USING[Close],
LocalWorkstationServer
USING[Initialize],
Process
USING[Detach],
RandomCard
USING[Init, Choose],
RPC
USING[EncryptionKey, MakeKey],
Rope
USING[Cat, Equal, ROPE],
System
USING[GreenwichMeanTime, SecondsSinceEpoch],
UserCredentials
USING[GetUserCredentials];


TransactionTestImpl: MONITOR
IMPORTS AlpD: AlpDebug, AlpF: AlpFile, AlpineInterimDirectory, AlpI: AlpInstance,
AlpT: AlpTransaction, CedarSnapshot, FileIO, Inline, IO, LocalWorkstationServer, Process,
RandomCard, RPC, Rope, System, UserCredentials =

BEGIN OPEN AE: AlpineEnvironment;


-- This test uses the logged in caller/psw, and creates files on the "first" volumeGroup of the filestore. It keeps track of what it is doing in a history file, called "<TransactionTest>TransactionTest.HistoryFile", registered in the AlpineInterimDirectory; this file is used for consistency checking and to keep track of the data files created, so that their space will not be lost if the test ends in an ERROR. The test also records information in a local file, "TransactionTest.Info", which is just a sort of typescript, so you can tell what's going on. The test creates an owner record for the caller and one for "TransactionTest" (it correctly handles owner already exists and so forth), therefore the caller must be an Alpine wheel. At completion, the test will leave the owner records around.

-- This is how to run the test:

-- The test runs in one of four states: local server with no sporadic rollbacks (crashes), local server with rollbacks, remote server with no rollbacks, and remote server with rollbacks. It also can be set up to execute one "run", or many "runs" with varying parameters. At the end of each run, consistency checking is done and if the state is consistent the data files and the history file are deleted.

-- 1. If you are going to test a local instance of Alpine:

-- Run AlpineImpls
-- Run LocalWorkstationServerImpl
-- ← AlpineControl.Initialize[filePageMgrNormalChunks: 50, filePageMgrLogChunks: 50, filePageMgrLeaderChunks: 10, coordinatorMapHashArraySlots: 64, transactionMapHashArraySlots: 64, fileMapHashArraySlots: 43, openFileMapHashArraySlots: 256, lockHashArraySlots: 256, fileMapFinalizationQueueLength: 50, nAccessCacheEntries: 20, alpineWheels: "AlpineWheels^.pa", staticZoneInitialSize: 1024]; or other parameter settings.

-- If you are going to test a remote instance of Alpine, start that server; if you intend to run with rollbacks of that server, be sure the server has the appropriate rollback proc installed.

-- 2. run TransactionTest.

-- 3. invoke TransactionTestImpl.StartTest with the appropriate parameters, which include specifying rollback and more than one run ("random"). This initializes the typescript file. If more than one run has been specified, the various parameters (NNormalTransactions, etc.) are treated as maximum settings rather than specific settings. For all cases except local + rollback, this will start the test running.

-- 4. If you have specified local + rollback, do a checkpoint. This will start the test, since the test is invoked from a rollback/checkpoint proc which (for the local + rollback case only) TransactionTest has registered in step 3.

-- 5. If StartTest was invoked in step 3 with random = FALSE, the test will execute one run; otherwise, it will execute repeated runs, with differing parameters, until you explicitly stop it (or until it detects an error during the consistency checking). To stop the random test, invoke the procedure StopTest from the interpreter; this will stop the test, with consistency checking and file deletion, at the completion of its current run.

-- 6. If the test detects an error it will error with the history file still existing. After investigating the error, you will want to delete the history file and (probably) any data files it points to. The procedure CleanUp may be called to do this; it deletes the history file and it attempts to delete the data files the history file points to; it can be told to ignore "file not found" for the data files.

-- 7. Rollbacks: The rollbacks are not invoked during the initial setup of each run, rather they are invoked by any of the worker processes.

-- local case: The rollback procedure will restart the local server instance, and do consistency checking. If the checking completes successfully the CleanUp will happen, and then if it is running "random", the test will continue with the next run, otherwise it will stop in its normal end of run fashion.

-- remote case: A rollback will evidence itself as an RPC.CallFailed[timeout or unbound], in which case the test does the following: if the detector is a worker process, it stops and "goes to completion"; if the detector is the controlling process in consistency checking, it waits 2 minutes, and then attempts to reconnect and start again at the appropriate place. Note that if the remote server has really crashed as opposed to rolling back, we want to notice that, so the test will indicate that it is waiting in some obvious fashion.

-- 8. In order to avoid losing space on the remote server due to losing pointers to data files, the test makes it difficult to inadvertantly delete the history file without releasing the data files it points to. In particular, when you invoke StartTest for a remote server, it will invoke CleanUp if the history file already exists. (Invoking StartTest in the local case wipes the local server, so this precaution is not necessary.)

-- 9. Note that when the test is run in the local + rollback case, it has registered a rollback proc. At normal completion, it will unregister this proc. Calling CleanUp will also unregister this proc. If, after an error ending to the test, you inadvertantly rollback before calling CleanUp or booting, the rollback proc will try to analyze the history file.

-- Note that the settings of the constants ListCallerIsIn and ListCallerIsNotIn should be adjusted, if necessary, if the caller is not me (Kolling).



PagesPerHistoryInitialInfo: NAT = 1;
WordsPerHistoryInitialInfo
: NAT = PagesPerHistoryInitialInfo*AE.wordsPerPage;
CompileTimeCheck1
: [WordsPerHistoryInitialInfo..WordsPerHistoryInitialInfo] =
SIZE[HistoryInitialInfo];

HistoryInitialInfo
: TYPE = MACHINE DEPENDENT RECORD[
nextSeed (0): INTEGER,
historyMaxPageNumber (1): AE.PageCount,
nNormalTrans
(4): NAT,
nErrorTrans (5): NAT,
nSharingTransSets (6): NAT,
nTransPerSharingSet (7: 0..2): [1..MaxFilesPerTrans],
nFilesPerNormalTrans (7: 3..5): [1..MaxFilesPerTrans],
nFilesPerSharingTransSets (7: 6..8): [1..MaxFilesPerTrans],
nPageRunStatesPerNormalTransFile (7: 9..12): [1..MaxPageRunStatesPerFile],
gap1 (7: 13..15): [0..7B],
nPageRunStatesPerSharingTransSetFile
(8: 0..3): [1..MaxPageRunStatesPerFile],
gap2 (8: 4..15): [0..7777B],
padding
(9): ARRAY [9..WordsPerHistoryInitialInfo) OF UNSPECIFIED
];



PagesPerHistoryDataEntry
: NAT = 2;
WordsPerHistoryDataEntry
: NAT = PagesPerHistoryDataEntry*AE.wordsPerPage;
CompileTimeCheck2
: [WordsPerHistoryDataEntry..WordsPerHistoryDataEntry] =
SIZE[HistoryDataEntry];

HistoryDataEntry
: TYPE = MACHINE DEPENDENT RECORD[
transID(0): AE.TransID,
timeStamp(SIZE[AE.TransID]): LONG CARDINAL,
nFileStates(SIZE[AE.TransID] + SIZE[LONG CARDINAL]): CARDINAL,
fileStates(SIZE[AE.TransID] + SIZE[LONG CARDINAL] + SIZE[CARDINAL]):
ARRAY [0..MaxFilesPerTrans) OF FileState,
padding(SIZE[AE.TransID] + SIZE[LONG CARDINAL] + SIZE[CARDINAL] + SIZE[ARRAY
[0..MaxFilesPerTrans) OF FileState]):
ARRAY [SIZE[AE.TransID] + SIZE[LONG CARDINAL] + SIZE[CARDINAL]
+ SIZE[ARRAY[0..MaxFilesPerTrans) OF
FileState]..WordsPerHistoryDataEntry) OF UNSPECIFIED
];

FileState
: TYPE = RECORD[
universalFile: AE.UniversalFile,
unshared: BOOLEAN,
deleted: BOOLEAN,
byteLength: AE.ByteCount,
createTime
: LONG CARDINAL,
runningHighWaterMark: AE.PageCount,
modifyAccess
: PackedAccessList,
owner: BOOLEAN,
readAccess: PackedAccessList,
size: AE.PageCount,
stringName
: PackedStringName,
versionIncrements: INT,
dirtied:
BOOLEAN,
nPageRunStates: NAT,
pageRunStates: ARRAY [0..MaxPageRunStatesPerFile) OF PageRunState];

PageRunState
: TYPE = RECORD[
pageRun: AE.PageRun,
seed: INTEGER];

PackedStringName: TYPE = RECORD[count: NAT, chars: ARRAY [0..64) OF CHARACTER];
nullPackedStringName: PackedStringName = [count: 0, chars: ALL[' ]];

PackedAccessList
: TYPE = MACHINE DEPENDENT RECORD[world, owner: BOOLEAN, list1, list2:
{null, in, notIn}, other: BOOLEAN];
nullPackedAccessList: PackedAccessList = [world: FALSE, owner: FALSE, list1: null,
list2: null, other: FALSE];

ListCallerIsIn: Rope.ROPE = "CedarImplementors^.pa";-- any list the caller is in.
ListCallerIsNotIn: Rope.ROPE = "YogaClass^.pa"; -- any list the caller is not in.





-- parameters are "in core" at start of DoTest.

DoTest
: PROCEDURE =
BEGIN -- errors:
-- xx
DO
nextSeed ← GenDataStructures[];
InitHistoryFileWithTestParams[];

--do test stuff;



[ ] ← CheckConsistency[];
CleanUp[FALSE];
IF ((NOT runningRandom) OR forcedStop) THEN EXIT;
[] ← RandomCard.Init[seed: ABS[nextSeed]];
UpdateTestParams[];
ENDLOOP
;


END
;


-- At the end of a run of the test, we want to check both file properties and the contents of data pages.

-- How data pages are checked:


-- Because data pages may be overwritten both by the same transaction and by other transactions (if the file is shared), and because transactions can change the file's highWaterMark and size, the current contents of a data page may legitimately not match the recorded contents for a transaction that has written that page. As we see a record of a write into a data page, we record in a volatile structure whether the contents of that page match the recorded write or not. The volatile record for a page reflects the most recent transaction we have seen for that page. Thus at intermediate stages the volatile structure may contain entries for bad pages, but hopefully when all transactions have been looked at and the highWaterMark and size changes taken into account, all the entries will have become good.


-- How file properties are checked:


-- (type is always AlpFile.standardFile).

--      Explicitly Deltaed  Explicitly Deltaed
--       for Unshared.    for Shared
-- exists       Maybe    No
-- byteLength     Maybe    Maybe, but very infrequently
-- createTime     Maybe    Maybe, but very infrequently
-- highWaterMark    Maybe    Maybe, but very infrequently
-- modifyAccess    Maybe    Maybe, but very infrequently
-- owner      Maybe    No
-- readAccess     Maybe    Maybe, but very infrequently
-- size       Maybe    No
-- stringName     Maybe    Maybe, but very infrequently
-- type       No      No
-- version      Maybe    Maybe, but very infrequently


-- If a file is unshared: we expect all the "real" properties to match the "recorded" properties.

-- If the file is shared: some properties must match (exists must be TRUE, the owner must match reality, and the size is fixed at file creation time), others may be bad for that particular transaction because a later transaction has changed them; the latter properties are handled pretty nearly the same way data pages are handled. Shared files have their HWM initialized to the size.

-- If a value has not been explicitly set for a property, then we demand that it have its default value; the property fields in the FileStates are initialized to the default values to make checking easier. Note: the "recorded" highWaterMark is set when the trans explicitly sets the HWM, when the trans writes past that HWM, and when the trans sets the size < that HWM. To keep things simple, we never explicitly increase the highWaterMark after the first write for the file (avoids bugs such as: write page n, set hwm < n, page gets clobbered, set hwm > n, checking routine thinks it should believe n.). We do not bother to record data pages in the volatile structure if they are past the "local" highWaterMark.

-- After all the transactions that touch a given file have been seen, the following are error conditions if found in the volatile data structure:
-- Any data page which is marked bad and which is <= the real high water mark.
-- Any file property which is marked bad.

-- The "default values" are:
--  exists     TRUE.
--  byteLength   0.
--  createTime   >= TestStartTime and <= the commit time of
--       the oldest transaction which touched the file.
--  highWaterMark  unshared: no. of highest page written + 1, shared: initial value of
--        size at file creation.
--  modifyAccess  Alpine default (owner).
--  owner     initial value at file creation.
--  readAccess   Alpine default (world).
--  size     initial value at file creation.
--  stringName   Alpine default (NIL).
--  type     N.A.
--  version    unshared: 1, shared: 1 + no. of trans that dirtied the file.


regFiles: LIST OF RegFile ← NIL;

RegFile
: TYPE = RECORD[
universalFile: AE.UniversalFile,
unshared: BOOLEAN,
realByteLength: AE.ByteCount, -- real values, saved here for convenience.
realCreateTime: LONG CARDINAL,
realHighWaterMark: AE.PageCount,
realModifyAccess: PackedAccessList,
realOwner: BOOLEAN,
realReadAccess: PackedAccessList,
realSize: AE.PageCount,
realStringName: PackedStringName,
realVersion: AE.FileVersion,
state: ARRAY VolatileFileProp OF StateAndTimeRec, -- current state of properties that may have been changed by > 1 transaction (except for createTime and version).
dirtyCount: NAT, -- for checking
versionIncrements: NAT, -- version.
pages: LIST OF RegPages];

VolatileFileProp
: TYPE = {byteLength, highWaterMark, modifyAccess, readAccess,
stringName};

StateAndTimeRec
: TYPE = RECORD[state: State, timeStamp: LONG CARDINAL];
State: TYPE = {good, bad, unknown};

RegPages
: TYPE = RECORD[pageNumber: AE.PageNumber, okay: BOOLEAN, timeStamp: LONG
CARDINAL
];

BufferPageRun
: TYPE = ARRAY [0..MaxPageRunSizePerTransaction) OF BufferPage;
BufferPage: TYPE = ARRAY [0..AE.wordsPerPage) OF WORD;

CheckConsistency
: PROCEDURE RETURNS[nextSeed: INTEGER] =
BEGIN -- errors:
instHandle: AlpI.Handle ← AlpI.Create[fileStore, caller, key];
transHandle: AlpT.Handle ← AlpT.Create[instHandle, TRUE];
historyUniversalFile: AE.UniversalFile ← AlpineInterimDirectory.OpenUnderTrans[transHandle,
historyFileName, oldOnly, 0].universalFile;
historyFileHandle: AlpF.Handle ← AlpF.Open[transHandle, historyUniversalFile, readWrite,
[write, wait], log, random].handle;
bufferPage: BufferPage;
historyInitialInfo: HistoryInitialInfo;
historyDataEntry: HistoryDataEntry;
historyFileHandle.ReadPages[[0, PagesPerHistoryInitialInfo],
DESCRIPTOR[@historyInitialInfo, AE.wordsPerPage], [write, wait]];
nextSeed ← historyInitialInfo.nextSeed;
IF (historyMaxPageNumber ← historyInitialInfo.historyMaxPageNumber) = 0 THEN ERROR;
regFiles ← NIL;
FOR historyPageNumber: AE.PageNumber ← PagesPerHistoryInitialInfo,
historyPageNumber + PagesPerHistoryDataEntry
UNTIL historyPageNumber >= historyMaxPageNumber
DO
historyFileHandle.ReadPages[[historyPageNumber, PagesPerHistoryDataEntry],
DESCRIPTOR[@historyDataEntry, WordsPerHistoryDataEntry], [write, wait]];
IF historyDataEntry.transID = AE.nullTransID
THEN BEGIN
LogAbortedTransInTypescript[]; LOOP; END;
IF
historyDataEntry.timeStamp = 0 THEN ERROR;
FOR
fileIndex: INT DECREASING IN [0..historyDataEntry.nFileStates)
DO
universalFile: AE.UniversalFilehistoryDataEntry.fileStates[fileIndex].universalFile;
fileFound: BOOLEANTRUE;
listRegFile: LIST OF RegFile ← NIL;
newReg: BOOLEAN;
fileHandle: AlpF.Handle;
[fileHandle, ] ← AlpF.Open[transHandle: transHandle, universalFile:
universalFile, access: readWrite, lock: [write, wait], recoveryOption: log,
referencePattern: random
! AlpF.Unknown => IF what = fileID THEN
BEGIN fileFound ← FALSE; CONTINUE; END];
-- exists: unshared must match now, shared should always be TRUE.
IF
(NOT fileFound)
THEN BEGIN
IF NOT (historyDataEntry.fileStates[fileIndex].unshared AND
historyDataEntry.fileStates[fileIndex].deleted) THEN ERROR;
LOOP;
END;
[listRegFile, newReg] ←
RegisterFileExistence[historyDataEntry.fileStates[fileIndex].universalFile,
historyDataEntry.fileStates[fileIndex].unshared];
-- type: demanded to match AlpFile.standardFile when read, then discarded.
IF
newReg THEN ReadAndRegRealProps[fileHandle, listRegFile];

-- createTime: must be >= teststarttime (checked in read props routine) and <= commit time of any trans that touches it.
IF
listRegFile.first.realCreateTime > historyDataEntry.timeStamp THEN ERROR;

-- owner: unshared must match now, shared is set once only at start up.
IF
listRegFile.first.realOwner # historyDataEntry.fileStates[fileIndex].owner THEN ERROR;

-- size: unshared must match now, shared is set once only at start up.
IF listRegFile.first.realSize # historyDataEntry.fileStates[fileIndex].size THEN ERROR;

-- simple cases of may
be changed by > 1 transaction.
RegisterStateProps[listRegFile, historyDataEntry.fileStates[fileIndex],
historyDataEntry.timeStamp];

-- version:
IF historyDataEntry.fileStates[fileIndex].dirtied
THEN listRegFile.first.dirtyCount ← listRegFile.first.dirtyCount + 1;
listRegFile.first.versionIncrementslistRegFile.first.versionIncrements
+ historyDataEntry.fileStates[fileIndex].versionIncrements;

FOR
pageRunIndex: INT DECREASING IN
[0..historyDataEntry.fileStates[fileIndex].nPageRunStates)
DO
pageRun: AE.PageRun ←
historyDataEntry.fileStates[fileIndex].pageRunStates[pageRunIndex].pageRun;
seed: CARDINAL
historyDataEntry.fileStates[fileIndex].pageRunStates[pageRunIndex].seed;
FOR
pageIndex: AE.PageNumber IN [pageRun.firstPage..pageRun.firstPage +
pageRun.count)
DO
IF historyDataEntry.fileStates[fileIndex].runningHighWaterMark <= pageIndex THEN EXIT;
fileHandle.ReadPages[[pageIndex, 1], DESCRIPTOR[@bufferPage, AE.wordsPerPage],
[write, wait]];
Register[listRegFile, pageIndex, historyDataEntry.timeStamp, ((bufferPage[0] = seed)
AND
(bufferPage[1] = (seed + pageIndex - pageRun.firstPage)))];
ENDLOOP;
ENDLOOP;
fileHandle.Close[];

ENDLOOP
;
ENDLOOP
;
historyFileHandle.Close[];


FindBadInRegistry[];

-- deleteFiles.
FOR
file: LIST OF RegFile ← regFiles, file.rest
UNTIL
file = NIL
DO

fileHandle: AlpF.Handle ← AlpF.Open[transHandle: transHandle, universalFile:
file.first.universalFile, access: readWrite, lock: [write, wait], recoveryOption: log,
referencePattern: random].handle;
fileHandle.Delete;
ENDLOOP;

--xx delete history file also;
IF transHandle.Finish[commit, FALSE] # commit THEN xxxxx;

END;



RegisterFileExistence
: PROCEDURE[universalFile: AE.UniversalFile, unshared: BOOLEAN]
RETURNS[listRegFile: LIST OF RegFile, newReg: BOOLEAN]=
BEGIN
FOR
file: LIST OF RegFile ← regFiles, file.rest
UNTIL
file = NIL
DO

IF universalFile = file.first.universalFile
THEN BEGIN -- found file.
IF unshared OR file.first.unshared THEN ERROR;
RETURN
[file, FALSE];
END;
ENDLOOP;
regFiles ← CONS[[universalFile: universalFile,
unshared: unshared,
realByteLength: 0,
realCreateTime: 0,
realHighWaterMark: 0,
realModifyAccess: nullPackedAccessList,
realOwner: TRUE,
realReadAccess: nullPackedAccessList,
realSize: 0,
realStringName: nullPackedStringName,
realVersion: 0,
state: ALL[[state: unknown, timeStamp: 0]],
dirtyCount: 0,
versionIncrements: 0,
pages: NIL], regFiles];
RETURN
[regFiles, TRUE];
END
;


ReadAndRegRealProps
: PROCEDURE[fileHandle: AlpF.Handle, listRegFile: LIST OF RegFile] =
BEGIN
realType: File.TypeFileTypes.tUnassigned;
bugCheck: ARRAY AE.Property OF BOOLEANALL[FALSE];
fileProperties: LIST OF AE.PropertyValuePair ← fileHandle.ReadProperties[AlpF.allProperties,
[write, wait]];
FOR list: LIST OF AE.PropertyValuePair ← fileProperties, list.rest
UNTIL list = NIL
DO
IF bugCheck[list.first.property] THEN ERROR;
SELECT
list.first.property FROM
byteLength => listRegFile.first.realByteLength NARROW[list.first,
AE.PropertyValuePair.byteLength].byteLength;
createTime => BEGIN listRegFile.first.realCreateTime
System.SecondsSinceEpoch[NARROW[list.first,
AE.PropertyValuePair.createTime].createTime];
IF
listRegFile.first.realCreateTime < TestStartTime THEN ERROR;
END;
highWaterMark => listRegFile.first.realHighWaterMark ← NARROW[list.first,
AE.PropertyValuePair.highWaterMark].highWaterMark;
modifyAccess => listRegFile.first.realModifyAccessPackAccessList[NARROW[list.first,
AE.PropertyValuePair.modifyAccess].modifyAccess];
owner => BEGIN realOwnerRope: Rope.ROPE ← NARROW[list.first,
AE.PropertyValuePair.owner].owner;
listRegFile.first.realOwner ← SELECT TRUE FROM
Rope.Equal[realOwnerRope, caller, FALSE] => TRUE,
Rope.Equal[realOwnerRope, "TransactionTest", FALSE] => FALSE,
ENDCASE => ERROR;
END;

readAccess => listRegFile.first.realReadAccess ← PackAccessList[NARROW[list.first,
AE.PropertyValuePair.readAccess].readAccess];
stringName => listRegFile.first.realStringName ← PackStringName[NARROW[list.first,
AE.PropertyValuePair.stringName].stringName];
type => BEGIN realType ← NARROW[list.first, AE.PropertyValuePair.type].type;
IF realType # AlpFile.standardFile THEN ERROR;
END;
version => listRegFile.first.realVersion ← NARROW[list.first,
AE.PropertyValuePair.version].version;
ENDCASE => ERROR;
bugCheck[list.first.property] ← TRUE;
ENDLOOP;

listRegFile.first.realSize ← fileHandle.GetSize[[write, wait]];
FOR
prop: AE.Property IN AE.Property
DO IF
(NOT bugCheck[prop]) THEN ERROR;
ENDLOOP;
END;


RegisterStateProps
: PROCEDURE[listRegFile: LIST OF RegFile, fileState: FileState, timeStamp:
LONG CARDINAL] =
BEGIN
FOR volatileFileProp: VolatileFileProp IN VolatileFileProp
DO
IF
listRegFile.first.state[volatileFileProp].timeStamp < timeStamp
THEN BEGIN
okay: BOOLEAN ←
SELECT
volatileFileProp FROM
byteLength => listRegFile.first.realByteLength = fileState.byteLength,
highWaterMark => listRegFile.first.realHighWaterMark =
fileState.runningHighWaterMark,
modifyAccess => ComparePackedAccessLists[listRegFile.first.realModifyAccess,
fileState.modifyAccess],
readAccess => ComparePackedAccessLists[listRegFile.first.realReadAccess,
fileState.readAccess],
stringName => ComparePackedStringNames[listRegFile.first.realStringName,
fileState.stringName],
ENDCASE
=> ERROR;
listRegFile.first.state[volatileFileProp] ← [state: okay, timeStamp: timeStamp];
END;
ENDLOOP;
END
;


Register
: PROCEDURE[listRegFile: LIST OF RegFile, pageNumber: AE.PageNumber, timeStamp:
LONG CARDINAL, okay: BOOLEAN] =
BEGIN
FOR pages: LIST OF RegPages ← listRegFile.first.pages, pages.rest
UNTIL pages = NIL
DO
IF pages.first.pageNumber = pageNumber THEN
BEGIN
-- if same trans, okay due to order of calling.
IF pages.first.timeStamp < timeStamp THEN pages.first ← [pageNumber, okay, timeStamp];
RETURN;
END;
ENDLOOP;
listRegFile.first.pages ← CONS[[pageNumber, okay, timeStamp], listRegFile.first.pages];
END;


FindBadInRegistry: PROCEDURE =
BEGIN -- errors:
FOR listRegFile: LIST OF RegFile ← regFiles, listRegFile.rest
UNTIL
listRegFile = NIL
DO

-- check properties:
FOR volatileFileProp: VolatileFileProp IN VolatileFileProp
DO
IF
listRegFile.first.state[volatileFileProp].state = bad THEN ERROR;
ENDLOOP;

IF listRegFile.first.realVersion #
(1 + listRegFile.first.dirtyCount + listRegFile.first.versionIncrements)
THEN ERROR;

-- check data pages:

FOR
pages: LIST OF RegPages ← listRegFile.first.pages, pages.rest
UNTIL pages = NIL
DO
IF ((NOT pages.first.okay) AND
(pages.first.pageNumber < listRegFile.first.realHighWaterMark)) THEN ERROR;
ENDLOOP;
ENDLOOP;

END;


-- Checks what we expect for the Alpine defaults:


CheckMatchesDefaultModifyAccessList
: PROCEDURE[accessList: AE.AccessList,
ownerIsCaller: BOOLEAN] =
BEGIN -- errors:
IF accessList = NIL THEN ERROR;
SELECT TRUE FROM
ownerIsCaller => -- expect one entry: "owner".
BEGIN
IF
accessList.rest # NIL THEN ERROR;
IF
NOT Rope.Equal[accessList.first, "owner", FALSE] THEN ERROR;
END;
ENDCASE => -- expect two entries: "owner" and caller.
BEGIN
IF (
(accessList.rest = NIL) OR (accessList.rest.rest # NIL)) THEN ERROR;
IF
((NOT Rope.Equal[accessList.first, "owner", FALSE]) AND (NOT
Rope.Equal[accessList.rest.first, "owner", FALSE])) THEN ERROR;
IF ((NOT Rope.Equal[accessList.first, caller, FALSE]) AND (NOT
Rope.Equal[accessList.rest.first, caller, FALSE])) THEN ERROR;
END;
END;


CheckMatchesDefaultReadAccessList
: PROCEDURE[accessList: AE.AccessList] =
BEGIN -- errors:
IF (NOT
Rope.Equal[accessList.first, "owner", FALSE]) OR (accessList.rest # NIL)
THEN ERROR;
END;


DefaultStringName
: Rope.ROPE ← "";




CleanUp
: PROCEDURE[ignoreFileNotFound: BOOLEAN] =
BEGIN -- errors:
-- xx
--xx delete history file
--xx delete date files
-- unreg rollback proc if local + rollback.
END;



StartTest
: PROCEDURE[server: AE.FileStore, crashServer: BOOLEAN, random: BOOLEAN,
nNormalTransactions, nErrorTransactions, nSharingTransactionSets, nTransactionsPerSharingSet:
NAT, nFilesPerNormalTransactions, nFilesPerSharingTransactionSets: [1..MaxFilesPerTrans],
nPageRunStatesPerNormalTransactionFile, nPageRunStatesPerSharingTransactionSetFile:
[1..MaxPageRunStatesPerFile], seed: INTEGER] =
BEGIN
-- errors: all errors uncaught here are fatal.
psw: Rope.ROPE;
fileStore ← server;
crashes ← crashServer;
runningRandom ← random;
nextSeed ← seed;
maxNNormalTrans ← nNormalTrans ← nNormalTransactions;
maxNErrorTrans ← nErrorTrans ← nErrorTransactions;
maxNSharingTransSets ← nSharingTransSets ← nSharingTransactionSets;
maxNTransPerSharingSet ←nTransPerSharingSet ← nTransactionsPerSharingSet;
maxNFilesPerNormalTrans ← nFilesPerNormalTrans ← nFilesPerNormalTransactions;
maxNFilesPerSharingTransSets ← nFilesPerSharingTransSets ← nFilesPerSharingTransactionSets;
maxNPageRunStatesPerNormalTransFile ← nPageRunStatesPerNormalTransFile ←
nPageRunStatesPerNormalTransactionFile;
maxNPageRunStatesPerSharingTransSetFile ← nPageRunStatesPerSharingTransSetFile ←
nPageRunStatesPerSharingTransactionSetFile;
[caller, psw] ← UserCredentials.GetUserCredentials[];
key ← RPC.MakeKey[psw];
historyFileName ← Rope.Cat["[", fileStore, "]<TransactionTest>TransactionTest.HistoryFile"];
IF Rope.Equal[fileStore, "local.alpine", FALSE]
THEN BEGIN
LocalWorkstationServer.Initialize[fileStore: fileStore, typeOfRestart: createServer,
nLogPages: 1000, nOwners: 10];
instHandle ← AlpI.Create[fileStore, caller, key];
END
ELSE BEGIN
[instHandle, , ]AlpineInterimDirectory.Open[fileName: historyFileName,
createOptions: oldOnly, initialByteAllocation: 0
! AlpineInterimDirectory.Error => IF ((why = fileNotFound) OR (why =
ownerNotFound)) THEN GOTO noHistoryFile;];
CleanUp[FALSE];
EXITS
noHistoryFile => NULL;
END
;
-- create owner records.
transHandle ← AlpT.Create[instHandle, TRUE];
transHandle.AssertAlpineWheel[TRUE];
volumeGroupID ← transHandle.GetNextVolumeGroup[AE.nullVolumeGroupID, [none, wait]];
[] ← transHandle.CreateOwner[volumeGroupID: volumeGroupID, owner:
caller, properties: LIST[[quota[4000]]], enforceTotalQuota: TRUE
! AlpI.OperationFailed => IF why = duplicateOwner THEN CONTINUE];
[] ← transHandle.CreateOwner[volumeGroupID: volumeGroupID, owner:
"TransactionTest", properties: LIST[[quota[4000]]], enforceTotalQuota: TRUE
! AlpI.OperationFailed => IF why = duplicateOwner THEN CONTINUE];
-- set up history file.
IF transHandle.Finish[requestedOutcome: commit, continue: FALSE] # commit THEN ERROR;
-- create typescript.
typescriptStream ← FileIO.Open[fileName: "TransactionTest.info", accessOptions: overwrite,
createOptions: none];
LogStartOfTestInTypescript[];
typescriptStream.Close[];
[] ← RandomCard.Init[seed: ABS[nextSeed]];
IF (Rope.Equal[fileStore, "local.alpine", FALSE] AND crashes)
THEN CedarSnapshot.Register[r: RollbackTest]
ELSE DoTest[];
END;


RollbackTest
: PROCEDURE[after: CedarSnapshot.After] =
BEGIN
-- errors:
IF NOT Rope.Equal[fileStore, "local.alpine", FALSE] THEN ERROR;
IF
after = checkpoint -- weird form to keep compiler happy.
THEN Process.Detach[FORK DoTest[]]
ELSE Process.Detach[FORK RollbackDoTest[]];
END;


RollbackDoTest
: PROCEDURE =
BEGIN
-- errors:
LogRollbackInTypescript[];
LocalWorkstationServer.Initialize[fileStore: fileStore, typeOfRestart: warmStart, nLogPages: 1000,
nOwners: 10];
nextSeed ← CheckConsistency[];
CleanUp[FALSE];
IF (runningRandom AND (NOT forcedStop))
THEN BEGIN
[] ← RandomCard.Init[seed: ABS[nextSeed]];
UpdateTestParams[];
DoTest[];
END
ELSE LogEndOfTestInTypescript[]
END;




LogStartOfTestInTypescript
: PROCEDURE =
BEGIN
-- xx
END
;


LogRollbackInTypescript
: PROCEDURE =
BEGIN
-- xx
END
;


LogEndOfTestInTypescript
: PROCEDURE =
BEGIN
-- xx
END
;


ComparePackedAccessLists
: PROCEDURE[packedAccessList1, packedAccessList2:
PackedAccessList] RETURNS[equal: BOOLEAN] =
BEGIN
-- xx
END
;


ComparePackedStringNames
: PROCEDURE[packedStringName1, packedStringName2:
PackedStringName] RETURNS[equal: BOOLEAN] =
BEGIN
-- xx
END
;


PackAccessList
: PROCEDURE[accessList: AE.AccessList] RETURNS[packedAccessList:
PackedAccessList] =
BEGIN
-- xx
END
;


PackStringName
: PROCEDURE[rope: Rope.ROPE] RETURNS[packedStringName:
PackedStringName] =
BEGIN
-- xx
END
;


InitHistoryFileWithTestParams
: PROCEDURE =
BEGIN
historyInitialInfo: HistoryInitialInfo ←
[nextSeed: nextSeed,
historyMaxPageNumber: 0,
nNormalTrans: nNormalTrans,
nErrorTrans: nErrorTrans,
nSharingTransSets: nSharingTransSets,
nTransPerSharingSet: nTransPerSharingSet,
nFilesPerNormalTrans: nFilesPerNormalTrans,
nFilesPerSharingTransSets: nFilesPerSharingTransSets,
nPageRunStatesPerNormalTransFile: nPageRunStatesPerNormalTransFile,
gap1: 0,
nPageRunStatesPerSharingTransSetFile: nPageRunStatesPerSharingTransSetFile,
gap2: 0,
padding: ALL[0]
];
historyDataEntry: HistoryDataEntry ←
[transID: AE.nullTransID,
timeStamp: 0,
nFileStates: 0,
fileStates: ALL[
[universalFile: AE.nullUniversalFile,
unshared: TRUE,
deleted: FALSE,
byteLength: -1,
createTime: 0,
runningHighWaterMark: -1,
modifyAccess: nullPackedAccessList,
owner: TRUE,
readAccess: nullPackedAccessList,
size: -1,
stringName: nullPackedStringName,
versionIncrements: -1,
dirtied: FALSE,
nPageRunStates: 0,
pageRunStates: ALL[[pageRun: [firstPage: 0, count: 0], seed: 0]]
]],
padding: ALL[0]
];
instHandle: AlpI.Handle ← AlpI.Create[fileStore, caller, key];
transHandle: AlpT.Handle ← AlpT.Create[instHandle, TRUE];
historyUniversalFile: AE.UniversalFile;
fileHandle: AlpFile.Handle;
historyInitialInfo.historyMaxPageNumber ← historyMaxPageNumber ← PagesPerHistoryInitialInfo
+ PagesPerHistoryDataEntry*(nNormalTrans + nErrorTrans +
nSharingTransSets*nTransPerSharingSet);
historyNextPageNumber ← 0;
historyUniversalFile ← AlpineInterimDirectory.OpenUnderTrans[transHandle,
historyFileName, newOnly, (historyMaxPageNumber*AE.bytesPerPage)].universalFile;
fileHandle ← AlpF.Open[transHandle, historyUniversalFile, readWrite,
[write, wait], log, random].handle;
fileHandle.WritePages[pageRun: [firstPage: 0, count: PagesPerHistoryInitialInfo],
pageBuffer: DESCRIPTOR[@historyInitialInfo, WordsPerHistoryInitialInfo],
lock: [write, wait]];
FOR
pageNumber: AE.PageNumber ← PagesPerHistoryInitialInfo, pageNumber +
PagesPerHistoryDataEntry
UNTIL pageNumber >= historyMaxPageNumber
DO
fileHandle.WritePages[pageRun: [firstPage: pageNumber, count:
PagesPerHistoryDataEntry], pageBuffer: DESCRIPTOR[@historyDataEntry,
WordsPerHistoryDataEntry], lock: [write, wait]];
ENDLOOP;
IF
transHandle.Finish[requestedOutcome: commit, continue: FALSE] # commit THEN xxxxx;
END
;


UpdateTestParams
: PROCEDURE =
BEGIN
nNormalTrans ← RandomCard.Choose[min: 0, max: maxNNormalTrans];
nErrorTrans ← RandomCard.Choose[min: 0, max: maxNErrorTrans];
nSharingTransSets ← RandomCard.Choose[min: 0, max: maxNSharingTransSets];
nTransPerSharingSet ← RandomCard.Choose[min: 0, max: maxNTransPerSharingSet];
nFilesPerNormalTrans ← RandomCard.Choose[min: 0, max: maxNFilesPerNormalTrans];
nFilesPerSharingTransSets ← RandomCard.Choose[min: 0, max: maxNFilesPerSharingTransSets];
nPageRunStatesPerNormalTransFile ← RandomCard.Choose[min: 0, max:
maxNPageRunStatesPerNormalTransFile];
nPageRunStatesPerSharingTransSetFile ← RandomCard.Choose[min: 0, max:
maxNPageRunStatesPerSharingTransSetFile];
END
;


StopTest
: ENTRY PROCEDURE =
BEGIN
forcedStop ← TRUE;
END
;


CrashServer
: ENTRY PROCEDURE[transHandle: AlpT.Handle] =
BEGIN
IF
forcedStop THEN RETURN;
IF
testtoseeifweshouldrollback
THEN
AlpD.CrashSystem[transHandle];
END;



-- params still valid after rollback:

fileStore
: AE.FileStore;
volumeGroupID
: AE.VolumeGroupID;
historyFileName: Rope.ROPE;

runningRandom: BOOLEAN;

caller: AE.Principal;
key: RPC.EncryptionKey;

MaxFilesPerTrans
: NAT = 4; -- adjust as necessary so recorded info will
MaxPageRunStatesPerFile: NAT = 8; -- fit on a data record in the history file.

MaxPageRunSizePerTransaction: NAT = 15;
MaxSizeOfFile: NAT = 300;

crashes: BOOLEAN;

maxNNormalTrans
: NAT;
maxNErrorTrans: NAT;
maxNSharingTransSets: NAT;
maxNTransPerSharingSet: [1..MaxFilesPerTrans];
maxNFilesPerNormalTrans: [1..MaxFilesPerTrans];
maxNFilesPerSharingTransSets: [1..MaxFilesPerTrans];
maxNPageRunStatesPerNormalTransFile: [1..MaxPageRunStatesPerFile];
maxNPageRunStatesPerSharingTransSetFile: [1..MaxPageRunStatesPerFile];



-- other params:

nextSeed: INTEGER;
forcedStop: BOOLEAN ← FALSE;

historyMaxPageNumber: NAT;
historyNextPageNumber: NAT;

nNormalTrans
: NAT;
nErrorTrans: NAT;
nSharingTransSets: NAT;
nTransPerSharingSet: [1..MaxFilesPerTrans];
nFilesPerNormalTrans: [1..MaxFilesPerTrans];
nFilesPerSharingTransSets: [1..MaxFilesPerTrans];
nPageRunStatesPerNormalTransFile: [1..MaxPageRunStatesPerFile];
nPageRunStatesPerSharingTransSetFile: [1..MaxPageRunStatesPerFile];

instHandle: AlpI.Handle;
transHandle: AlpT.Handle;

typescriptStream: FileIO.STREAM;



-- main line code:






END.