-- 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.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, changedVersionIncrements: 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 (nSharedFilesThisRun _ nSharedFilesThisRun + 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 (bugCatcher _ bugCatcher -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 transSets _ newTransSets; 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].universalFile _ refUniversalFile^; 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 nCommittedTrans _ nCommittedTrans + 1; IF historyDataEntry.timeStamp = 0 THEN ERROR BadState; FOR fileIndex: INT DECREASING IN [0..historyDataEntry.nFileStates) DO universalFile: AE.UniversalFile _ historyDataEntry.fileStates[fileIndex].universalFile; fileFound: BOOLEAN _ TRUE; 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.oldestTransCommitTime _ historyDataEntry.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.versionIncrements _ listRegFile.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.Type _ FileTypes.tUnassigned; modifyAccessList: AE.AccessList; readAccessList: AE.AccessList; owner: AE.OwnerName; bugCheck: ARRAY AE.Property OF BOOLEAN _ ALL[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.realModifyAccess _ PackAccessList[modifyAccessList, owner]; listRegFile.first.realReadAccess _ PackAccessList[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.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: BOOLEAN _ SELECT TRUE FROM Rope.Equal[owner, caller, FALSE] => TRUE, Rope.Equal[owner, "AlpineTest", FALSE] => FALSE, ENDCASE => ERROR; packedAccessList _ nullPackedAccessList; FOR list: AE.AccessList _ accessList, 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 packedStringName _ nullPackedStringName; IF (packedStringName.count _ rope.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. Êõ˜Jš¿œÏc(œ$œÏk œžœ&žœ»žœÄžœ:žœIžœ€žœ3žœ!žœ!žœ^žœ%žœ$žœ)žœ:žœ9žœ]žœ2žœ(žœÐblœžœžœîžœžœšÐbcöÐckñÏn¢œžœ¢œžœ.¢œ@žœ¢œžœžœž œžœÐcvÏvœžœœœž œ œžœ œžœœ&œ&œ*œ/œœžœœ+œœžœžœœžœžœžœžœ žœžœžœ6žœž œ¢œžœ¢œžœ,¢œ<žœ¢œžœžœž œžœœ œžœž œ œžœžœž œžœ œžœžœž œžœžœ žœžœœžœžœž œžœžœžœžœžœžœžœžœž œžœžœ žœžœžœ/žœž œ¢ œžœžœ œœžœ žœœ œœ žœ žœžœœžœœ œœ #œ œœžœœœž œœœœœœœœžœ œžœžœ¢ œžœ#¢œžœ!¢ œžœ}¢œžœ'¢œžœžœœœžœ¢œžœžœžœ žœ žœž œ¢œ'žœ¢œžœ¢œžœžœžœžœžœ¢œžœžœ¢œžœ-bœ¢œ>žœžœ4žœžœžœ·žœ2¢œ&œ¢œ !œ¢ œžœžœažœ0¢ž œž ¢œZžœžœžœžœžœžœ žœ"ž œ)žœ žœ6žœžœ¨¢œgžœ+žœžœ&žœžœ(žœžœžœ žœžœ"žœžœžœžœžœžœžœžœžœžœ žœžœžœ#ž œ žœ)žœ—žœ žœ žœžœžœžœ žœžœ žœžœž œ¢žœžœžœ¢>¢ž œžœ žœž ¢žœ žœžœž œJžœžœ¢œžœ¢œžœ¢ž œ$žœž œžœž ¢œžœžœ<ž œžœžœžœžœFžœ_žœ#žœžœžœSžœ)žœžœ7žœ#žœžœžœ\žœEžœžœ6žœžœ¢ œžœžœQ¢ž œ'žœ'ž œ$žœž œžœ žœžœž ¢žœžœcžœžœ žœžœžv¡žœ žœžœžœžœ žœžœžœžœžœ žœ¦žœažœžœžœžœ#žœžœžœžœAžœžœžœžœ?žœžœžœ/žœžœŒžœ„žœžœ žœ_žœ$žœžœ6žœ!žœžœKžœ žœžœžœœžœ!žœ-žœ žœ!ž œžœž œ\žœžœ+žœžœ žœ žœ žœ9žœ žœžœJžœžœ…žœ;žœOžœžœ žœžœ‰žœDžœ9žœžœžœGžœ?žœTžœžœž œ@žœžœCžœžœžœžœžœžœ žœ žœžœ¢ ž ¢žœ žœžœžœ žœžœ žœžœžœžœ-ž œ ž¢$¢ œžœ œ œœœœœ œœœœ+œ¢œž œžœžœ žœžœ8žœåž œJž œžœFž œOžœ3ž œ žœ žœžœ«ž œ žœžœTžœž œžœžœžœžœžœ6žœžœžœž œžœžœžœ+ž œžœ*žœžœžœ%žœœž œ‹žœžœœž œ–žœžœ œž œ…žœžœœž œxžœžœœž œžœžœœž œŠžœžœœž œžœžœœž œEžœžœ œž œžœžœ œž œÄžœœž œ¢žœœž œ­žœ ž œžœ žœžœ žœžœŽžœžœžœ¢œ¢ œž œžœžœžœžœžœžœžœžœžœ žœžœžœžœžœžœžœ žœžœ œžœžœžœžœžœ žœžœž œžœžœžœ6žœžœžœ\žœžœBžœ žœžœ$žœžœžœž œžœž œžœžœ^žœ žœ?žœžœBžœ*žœ6žœžœžœžœžœ#žœž'œižœ]žœ6žœžœžœ ž œ žœžœ[žœž œžœ}žœž œ žœHžœœ žœLžœfžœ§ž œUžœžœ-žœ5žœ¢œž œžœ,ž¢œXžœ$žœžœ,¢œ8¢œ_ž œOžœ¢œ‰œÕœœ÷Ðcn"¥¥*œ¢ª¡á ¡ã¢œžœ žœ¢ œžœžœ3žœžœœžœ œH1œž œ$+œž œžœâžœžœœ œ'œ+œ žœ ¢œžœžœ žœ ž œ¢œžœ¢ œžœžœ"žœ žœ¢œžœžœžœ ¢ œžœžœžœ¢ž œ+žœžœ žœž ¢ž¢œ^¢œj¢œžœžœž œž œž 1ž œžœžœžœžœž!œž œžœžœž!œž œžœžœžœžœž"œžœžœž!œGž¢œ”žœú¢œFž œC¢žœLž œ#žœžœžœržœ1žœžœUž œ?ž œžœžœ#žœžœž œž œ ž œ žœž œ,žœžœžœ4ž œ žœžœžœ žœžœýžœžœžœ žœž œžKžœžœžœžœ žœ#žœjžœž2œžœžœžœžœžœ2žœNžœdžœžœ ž=œvKžœžœ/žœžœ žœžœ žœžœžHžœ^žœžœ ž¢ ž Qžœ,žœEžœCžœ3žœžœ ž žžž œpž œ ž ž œžœ?žœBžœQžœžœžœžœžœžœ?žœžœž œRžœžœZžœžœžœžœTžœžœ\ž œ6ž œ¦žœžœžœ"žœžœžœžœžœžœžœžœžœžœžœžœ¢žœžœž œžžœž!œžœžœžœž+œž(œžœžœž+œž(œžœžœžœž+œž(œžœž+œžœž œžœžœ žœ žœžœ?žœžœžœžœžœžœžœšžœžœAžœžœ žœ#žœžœ žœ žœžœ>žœ¬žœžœžœ Rœ4žœ-žœ%žœžœžœ¢žœ¢œžœžœ¢ œžœžœ&œ¢ž œžœžœžœ¢žœžœž œ žœžœžœ;žœžœž œž$œžœžœ ž!œžœžœ žœž œžœž'œžœ žœžœž#œžœÑžœž œ‚žœ,žœªžœžœ0žœ#žœžœ¢žœ¢ž œ'žœ ¢žœžœ¢œ!¢œ7¢œ žœ žœžœžœžœžœžœ_žœžœ4žœžœžœžœž œ žœžœžœ žœžœmžœ!ž œžœžœ•ž œ[žœ)ž œYžœžœ žœ/ž œ0žœ8žœJžœ*žœžœ ž$œ)žœ žœižœ žœ.ž!œžœžœ ž"œ6žœQžœžœ žœžœžœžœžœ ž œžœžœžœžœ¢œ"žœ)¢œ žœ'¢œ?ž¢žœ ¢ž œžœ,ž œ¢žœžœ"žœžœžœ@žœ$žœžœ&žœžœžœºžœIžœžœFžœžœžœ%ž¢žœ¢ ž œžœ1ž œžœž¢žœžœ0¢žœ Ðjkœ¢œ¦œ¢œžœ%ž¡žžœ¢œžœ!žœ,ž$¢œ¦œ¢œžœ:Ðknžœ¢œ¢ž ¢ž ¢žœ žœžœž œ žœžœœžœ"žœž!œ0žœžœ"žœ.žœ ž(œ žœ,žœžœžœ#žœ0žœžœž œžœ7žœ ¦œ ¦œ žœžœžœKžœž œ¦œžœ¢žœ¢1¢ž œ2žœ žœ žœž ¢œžœžœž œHžœ"žœžœžœ\žœžœžœ>žœžœžœžœžœ{žœ9ž œžœžœ¢œ%žœ žœ žœ žœžœ¢œ%žœ žœ žœ žœžœ¢œ%žœ žœ žœžœžœ¢œ+¢ ž œžœž ¢žœ'žœ žœ9žœ!žœžœ¢ ž œ9žœ*žœÄžœžœž 1¢œCžœ¢œžœTžœžœ.žœžœžœLžœ²žœ žœžœ™žœ1žœžœžœžœžœžœ®žœžœ*ž œž œ'žœ žœ*žœžœžœžœœ&žœ$žœ¹žœ$žœ!žœž œhžœ"žœ'žœ!žœž œžœ6žœžœ"žœžœ ¢žœžœžœ/žœ žœ¢ ž œž œž œžœ%œžœžœ5žœ žœžœžœ3žœžœžœ¢ž œž œ€¢œ0žœ žœ žœžœžœžœžœžœ)žœD¢ž¢œž œ^žœžœ¢ž œž ¢žœžœžœ.žœ¢œž ¢ž œdžœ6žœžœ@žœ žœ,žœžœ-žœžœžœ¢œž ¢ž œ>žœ®žœ¢œž œž œNžœ¢œž œž œ^žœ¢œž ¢ž œMžœ¢œž œ1žœ%¢ž œžœžœžœžœ'žœžœžœžœžœžœž œžœ žœž œžœžœ(žœžœ ž œ žœ ž œžœžœžœžœž œ%žœžœžœžœžœž œ'žœžœž œ*žœžœžœž œžœžœžœžœžœ¢œž œžœ%¢ž œžœžœžœžœ(žœžœžœžœ2žœ¢œž œžœc¢ž œ€žœžœ žœ?žœ&žœžœÕžœužœžœHž œ#žœtž œLžœ¢ œž ¢œ ¢ž œŸžœ'žœ žœžœž œžœ5žœžœžœŠžœCžœkž œNžœ¢œžœžœ&ž œ+ž¦œi¦œ¦œž¢œž ¢ž œ<žœ@žœPžœTžœZžœ\žœ>žœ¢ œž¢ž œ ž œ¢œžœ¢žœ žœžœžœ¢&¢ œ¢œ¢žœ¢œ¢œ¢ œžœ¢œ¢œ¢œžœ,œ¢œžœ,œ¢œžœ¢ œžœ¢œžœ¢œžœ¢œžœ¢œžœ¢œ œ¢œ%œ¢œ ¢œ ¢œžœ¢œ¢œ¢œ¢ œžœ¢ œž œ¢ œžœ¢œ¢œ¢ œžœ¢ œžœ¢œ œ¢œ%œ¢œ ¢œ¢ œžœ¢œ¢ œžœ¢œžœžœ žœ žœžœ*žœ¢œžœ˜¢‡J˜—…—¦¡