-- AlpineTestImpl.mesa
-- Last edited by
-- Kolling on July 19, 1983 12:45 pm
DIRECTORY

AlpDebug
USING[CrashSystem],
AlpFile
USING[allProperties, Close, Create, Delete, GetSize, Handle, Open, ReadPages,
ReadProperties, standardFile, Unknown, WritePages, WriteProperties],
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, Failed, Handle, OperationFailed],
AlpTransaction
USING[AssertAlpineWheel, Create, CreateOwner, Finish, GetNextVolumeGroup, Handle,
Unknown],
CedarSnapshot
USING[After, Deregister, Register],
File
USING[Type],
FileTypes
USING[tUnassigned],
IO
USING[bool, Close, Flush, int, PutF, PutRope, rope, STREAM],
LocalWorkstationServer
USING[Initialize],
Process
USING[Detach],
RandomCard
USING[Init, Choose, Next],
RPC
USING[CallFailed, EncryptionKey, MakeKey],
Rope
USING[Cat, Equal, Fetch, Length, ROPE],
System
USING[GetGreenwichMeanTime, GreenwichMeanTime, SecondsSinceEpoch],
UserCredentials
USING[GetUserCredentials],
UserTerminal
USING[BlinkDisplay],
ViewerIO
USING[CreateViewerStreams];


AlpineTestImpl: MONITOR
IMPORTS AlpD: AlpDebug, AlpF: AlpFile, AlpineInterimDirectory, AlpI: AlpInstance,
AlpT: AlpTransaction, CedarSnapshot, IO, LocalWorkstationServer, Process,
RandomCard, RPC, Rope, System, UserCredentials, UserTerminal, ViewerIO =

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 "<AlpineTest>AlpineTest.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, "AlpineTest.typescript", 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 "AlpineTest" (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 AlpineTestLocal or AlpineTestRemote.

-- 3. invoke AlpineTestImpl.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 (nTransactionSets, 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) AlpineTest 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 deletion of data and history files 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[unbound], in which case the test does the following: if a worker process detects this, 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 and the history file already exists, the test will delete the data files and history file. (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. Explicitly 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 = 2;
WordsPerHistoryInitialInfo
: NAT = PagesPerHistoryInitialInfo*AE.wordsPerPage;
CompileTimeCheck1
: [WordsPerHistoryInitialInfo..WordsPerHistoryInitialInfo] =
SIZE[HistoryInitialInfo];

HistoryInitialInfo
: TYPE = MACHINE DEPENDENT RECORD[
seed (0): INTEGER,
historyMaxPageNumber (1): AE.PageNumber,
testStartTime
(3): LONG CARDINAL,
nTransSets (5): NAT,
nTransPerSet (6): NAT,
nSharedFilesPerTrans (7: 0..2): [0..MaxFilesPerTrans],
nUnsharedFilesPerTrans (7: 3..5): [0..MaxFilesPerTrans],
nPageRunsPerSharedFile (7: 6..9): [0..MaxPageRunStatesPerFile],
nPageRunsPerUnsharedFile (7: 10..13): [0..MaxPageRunStatesPerFile],
gap (7: 14..15): [0..3B],
nErrorTrans
(8): NAT,
nSharedFilesCreated (9: 0..4): [0..MaxSharedFilesEachRun],
gap1 (9: 5..15): [0..3777B],
sharedFiles
(10): ARRAY [0..MaxSharedFilesEachRun) OF AE.UniversalFile,
padding (10 + SIZE[ARRAY [0..MaxSharedFilesEachRun) OF AE.UniversalFile]): ARRAY [10 +
SIZE[ARRAY [0..MaxSharedFilesEachRun) OF AE.UniversalFile]..WordsPerHistoryInitialInfo)
OF UNSPECIFIED
];



PagesPerHistoryDataEntry
: NAT = 3;
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,
dirtied: BOOLEAN,
initialSize: AE.PageCount,
runningHighWaterMark:
AE.PageCount,
changed:
ARRAY AllFileProp OF BOOLEAN,
changedDeleted: BOOLEAN,
changedOwner: OwnerEnum, -- also used for initial owner.
changedSize: AE.PageCount,
changedVersionIncrement
s: INT,
changedByteLength
: AE.ByteCount,
changedCreateTime
: LONG CARDINAL,
changedHighWaterMark: AE.PageCount,
changedModifyAccess
: PackedAccessList,
changedReadAccess: PackedAccessList,
changedStringName: PackedStringName,
nPageRunStates
: NAT,
pageRunStates: ARRAY [0..MaxPageRunStatesPerFile) OF PageRunState];

OwnerEnum
: TYPE = {caller, AlpineTest, unchanged};
RealOwnerEnum
: TYPE = OwnerEnum[caller..AlpineTest];

AllFileProp
: TYPE = {deleted, owner, size, versionIncrements, byteLength, createTime,
highWaterMark, modifyAccess, readAccess, stringName};
VolatileFileProp
: TYPE = AllFileProp[byteLength..stringName];

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

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

PackedAccessList
: TYPE = MACHINE DEPENDENT RECORD[lists: ARRAY AccessListEntries OF
BOOLEAN];
nullPackedAccessList: PackedAccessList = [ALL[FALSE]];
AccessListEntries: TYPE = {world, owner, listIn, listNotIn, other}; -- other is caller if this file belongs to AlpineTest, AlpineTest if this file belongs to caller.

nullFileState
: FileState = [universalFile: AE.nullUniversalFile, unshared: TRUE,
dirtied: FALSE, initialSize: 0, runningHighWaterMark: 0, changed: ALL[FALSE],
changedDeleted: FALSE, changedOwner: unchanged, changedSize: 0,
changedVersionIncrements: 0, changedByteLength: 0, changedCreateTime: 0,
changedHighWaterMark: 0, changedModifyAccess: nullPackedAccessList,
changedReadAccess: nullPackedAccessList, changedStringName: nullPackedStringName,
nPageRunStates: 0, pageRunStates:ALL[[pageRun: [firstPage: 0, count: 0], seed: 0]]];

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

TransRunData
: TYPE = RECORD[historyUniversalFile: AE.UniversalFile,
historyPageNumber: AE.PageNumber, refHistoryDataEntry: REF HistoryDataEntry];



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


DoTest
: PROCEDURE =
BEGIN -- errors:
masterTransHandle: AlpT.Handle;
historyFileHandle: AlpF.Handle;
-- xx
processes: LIST OF PROCESS RETURNS[aborted, serverDown: BOOLEAN];
transSets: LIST OF TransRunData;
nAbortedTrans, nServersDownSeen: NAT;
DO

nAbortedTrans ← nServersDownSeen ← 0;
processes ← NIL;
nSharedFilesThisRun ← 0;
sharedFilesThisRun ← NIL;
testStartTime ← System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]];
[historyUniversalFile, masterTransHandle, historyFileHandle] ← InitHistoryFileDataArea[];
transSets ← GenDataStructures[masterTransHandle];
InitHistoryFileInitialInfoArea[historyFileHandle];
IF
masterTransHandle.Finish[commit] # commit THEN ERROR;
LogStartingRunInTypescript[];
FOR list: LIST OF TransRunData ← transSets, list.rest
UNTIL list = NIL
DO processes ← CONS[FORK DoTrans[list], processes];
ENDLOOP;
FOR list: LIST OF PROCESS RETURNS[aborted, serverDown: BOOLEAN] ← processes, list.rest
UNTIL list = NIL
DO
aborted, serverDown: BOOLEAN;
[aborted, serverDown] ← JOIN list.first;
IF aborted THEN nAbortedTrans ← nAbortedTrans + 1;
IF serverDown THEN nServersDownSeen ← nServersDownSeen + 1;
ENDLOOP;
LogNATAndEventInTypescript[nAbortedTrans, "trans reported being aborted."];
LogNATAndEventInTypescript[nServersDownSeen, "trans reported seeing the server down."];
LogEventInTypescript["Starting consistency checking."];
nextSeed ← CheckConsistencyAndCleanUp[check: TRUE, cleanUp: TRUE,
ignoreDataFileNotFound: FALSE];
IF ((NOT runningRandom) OR forcedStop) THEN EXIT;
[] ← RandomCard.Init[seed: ABS[nextSeed]];
UpdateTestParams[];
ENDLOOP
;
UnregisterRollbackProc[];
LogEndOfTestInTypescript[];
END;


-- do this sequentially so the random values are reproducible.

GenDataStructures
: PROCEDURE [transHandle: AlpT.Handle] RETURNS[transSets: LIST OF
TransRunData] =
BEGIN -- errors:
transSets ← NIL;
THROUGH [0..nTransSets)
DO transSets ← SpinATransSet[transHandle, transSets, GetSharedFilesThisSet];
ENDLOOP;
END;


sharedFilesThisRun
: LIST OF AE.UniversalFile;
nSharedFilesThisRun
: NAT;

GetSharedFilesThisSet
: PROCEDURE [transHandle: AlpT.Handle, nFiles: NAT]
RETURNS
[sharedFiles: LIST OF SharedFile] =
BEGIN -- errors:
IF (nSharedFilesThisRunnSharedFilesThisRun + nFiles) > MaxSharedFilesEachRun
THEN ERROR;
sharedFiles ← NIL;
THROUGH [0..nFiles)
DO
refUniversalFile: REF AE.UniversalFile;
fileHandle: AlpF.Handle;
sharedFiles ← CONS[[universalFile: , size: RandomCard.Choose[min: 0, max: MaxSizeOfFile],
changedOwner: SELECT RandomCard.Choose[min: 0, max: 1] FROM 0 => caller,
ENDCASE => AlpineTest], sharedFiles];
[fileHandle, refUniversalFile] ← AlpF.Create[transHandle, volumeGroupID,
IF sharedFiles.first.changedOwner = caller THEN caller ELSE "AlpineTest",
sharedFiles.first.size, , log, SELECT RandomCard.Choose[min: 0, max: 1] FROM 0 =>
random, ENDCASE => sequential];
sharedFiles.first.universalFile ← refUniversalFile^;
fileHandle.WriteProperties[LIST[[highWaterMark[sharedFiles.first.size]]]];
fileHandle.Close[];
sharedFilesThisRun ← CONS[sharedFiles.first.universalFile, sharedFilesThisRun];
ENDLOOP;
END;


SharedFile
: TYPE = RECORD[universalFile: AE.UniversalFile, size: AE.PageCount,
changedOwner: OwnerEnum];


SpinATransSet
: PROCEDURE [transHandle: AlpT.Handle, transSets: LIST OF TransRunData,
GetSharedFilesThisSet: PROCEDURE [transHandle: AlpT.Handle, nFiles: NAT]
RETURNS
[sharedFiles: LIST OF SharedFile]] RETURNS[newTransSets: LIST OF TransRunData] =
BEGIN -- errors:
nSharedFilesAvailable: NAT ← MaxSharedFilesEachRun - nSharedFilesThisRun;
maxNSharedFilesNeededThisSet, tempNSharedFiles: NAT ← 0;
sharedFiles: LIST OF SharedFile;
newTransSets ← NIL;

-- find the max shared files we need for this set, and mark for each trans which file states are for shared files.
FOR
transIndex: NAT IN [0..IF runningRandom THEN RandomCard.Choose[min: 0, max:
nTransPerSet] ELSE nTransPerSet)
DO
bugCatcher: NAT ← 2000;
transRunningData: TransRunData ← [historyUniversalFile: historyUniversalFile,
historyPageNumber: GetNewHistoryPageNumber[], refHistoryDataEntry:
NEW[HistoryDataEntry ← [transID: AE.nullTransID, timeStamp: 0, nFileStates: 0,
fileStates: ALL[nullFileState], padding: ALL[0]]]];
nUnsharedFiles: NAT;
nSharedFiles, tempNSharedFiles: NAT;
nUnsharedFiles ← IF runningRandom
THEN RandomCard.Choose[min: 0, max: nUnsharedFilesPerTrans]
ELSE nUnsharedFilesPerTrans;
nSharedFiles ← IF runningRandom
THEN RandomCard.Choose[min: 0, max: nSharedFilesPerTrans]
ELSE nSharedFilesPerTrans;
IF nSharedFiles > nSharedFilesAvailable
THEN BEGIN
nSharedFiles ← nSharedFilesAvailable;
LogEventInTypescript["Ran out of shared files."];
END;
tempNSharedFiles ← nSharedFiles;
transRunningData.refHistoryDataEntry.nFileStates ← nSharedFiles + nUnsharedFiles;
WHILE tempNSharedFiles > 0
DO
fileIndex: NAT ← RandomCard.Choose[min: 0, max:
transRunningData.refHistoryDataEntry.nFileStates - 1];
IF
transRunningData.refHistoryDataEntry.fileStates[fileIndex].unshared
THEN BEGIN

transRunningData.refHistoryDataEntry.fileStates[fileIndex].unshared ← FALSE;
tempNSharedFiles ← tempNSharedFiles - 1;
END;
IF
(bugCatcherbugCatcher -1) = 0 THEN ERROR; -- just in case.
ENDLOOP;
maxNSharedFilesNeededThisSet ← MAX[maxNSharedFilesNeededThisSet, nSharedFiles];
newTransSets ← CONS[transRunningData, newTransSets];
IF
transIndex = 0 THEN newTransSets.rest ← transSets;
ENDLOOP;

sharedFiles ← GetSharedFilesThisSet[transHandle, maxNSharedFilesNeededThisSet];

FOR
list: LIST OF TransRunData ← newTransSets, list.rest
UNTIL list = NIL
DO
filePlace: LIST OF SharedFile ← sharedFiles;
FOR fileIndex: NAT IN [0..list.first.refHistoryDataEntry.nFileStates)
DO
IF NOT list.first.refHistoryDataEntry.fileStates[fileIndex].unshared
THEN BEGIN list.first.refHistoryDataEntry.fileStates[fileIndex].universalFile ←
filePlace.first.universalFile;
list.first.refHistoryDataEntry.fileStates[fileIndex].initialSize ← filePlace.first.size;
list.first.refHistoryDataEntry.fileStates[fileIndex].runningHighWaterMark ←
filePlace.first.size;
list.first.refHistoryDataEntry.fileStates[fileIndex].changedOwner ←
filePlace.first.changedOwner;
filePlace ← filePlace.rest;
list.first.refHistoryDataEntry.fileStates[fileIndex].nPageRunStates ← IF
runningRandom
THEN RandomCard.Choose[min: 0, max: nPageRunsPerSharedFile]
ELSE nPageRunsPerSharedFile;
END

ELSE BEGIN list.first.refHistoryDataEntry.fileStates[fileIndex].initialSize ←
RandomCard.Choose[min: 0, max: MaxSizeOfFile];
list.first.refHistoryDataEntry.fileStates[fileIndex].changedOwner ← SELECT
RandomCard.Choose[min: 0, max: 1] FROM 0 => caller, ENDCASE => AlpineTest;
list.first.refHistoryDataEntry.fileStates[fileIndex].nPageRunStates ← IF
runningRandom
THEN RandomCard.Choose[min: 0, max: nPageRunsPerUnsharedFile]
ELSE nPageRunsPerUnsharedFile;
END;
IF list.first.refHistoryDataEntry.fileStates[fileIndex].initialSize = 0
THEN
list.first.refHistoryDataEntry.fileStates[fileIndex].nPageRunStates ← 0;
ENDLOOP;
GenPageRunStates[list];
ENDLOOP;

IF newTransSets # NIL
THEN transSetsnewTransSets;
END;

RandomNextNonNegativeInteger:
PROCEDURE RETURNS[integer: INTEGER] =
BEGIN
DO

cardSeed: CARDINAL;
LastInteger: CARDINAL = LAST[INTEGER];
IF
(cardSeed ← RandomCard.Next[]) <= LastInteger THEN RETURN[cardSeed];
ENDLOOP;
END;


-- for all files for a transaction.

WhatToDo:
TYPE = {overlapBefore, overlapAfter, subset, contiguousBefore, contiguousAfter, separateBefore, separateAfter, same, random, superset, completeOverlapBefore, completeOverlapAfter};

GenPageRunStates
: PROCEDURE[trans: LIST OF TransRunData] =
BEGIN
FOR
fileIndex: INT IN [0..trans.first.refHistoryDataEntry.nFileStates)
DO
lastPageOfFile: AE.PageNumber;
firstPage, lastPage, oldFirstPage, oldLastPage: AE.PageNumber;
pageBeforeFirstPage, pageContigBeforeFirstPage, pageContigAfterLastPage,
pageAfterLastPage: AE.PageNumber;
IF trans.first.refHistoryDataEntry.fileStates[fileIndex].nPageRunStates = 0 THEN LOOP;
lastPageOfFile ← trans.first.refHistoryDataEntry.fileStates[fileIndex].initialSize - 1;
DO

oldFirstPage ← RandomCard.Choose[0, lastPageOfFile];
oldLastPage ← RandomCard.Choose[oldFirstPage, lastPageOfFile];
IF oldLastPage - oldFirstPage < MaxPagesPerPageRun THEN EXIT;
ENDLOOP;

trans.first.refHistoryDataEntry.fileStates[fileIndex].pageRunStates[0] ←
[[oldFirstPage, oldLastPage - oldFirstPage + 1], RandomNextNonNegativeInteger[]];
FOR
pageRunIndex: INT IN [0..trans.first.refHistoryDataEntry.fileStates[fileIndex].nPageRunStates)
DO
whatToDo: WhatToDo;
DO
pageContigBeforeFirstPage ← IF oldFirstPage = 0 THEN 0 ELSE oldFirstPage - 1;
pageContigAfterLastPage ← IF oldLastPage = lastPageOfFile THEN lastPageOfFile ELSE
oldLastPage + 1;
pageBeforeFirstPage ← IF pageContigBeforeFirstPage = 0 THEN 0 ELSE
pageContigBeforeFirstPage - 1;
pageAfterLastPage ← IF pageContigAfterLastPage = lastPageOfFile THEN
lastPageOfFile ELSE pageContigAfterLastPage + 1;
SELECT
RandomCard.Choose[0, whatToDoCount] FROM
0 => --overlapBefore--
BEGIN firstPage ← RandomCard.Choose[0, pageContigBeforeFirstPage];
lastPage ← RandomCard.Choose[oldFirstPage, oldLastPage];
whatToDo ← overlapBefore;
END;
1 => --overlapAfter--
BEGIN firstPage ← RandomCard.Choose[oldFirstPage, oldLastPage];
lastPage ← RandomCard.Choose[pageContigAfterLastPage, lastPageOfFile];
whatToDo ← overlapAfter;
END;
2 => --subset--
BEGIN firstPage ← RandomCard.Choose[oldFirstPage, oldLastPage];
lastPage ← RandomCard.Choose[firstPage, oldLastPage];
whatToDo ← subset;
END
;
3 => --contiguousBefore--
BEGIN firstPage ← RandomCard.Choose[0, pageContigBeforeFirstPage];
lastPage ← pageContigBeforeFirstPage;
whatToDo ← contiguousBefore;
END
;
4 => --contiguousAfter--
BEGIN firstPage ← pageContigAfterLastPage;
lastPage ← RandomCard.Choose[pageContigAfterLastPage, lastPageOfFile];
whatToDo ← contiguousAfter;
END;
5 => --separateBefore--
BEGIN firstPage ← RandomCard.Choose[0, pageBeforeFirstPage];
lastPage ← RandomCard.Choose[firstPage, pageBeforeFirstPage];
whatToDo ← separateBefore;
END;
6 => --separateAfter--
BEGIN firstPage ← RandomCard.Choose[pageAfterLastPage, lastPageOfFile];
lastPage ← RandomCard.Choose[firstPage, lastPageOfFile];
whatToDo ← separateAfter;
END;
7 => --same--
BEGIN firstPage ← oldFirstPage;
lastPage ← oldLastPage;
whatToDo ← same;
END
;
8 => --random--
BEGIN firstPage ← RandomCard.Choose[0, lastPageOfFile];
lastPage ← RandomCard.Choose[firstPage, lastPageOfFile];
whatToDo ← random;
END
;
9 => --superset--
BEGIN firstPage ← RandomCard.Choose[0, pageContigBeforeFirstPage];
lastPage ← RandomCard.Choose[pageContigAfterLastPage, lastPageOfFile];
whatToDo ← superset;
END;
10 => --completeOverlapBefore--
BEGIN firstPage ← RandomCard.Choose[0, pageContigBeforeFirstPage];
lastPage ← oldLastPage;
whatToDo ← completeOverlapBefore;
END;
11 => --completeOverlapAfter--
BEGIN firstPage ← oldFirstPage;
lastPage ← RandomCard.Choose[pageContigAfterLastPage, lastPageOfFile];
whatToDo ← completeOverlapAfter;
END;
ENDCASE => ERROR;
IF lastPage - firstPage < MaxPagesPerPageRun THEN EXIT;
ENDLOOP
;
trans.first.refHistoryDataEntry.fileStates[fileIndex].pageRunStates[
pageRunIndex] ← [[firstPage, lastPage - firstPage + 1],
RandomNextNonNegativeInteger[]];
oldFirstPage ← firstPage;
oldLastPage ← lastPage;
ENDLOOP;
ENDLOOP; -- end of for each file.
END
;


--xx needs changed stuff.

DoTrans
: PROCEDURE[trans: LIST OF TransRunData] RETURNS[aborted, serverDown: BOOLEAN] =
BEGIN
ENABLE

BEGIN
AlpT.Unknown => IF (what = openFileID) OR (what = transID)
THEN BEGIN aborted ← TRUE; CONTINUE; END;
RPC.CallFailed => IF ((NOT local) AND (why = unbound))
THEN BEGIN serverDown ← TRUE; CONTINUE; END; -- local test is a bug catcher.
AlpI.Failed => IF ((NOT local) AND (why = unbound))
THEN BEGIN serverDown ← TRUE; CONTINUE; END;
END;
bufferPageRun: REF BufferPageRun ← NEW[BufferPageRun];
instHandle: AlpI.Handle;
transHandle: AlpT.Handle;
aborted ← serverDown ← FALSE;
instHandle ← AlpI.Create[fileStore, caller, key];
transHandle ← AlpT.Create[instHandle, TRUE];
trans.first.refHistoryDataEntry.transID ← transHandle.transID;
FOR fileIndex: INT IN [0..trans.first.refHistoryDataEntry.nFileStates)
DO -- for each file.
fileHandle: AlpF.Handle;
refUniversalFile: REF AE.UniversalFile;
IF
trans.first.refHistoryDataEntry.fileStates[fileIndex].unshared
THEN BEGIN
[fileHandle, refUniversalFile] ← AlpF.Create[transHandle, volumeGroupID,
(IF trans.first.refHistoryDataEntry.fileStates[fileIndex].changedOwner = caller
THEN caller ELSE "AlpineTest"),
trans.first.refHistoryDataEntry.fileStates[fileIndex].initialSize, , log, (SELECT
RandomCard.Choose[min: 0, max: 1] FROM 0 => random, ENDCASE => sequential)];
trans.first.refHistoryDataEntry.fileStates[fileIndex].universalFilerefUniversalFile^;
END
ELSE
fileHandle ←
AlpF.Open[transHandle,
trans.first.refHistoryDataEntry.fileStates[fileIndex].universalFile,
readWrite, [intendWrite, wait], log, (SELECT RandomCard.Choose[min: 0, max: 1]
FROM 0 => random, ENDCASE => sequential)].handle;
FOR
pageRunIndex: INT IN
[0..trans.first.refHistoryDataEntry.fileStates[fileIndex].nPageRunStates)
DO
pageRun: AE.PageRun;
seed: INTEGER;
[pageRun, seed] ← trans.first.refHistoryDataEntry.fileStates[
fileIndex].pageRunStates[pageRunIndex];
FOR offset: CARDINAL IN [0..pageRun.count)
DO bufferPageRun[offset][0] ← seed;
bufferPageRun[offset][1] ← INTEGER[seed - offset]; -- seed is always >= 0.
ENDLOOP;
trans.first.refHistoryDataEntry.fileStates[fileIndex].dirtied ← TRUE;
trans.first.refHistoryDataEntry.fileStates[fileIndex].runningHighWaterMark ←
MAX[pageRun.firstPage + pageRun.count,
trans.first.refHistoryDataEntry.fileStates[fileIndex].runningHighWaterMark];
fileHandle.WritePages[pageRun, DESCRIPTOR[bufferPageRun,
pageRun.count*AE.wordsPerPage], [write, wait]];
ENDLOOP;
ENDLOOP;
UpdateHistoryLogPage[trans, transHandle];

aborted ← (transHandle.Finish[commit] = abort);
END;


UpdateHistoryLogPage
: PROCEDURE[trans: LIST OF TransRunData,
transHandle: AlpT.Handle] =
BEGIN
historyFileHandle: AlpF.Handle ← AlpF.Open[transHandle, trans.first.historyUniversalFile,
readWrite, [intendWrite, wait], log, random].handle;
trans.first.refHistoryDataEntry.timeStamp ←
System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]];
historyFileHandle.WritePages[[trans.first.historyPageNumber, PagesPerHistoryDataEntry],
DESCRIPTOR[@trans.first.refHistoryDataEntry^, WordsPerHistoryDataEntry], [write, wait]];
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:


--
      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


-- (type is always AlpFile.standardFile).


-- 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. 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  for caller: Alpine default (owner), for AlpineTest: owner + caller.
--  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,
dirtyCount: NAT, -- for checking
versionIncrements: NAT, -- version.
initialSize: AE.PageCount,
runningHighWaterMark: AE.PageCount, -- both writes and explicit sets of HWM and size.
oldestTransCommitTime: LONG CARDINAL,
realByteLength: AE.ByteCount, -- real values, saved here for convenience.
realCreateTime: LONG CARDINAL,
realHighWaterMark: AE.PageCount,
realModifyAccess: PackedAccessList,
realOwner: RealOwnerEnum,
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 version, which needs special handling).
pages: LIST OF RegPages];

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..MaxPagesPerPageRun) OF BufferPage;
BufferPage: TYPE = ARRAY [0..AE.wordsPerPage) OF UNSPECIFIED;

CheckConsistencyAndCleanUp: PROCEDURE [check, cleanUp, ignoreDataFileNotFound:
BOOLEAN] RETURNS[nextSeed: INTEGER] =
BEGIN -- errors:
ENABLE BadState => LogEventInTypescript["BAD STATE SEEN."]; -- xx does typescript always exist here??
transHandle: AlpT.Handle;
historyInitialInfo: HistoryInitialInfo;
historyFileHandle: AlpF.Handle;
nCommittedTrans: NAT;
nAbortedTrans: NAT;
ReportServerCrash: PROCEDURE =
BEGIN
LogEventInTypescript["Server down or trans aborted during checking and building data structure phase of consistency checking."]; UserTerminal.BlinkDisplay[]; END;
BEGIN
-- checking and building data structure phase.
ENABLE
BEGIN
AlpI.Failed => IF ((NOT local) AND (why = unbound))
THEN BEGIN
ReportServerCrash[]; RETRY; END;
AlpT.Unknown => IF (what = openFileID) OR (what = transID)
THEN BEGIN
ReportServerCrash[]; RETRY; END;
RPC.CallFailed => IF ((NOT local) AND (why = unbound))
THEN BEGIN
ReportServerCrash[]; RETRY; END;
AbortDetected => BEGIN ReportServerCrash[]; RETRY; END;
END
;
historyUniversalFile: AE.UniversalFile;
bufferPage: BufferPage;
historyDataEntry: HistoryDataEntry;
nCommittedTrans ← 0;
nAbortedTrans ← 0;
transHandle ← AlpT.Create[AlpI.Create[fileStore, caller, key], TRUE];
historyUniversalFile ← AlpineInterimDirectory.OpenUnderTrans[transHandle,
historyFileName, oldOnly, 0].universalFile;
historyFileHandle ← AlpF.Open[transHandle, historyUniversalFile, readWrite,
[write, wait], log, random].handle;
historyFileHandle.ReadPages[[0, PagesPerHistoryInitialInfo],
DESCRIPTOR[@historyInitialInfo, WordsPerHistoryInitialInfo], [write, wait]];
IF (historyMaxPageNumber ← historyInitialInfo.historyMaxPageNumber) = 0
THEN ERROR BadState;
[] ← RandomCard.Init[ABS[historyInitialInfo.seed]];
nextSeed ← RandomNextNonNegativeInteger[];
testStartTime ← historyInitialInfo.testStartTime;
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
nAbortedTrans ← nAbortedTrans + 1; LOOP; END
ELSE
nCommittedTransnCommittedTrans + 1;
IF historyDataEntry.timeStamp = 0 THEN ERROR BadState;
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 check)
THEN BEGIN

IF (cleanUp AND fileFound)
THEN [] ← RegisterFileExistence[historyDataEntry.fileStates[fileIndex]
! BadState => CONTINUE;];
LOOP;
END;
IF
(NOT fileFound)
THEN BEGIN
IF NOT (historyDataEntry.fileStates[fileIndex].unshared AND
historyDataEntry.fileStates[fileIndex].changed[deleted] AND
historyDataEntry.fileStates[fileIndex].changedDeleted)
THEN ERROR BadState;
LOOP;
END;
[listRegFile, newReg] ←
RegisterFileExistence[historyDataEntry.fileStates[fileIndex]];
-- type: demanded to match AlpFile.standardFile when read, then discarded.
IF
newReg THEN ReadAndRegRealProps[fileHandle, listRegFile];

IF
historyDataEntry.timeStamp < listRegFile.first.oldestTransCommitTime
THEN
listRegFile.first.oldestTransCommitTimehistoryDataEntry.timeStamp;

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

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

-- 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;
IF historyDataEntry.fileStates[fileIndex].changed[versionIncrements]
THEN listRegFile.first.versionIncrementslistRegFile.first.versionIncrements
+ historyDataEntry.fileStates[fileIndex].changedVersionIncrements;

FOR
pageRunIndex: INT DECREASING IN
[0..historyDataEntry.fileStates[fileIndex].nPageRunStates)
DO
pageRun: AE.PageRun ←
historyDataEntry.fileStates[fileIndex].pageRunStates[pageRunIndex].pageRun;
seed: INTEGER
historyDataEntry.fileStates[fileIndex].pageRunStates[pageRunIndex].seed;
count: CARDINAL ← 0;
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] = INTEGER[seed - count]))];
count ← count + 1;
ENDLOOP;
ENDLOOP;
fileHandle.Close[];

ENDLOOP
;
ENDLOOP
;
IF transHandle.Finish[commit, cleanUp] # commit THEN ERROR AbortDetected;
END;


LogNATAndEventInTypescript[nAbortedTrans, "aborted trans seen."];
LogNATAndEventInTypescript[nCommittedTrans, "committed trans seen."];

IF
check THEN FindBadInRegistry[];

IF
cleanUp THEN
BEGIN
-- deleteFiles.
ENABLE
BEGIN
AlpI.Failed => IF ((NOT local) AND (why = unbound))
THEN BEGIN
ReportServerCrash[]; RETRY; END;
AlpT.Unknown => IF (what = openFileID) OR (what = transID)
THEN BEGIN
ReportServerCrash[]; RETRY; END;
RPC.CallFailed => IF ((NOT local) AND (why = unbound))
THEN BEGIN
ReportServerCrash[]; RETRY; END;
AbortDetected => BEGIN ReportServerCrash[]; RETRY; END;
END
;
ReportServerCrash: PROCEDURE =
BEGIN
LogEventInTypescript["Server down or trans aborted during deletion phase of consistency checking."]; UserTerminal.BlinkDisplay[]; transHandle ← NIL; END;
IF
transHandle = NIL
THEN
transHandle ← AlpT.Create[AlpI.Create[fileStore, caller, key], TRUE];
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
! AlpF.Unknown => IF (ignoreDataFileNotFound) AND
(what = fileID)
THEN LOOP].handle;
fileHandle.Delete;
ENDLOOP;
FOR fileIndex: NAT IN [0..historyInitialInfo.nSharedFilesCreated)
DO
fileHandle: AlpF.Handle ← AlpF.Open[transHandle: transHandle, universalFile:
historyInitialInfo.sharedFiles[fileIndex], access: readWrite, lock: [write, wait],
recoveryOption: log, referencePattern: random
! AlpF.Unknown => IF what = fileID THEN LOOP].handle; -- normally will be not found; might exist if all trans touching it were aborted.
fileHandle.Delete;
ENDLOOP;
historyFileHandle.Delete[];
IF transHandle.Finish[commit] # commit THEN ERROR AbortDetected;
END
;

END;

AbortDetected
: ERROR = CODE;
BadState
: ERROR = CODE; -- so can ignore if only cleaning up.

RegisterFileExistence
: PROCEDURE[fileState: FileState] RETURNS[listRegFile: LIST OF RegFile,
newReg: BOOLEAN]=
BEGIN
FOR
listRegFile ← regFiles, listRegFile.rest
UNTIL
listRegFile = NIL
DO
IF fileState.universalFile = listRegFile.first.universalFile THEN GOTO found;
REPEAT
found => BEGIN
IF
(fileState.unshared) OR (listRegFile.first.unshared) OR
(fileState.initialSize # listRegFile.first.initialSize) THEN ERROR BadState;
newReg ← FALSE;
END
;
FINISHED => BEGIN
newReg ← TRUE;
listRegFile ← regFiles ← CONS[[universalFile: fileState.universalFile,
unshared: fileState.unshared,
dirtyCount: 0,
versionIncrements: 0,
initialSize: fileState.initialSize,
runningHighWaterMark: 0,
oldestTransCommitTime: LAST[LONG CARDINAL],
realByteLength: 0,
realCreateTime: 0,
realHighWaterMark: 0,
realModifyAccess: nullPackedAccessList,
realOwner: caller,
realReadAccess: nullPackedAccessList,
realSize: 0,
realStringName: nullPackedStringName,
realVersion: 0,
state: ALL[[state: unknown, timeStamp: 0]],
pages: NIL], regFiles];
END;
ENDLOOP
;
END;


ReadAndRegRealProps
: PROCEDURE[fileHandle: AlpF.Handle, listRegFile: LIST OF RegFile] =
BEGIN
realType: File.TypeFileTypes.tUnassigned;
modifyAccessList: AE.AccessList;
readAccessList: AE.AccessList;
owner: AE.OwnerName;
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 BadState;
SELECT
list.first.property FROM
byteLength => listRegFile.first.realByteLength NARROW[list.first,
AE.PropertyValuePair.byteLength].byteLength;
createTime => listRegFile.first.realCreateTime
System.SecondsSinceEpoch[NARROW[list.first,
AE.PropertyValuePair.createTime].createTime];
highWaterMark => listRegFile.first.realHighWaterMark ← NARROW[list.first,
AE.PropertyValuePair.highWaterMark].highWaterMark;
modifyAccess => modifyAccessList ← NARROW[list.first,
AE.PropertyValuePair.modifyAccess].modifyAccess;
owner => BEGIN owner ← NARROW[list.first, AE.PropertyValuePair.owner].owner;
listRegFile.first.realOwner ← SELECT TRUE FROM
Rope.Equal[owner, caller, FALSE] => caller,
Rope.Equal[owner, "AlpineTest", FALSE] => AlpineTest,
ENDCASE => ERROR BadState;
END;

readAccess => readAccessList ← 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 # AlpF.standardFile THEN ERROR BadState;
END;
version => listRegFile.first.realVersion ← NARROW[list.first,
AE.PropertyValuePair.version].version;
ENDCASE => ERROR BadState;
bugCheck[list.first.property] ← TRUE;
ENDLOOP;

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


RegisterStateProps
: PROCEDURE[listRegFile: LIST OF RegFile, fileState: FileState, timeStamp:
LONG CARDINAL] =
BEGIN
FOR volatileFileProp: VolatileFileProp IN VolatileFileProp
DO
okay: BOOLEAN;
IF
listRegFile.first.state[volatileFileProp].timeStamp >= timeStamp THEN LOOP;
IF
((volatileFileProp # highWaterMark) AND (NOT fileState.changed[volatileFileProp]))
THEN LOOP;

okay ← SELECT volatileFileProp FROM
byteLength => fileState.changedByteLength = listRegFile.first.realByteLength,
createTime => fileState.changedCreateTime = listRegFile.first.realCreateTime,
highWaterMark => fileState.runningHighWaterMark =
listRegFile.first.realHighWaterMark,
modifyAccess => fileState.changedModifyAccess =
listRegFile.first.realModifyAccess,
readAccess => fileState.changedReadAccess = listRegFile.first.realReadAccess,
stringName => fileState.changedStringName = listRegFile.first.realStringName,
ENDCASE => ERROR BadState;
listRegFile.first.state[volatileFileProp] ← [state: (IF okay THEN good ELSE bad), timeStamp:
timeStamp];
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
SELECT
listRegFile.first.state[volatileFileProp].state FROM
bad => ERROR BadState;
unknown => IF NOT MatchesDefault[volatileFileProp, listRegFile]
THEN ERROR
BadState;
ENDCASE;
ENDLOOP;

IF listRegFile.first.realVersion #
((IF listRegFile.first.unshared THEN 0 ELSE 1) + listRegFile.first.dirtyCount +
listRegFile.first.versionIncrements) THEN ERROR BadState;

-- 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 BadState;
ENDLOOP;
ENDLOOP;

END;


-- Checks what we expect for the Alpine defaults:


MatchesDefault
: PROCEDURE[volatileFileProp: VolatileFileProp, listRegFile: LIST OF RegFile]
RETURNS[matches: BOOLEAN]=
BEGIN -- errors:
RETURN[SELECT volatileFileProp FROM
byteLength => listRegFile.first.realByteLength = 0,
createTime => ((listRegFile.first.realCreateTime >= testStartTime) AND
(listRegFile.first.realCreateTime <= listRegFile.first.oldestTransCommitTime)),
highWaterMark => ERROR,
modifyAccess => listRegFile.first.realModifyAccess = (IF listRegFile.first.realOwner =
caller THEN DefaultModifyAccessCaller ELSE DefaultModifyAccessAlpineTest),
readAccess => listRegFile.first.realReadAccess = DefaultReadAccess,
stringName => listRegFile.first.realStringName = DefaultStringName,
ENDCASE => ERROR];
END;


DefaultModifyAccessCaller
: PackedAccessList = [lists: [world: FALSE, owner: TRUE,
listIn: FALSE, listNotIn: FALSE, other: FALSE]];
DefaultModifyAccessAlpineTest: PackedAccessList = [lists: [world: FALSE, owner:
TRUE, listIn: FALSE, listNotIn: FALSE, other: TRUE]];
DefaultReadAccess: PackedAccessList = [lists: [world: TRUE, owner: FALSE, listIn: FALSE,
listNotIn: FALSE, other: FALSE]];
DefaultStringName: PackedStringName = nullPackedStringName;




CleanUp
: PROCEDURE[ignoreDataFileNotFound: BOOLEAN] =
BEGIN -- errors:
[] ← CheckConsistencyAndCleanUp[check: FALSE, cleanUp: TRUE, ignoreDataFileNotFound:
ignoreDataFileNotFound];
--xx what to do if test running?
UnregisterRollbackProc[];
END;



StartTest
: PROCEDURE[server: AE.FileStore, localServer, crashServer, random: BOOLEAN,
nTransactionSets, nTransactionsPerSet: NAT,
nSharedFilesPerTransaction, nUnsharedFilesPerTransaction: [0..MaxFilesPerTrans],
nPageRunStatesPerSharedFile, nPageRunStatesPerUnsharedFile: [0..MaxPageRunStatesPerFile],
nErrorTransactions: NAT, seed: INTEGER] =
BEGIN
-- errors: all errors uncaught here are fatal.
instHandle: AlpI.Handle;
transHandle: AlpT.Handle;
psw: Rope.ROPE;
IF nSharedFilesPerTransaction + nUnsharedFilesPerTransaction > MaxFilesPerTrans
THEN ERROR;
localVolumeName ← server;
fileStore ← IF (local ← localServer) THEN "local.alpine" ELSE server;
crashes ← crashServer;
runningRandom ← random;
nextSeed ← ABS[seed];
maxNTransSets ← nTransSets ← nTransactionSets;
maxNTransPerSet ← nTransPerSet ← nTransactionsPerSet;
maxNSharedFilesPerTrans ← nSharedFilesPerTrans ← nSharedFilesPerTransaction;
maxNUnsharedFilesPerTrans ← nUnsharedFilesPerTrans ← nUnsharedFilesPerTransaction;
maxNPageRunsPerSharedFile ← nPageRunsPerSharedFile ← nPageRunStatesPerSharedFile;
maxNPageRunsPerUnsharedFile ← nPageRunsPerUnsharedFile ←
nPageRunStatesPerUnsharedFile;
maxNErrorTrans ← nErrorTrans ← nErrorTransactions;
[caller, psw] ← UserCredentials.GetUserCredentials[];
key ← RPC.MakeKey[psw];
historyFileName ← Rope.Cat["[", fileStore, "]<AlpineTest>AlpineTest.HistoryFile"];
IF local
THEN BEGIN
LocalWorkstationServer.Initialize[fileStore: localVolumeName, 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;];
[] ← CheckConsistencyAndCleanUp[check: FALSE, cleanUp: TRUE,
ignoreDataFileNotFound: TRUE];
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:
"AlpineTest", properties: LIST[[quota[4000]], [createAccessList[LIST[caller]]]],
enforceTotalQuota: TRUE
! AlpI.OperationFailed => IF why = duplicateOwner THEN CONTINUE];
-- set up history file.
IF transHandle.Finish[requestedOutcome: commit] # commit THEN ERROR;
LogStartOfTestInTypescript[];
[] ← RandomCard.Init[seed: ABS[nextSeed]];
IF (local AND crashes)
THEN CedarSnapshot.Register[r: RollbackTest]
ELSE DoTest[];
END;


RollbackTest
: PROCEDURE[after: CedarSnapshot.After] =
BEGIN
-- errors:
IF NOT local THEN ERROR;
IF
after = checkpoint -- weird form to keep compiler happy.
THEN BEGIN LogEventInTypescript["Checkpoint."]; Process.Detach[FORK DoTest[]]; END
ELSE BEGIN LogEventInTypescript["Rollback."]; Process.Detach[FORK RollbackDoTest[]]; END;
END;


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


UnregisterRollbackProc
: PROCEDURE =
BEGIN
-- errors:
IF (local AND crashes)
THEN CedarSnapshot.Deregister[r: RollbackTest];
END;



LogStartOfTestInTypescript
: PROCEDURE =
BEGIN
[, typescriptStream] ← ViewerIO.CreateViewerStreams[name: "AlpineTest.typescript",
viewer: NIL, backingFile: "AlpineTest.typescript", editedStream: FALSE];
typescriptStream.PutRope["

Starting test with parameters:

"];
IF local
THEN typescriptStream.PutF["local server: %g
", IO.rope[localVolumeName]]
ELSE typescriptStream.PutF["remote server: %g
", IO.rope[fileStore]];
typescriptStream.PutF["crashServer: %g random: %g seed: %g
", IO.bool[crashes], IO.bool[runningRandom], IO.int[nextSeed]];
typescriptStream.PutF["maxNTransSets: %g maxNTransPerSet: %g
", IO.int[maxNTransSets], IO.int[maxNTransPerSet]];
typescriptStream.PutF["maxNSharedFilesPerTrans: %g maxNPageRunsPerSharedFile: %g
", IO.int[maxNSharedFilesPerTrans], IO.int[maxNPageRunsPerSharedFile]];
typescriptStream.PutF["maxNUnsharedFilesPerTrans: %g maxNPageRunsPerUnsharedFile: %g
", IO.int[maxNUnsharedFilesPerTrans], IO.int[maxNPageRunsPerUnsharedFile]];
typescriptStream.PutF["maxNErrorTrans: %g
", IO.int[maxNErrorTrans]];
typescriptStream.Flush[];
END;


LogStartingRunInTypescript
: PROCEDURE =
BEGIN
typescriptStream.PutRope["

Run of test with parameters:

"];
typescriptStream.PutF["nextSeed: %g
", IO.int[nextSeed]];
typescriptStream.PutF["nTransSets: %g nTransPerSet: %g
", IO.int[nTransSets], IO.int[nTransPerSet]];
typescriptStream.PutF["nSharedFilesPerTrans: %g nPageRunsPerSharedFile: %g
", IO.int[nSharedFilesPerTrans], IO.int[nPageRunsPerSharedFile]];
typescriptStream.PutF["nUnsharedFilesPerTrans: %g nPageRunsPerUnsharedFile: %g
", IO.int[nUnsharedFilesPerTrans], IO.int[nPageRunsPerUnsharedFile]];
typescriptStream.PutF["nErrorTrans: %g

", IO.int[nErrorTrans]];
typescriptStream.Flush[];
END;


LogEventInTypescript
: PROCEDURE[event: Rope.ROPE] =
BEGIN
typescriptStream.PutF["
%g", IO.rope[event]];
typescriptStream.Flush[];
END;


LogNATAndEventInTypescript
: PROCEDURE[nat: NAT, event: Rope.ROPE] =
BEGIN
typescriptStream.PutF["
%g %g", IO.int[nat], IO.rope[event]];
typescriptStream.Flush[];
END;


LogEndOfTestInTypescript
: PROCEDURE =
BEGIN
typescriptStream.PutRope["
End of test.
"];
typescriptStream.Close[];
END;


PackAccessList
: PROCEDURE[accessList: AE.AccessList, owner: AE.OwnerName] RETURNS[packedAccessList:
PackedAccessList] =
BEGIN
ownerIsCaller: BOOLEANSELECT TRUE FROM
Rope.Equal[owner, caller, FALSE] => TRUE,
Rope.Equal[owner, "AlpineTest", FALSE] => FALSE,
ENDCASE => ERROR;
packedAccessListnullPackedAccessList;
FOR
list: AE.AccessListaccessList, list.rest
UNTIL list = NIL
DO
type: AccessListEntries ← (SELECT TRUE FROM
Rope.Equal[list.first, "world", FALSE], Rope.Equal[list.first, "*", FALSE] => world,
Rope.Equal[list.first, "owner", FALSE] => owner,
Rope.Equal[list.first, caller, FALSE] => IF ownerIsCaller THEN owner ELSE other,
Rope.Equal[list.first, "AlpineTest", FALSE] => IF NOT ownerIsCaller THEN owner
ELSE other,
Rope.Equal[list.first, ListCallerIsIn, TRUE] => listIn,
Rope.Equal[list.first, ListCallerIsNotIn, TRUE] => listNotIn,
ENDCASE
=> ERROR --xx--);
IF
packedAccessList.lists[type] THEN ERROR --xx--;
packedAccessList.lists[type] ← TRUE;
ENDLOOP;
END
;



PackStringName
: PROCEDURE[rope: Rope.ROPE] RETURNS[packedStringName:
PackedStringName] =
BEGIN
packedStringNamenullPackedStringName;
IF
(packedStringName.countrope.Length[]) > PackedStringNameLength THEN ERROR --xx--;
FOR
count: NAT IN [0..packedStringName.count)
DO
packedStringName.chars[count] ← rope.Fetch[count];
ENDLOOP;
END
;


InitHistoryFileDataArea
: PROCEDURE RETURNS[historyUniversalFile:
AE.UniversalFile, transHandle: AlpT.Handle, historyFileHandle: AlpF.Handle] =
BEGIN
historyDataEntry: HistoryDataEntry ←
[transID: AE.nullTransID,
timeStamp: 0,
nFileStates: 0,
fileStates: ALL[nullFileState],
padding: ALL[0]
];
instHandle: AlpI.Handle ← AlpI.Create[fileStore, caller, key];
transHandle ← AlpT.Create[instHandle, TRUE];
historyNextPageNumber ← PagesPerHistoryInitialInfo;
historyMaxPageNumber ← PagesPerHistoryInitialInfo
+ PagesPerHistoryDataEntry*(nErrorTrans + nTransSets*nTransPerSet);
historyUniversalFile ← AlpineInterimDirectory.OpenUnderTrans[transHandle,
historyFileName, newOnly, (historyMaxPageNumber*AE.bytesPerPage)].universalFile;
historyFileHandle ← AlpF.Open[transHandle, historyUniversalFile, readWrite,
[write, wait], log, random].handle;
FOR
pageNumber: AE.PageNumber ← PagesPerHistoryInitialInfo, pageNumber +
PagesPerHistoryDataEntry
UNTIL pageNumber >= historyMaxPageNumber
DO
historyFileHandle.WritePages[pageRun: [firstPage: pageNumber, count:
PagesPerHistoryDataEntry], pageBuffer: DESCRIPTOR[@historyDataEntry,
WordsPerHistoryDataEntry], lock: [write, wait]];
ENDLOOP;
END
;

InitHistoryFileInitialInfoArea
: PROCEDURE [historyFileHandle: AlpF.Handle] =
BEGIN
historyInitialInfo: HistoryInitialInfo ←
[seed: nextSeed,
historyMaxPageNumber: historyMaxPageNumber,
testStartTime: testStartTime,
nTransSets: nTransSets,
nTransPerSet: nTransPerSet,
nSharedFilesPerTrans: nSharedFilesPerTrans,
nUnsharedFilesPerTrans: nUnsharedFilesPerTrans,
nPageRunsPerSharedFile: nPageRunsPerSharedFile,
nPageRunsPerUnsharedFile: nPageRunsPerUnsharedFile,
gap: 0,
nErrorTrans: nErrorTrans,
nSharedFilesCreated: 0,
gap1: 0,
sharedFiles: ALL[AE.nullUniversalFile],
padding: ALL[0]
];
nSharedFilesCreated: NAT ← 0;
FOR
list: LIST OF AE.UniversalFile ← sharedFilesThisRun, list.rest
UNTIL list = NIL
DO
historyInitialInfo.sharedFiles[nSharedFilesCreated] ← list.first;
nSharedFilesCreated ← nSharedFilesCreated + 1;
ENDLOOP;
historyInitialInfo.nSharedFilesCreated ← nSharedFilesCreated;
historyFileHandle.WritePages[pageRun: [firstPage: 0, count: PagesPerHistoryInitialInfo],
pageBuffer: DESCRIPTOR[@historyInitialInfo, WordsPerHistoryInitialInfo],
lock: [write, wait]];
END
;





GetNewHistoryPageNumber
: ENTRY PROCEDURE RETURNS[historyPageNumber:
AE.PageNumber] =
BEGIN
historyPageNumber ← historyNextPageNumber;
IF (historyNextPageNumber ← historyNextPageNumber + PagesPerHistoryDataEntry) >
historyMaxPageNumber THEN ERROR;
END;





UpdateTestParams
: PROCEDURE =
BEGIN
nTransSets ← RandomCard.Choose[min: 0, max: maxNTransSets];
nTransPerSet ← RandomCard.Choose[min: 0, max: maxNTransPerSet];
nSharedFilesPerTrans ← RandomCard.Choose[min: 0, max: maxNSharedFilesPerTrans];
nUnsharedFilesPerTrans ← RandomCard.Choose[min: 0, max: maxNUnsharedFilesPerTrans];
nPageRunsPerSharedFile ← RandomCard.Choose[min: 0, max:
maxNPageRunsPerSharedFile];
nPageRunsPerUnsharedFile ← RandomCard.Choose[min: 0, max:
maxNPageRunsPerUnsharedFile];
nErrorTrans ← RandomCard.Choose[min: 0, max: maxNErrorTrans];
END;


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


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



-- params still valid after rollback:

fileStore
: AE.FileStore;
localVolumeName
: AE.FileStore;
local:
BOOLEAN;
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.
MaxPagesPerPageRun: NAT = 15;
MaxSizeOfFile: NAT = 300;
MaxSharedFilesEachRun: NAT = 25;

crashes: BOOLEAN;

maxNTransSets
: NAT;
maxNTransPerSet: NAT;
maxNSharedFilesPerTrans: [0..MaxFilesPerTrans]; -- this +
maxNUnsharedFilesPerTrans: [0..MaxFilesPerTrans]; -- this must be <= MaxFilesPerTrans;
maxNPageRunsPerSharedFile: [0..MaxPageRunStatesPerFile];
maxNPageRunsPerUnsharedFile: [0..MaxPageRunStatesPerFile];
maxNErrorTrans: NAT;



-- other params:

historyUniversalFile: AE.UniversalFile;

nextSeed
: INTEGER;
testStartTime: LONG CARDINAL;
forcedStop: BOOLEAN ← FALSE;

historyMaxPageNumber: AE.PageNumber;
historyNextPageNumber: AE.PageNumber;

nTransSets
: NAT;
nTransPerSet: NAT;
nSharedFilesPerTrans: [0..MaxFilesPerTrans]; -- this +
nUnsharedFilesPerTrans: [0..MaxFilesPerTrans]; -- this must be <= MaxFilesPerTrans;
nPageRunsPerSharedFile: [0..MaxPageRunStatesPerFile];
nPageRunsPerUnsharedFile: [0..MaxPageRunStatesPerFile];
nErrorTrans
: NAT;

typescriptStream: IO.STREAM;

whatToDoCount: NAT ← 0;



-- main line code:


FOR whatToDo: WhatToDo IN [WhatToDo.FIRST..WhatToDo.LAST)
DO
whatToDoCount ← whatToDoCount + 1;
ENDLOOP;




END.