FSFileSpaceImpl.mesa
Last Edited by: December 12, 1983 3:26 pm
DIRECTORY
BasicTime USING [GMT, Period],
BTree USING [Error],
File USING [Delete, Error, FP, Handle, Open, SetFlusher, Volume],
FS USING [Error],
FSBackdoor USING [EntryPtr, EntryType, MakeFName, Version],
FSDir USING [AcquireOldGName, DeleteEntry, EnumerateEntries],
FSFileOps USING [GetNameBodyAndVersion, VolumeDesc],
FSLock USING [ActiveFile, ReleaseRecord],
FSReport USING [ReportRemote],
Rope USING [ROPE, Text];
FSFileSpaceImpl: CEDAR MONITOR
IMPORTS BasicTime, BTree, File, FS, FSBackdoor, FSDir, FSFileOps, FSLock, FSReport
EXPORTS FSFileOps
= BEGIN
Space management variables and types
UseRecord: TYPE = RECORD[next: REF UseRecord, fp: File.FP, used: BasicTime.GMT];
LRU: TYPE = RECORD[
vDesc: FSFileOps.VolumeDesc ← NIL,
first: REF UseRecord ← NIL,
killed, -- times killed
sorts, -- number of lru lists made since last killed
count, -- initial length of last lru list made
cumCount, flushed, error, deleted, superseded, open: INT ← 0 -- since killed
];
lru: LRU;
Exported to FSFileOps
RegisterVolumeFlusher: PUBLIC ENTRY PROC [svDesc: FSFileOps.VolumeDesc] =
BEGIN
IF svDesc = NIL
THEN BEGIN -- kill any existing volume flusher
IF lru.vDesc # NIL
THEN KillFlusher[]; -- already is a volume flusher
END
ELSE BEGIN -- establish a new volume flusher
lru.vDesc ← svDesc;
File.SetFlusher[lru.vDesc.vol, MakePagesAvailable, NIL];
END;
END;
RecordUsage: PUBLIC PROC [fp: File.FP, time: BasicTime.GMT] = {};
Internal procedures
MakePagesAvailable: ENTRY PROC [volume: File.Volume, nPages: INT, clientInfo: REF] RETURNS [ok: BOOLEAN] =
BEGIN
ok ← FALSE;
IF lru.vDesc # NIL
THEN BEGIN -- flusher is turned on
ENABLE BTree.Error, File.Error => { KillFlusher[]; CONTINUE };
ok ← FlushAnother[];
IF NOT ok
THEN ok ← FlushAnother[]; -- make another lru list and try again
END;
END;
KillFlusher: INTERNAL PROC =
BEGIN
File.SetFlusher[lru.vDesc.vol, NIL, NIL];
lru ← [NIL, NIL, lru.killed+1, 0, 0, 0, 0, 0, 0, 0, 0];
END;
FlushAnother: INTERNAL PROC RETURNS [BOOLEAN] =
BEGIN
IF lru.first = NIL
THEN BEGIN -- build a new LRU list
GetFPandUsed: UNSAFE PROC [entry: FSBackdoor.EntryPtr] RETURNS [accept, stop: BOOLEAN] = UNCHECKED
BEGIN
WITH e: entry^ SELECT FROM
cached => BEGIN
lru.first ← NEW [ UseRecord ← [lru.first, e.fp, e.used] ];
lru.count ← lru.count+1;
END;
ENDCASE => ERROR;
RETURN [accept: FALSE, stop: FALSE];
END;
lru.count ← 0;
FSDir.EnumerateEntries[lru.vDesc, "[", all, GetFPandUsed, NIL];
lru.first ← LRUOrder[lru.first];
lru.cumCount ← lru.cumCount + lru.count;
lru.sorts ← lru.sorts+1;
END;
DO
victim: REF UseRecord = lru.first;
IF victim = NIL THEN EXIT; -- time to stop
lru.first ← victim.next;
victim.next ← NIL;
IF TryVictim[victim] THEN RETURN[TRUE];
ENDLOOP;
RETURN [FALSE];
END;
TryVictim: INTERNAL PROC [v: REF UseRecord] RETURNS [BOOLEAN] =
BEGIN
fName: Rope.ROPE;
t: FSBackdoor.EntryType;
dirUsed: BasicTime.GMT;
aF: FSLock.ActiveFile;
entryFP: File.FP;
h: File.Handle;
nameBody: Rope.Text;
version: FSBackdoor.Version;
h ← File.Open[lru.vDesc.vol, v.fp
! File.Error => BEGIN
IF why = unknownFile
THEN lru.deleted ← lru.deleted+1
ELSE lru.error ← lru.error+1;
GOTO failed;
END ];
[nameBody, version] ← FSFileOps.GetNameBodyAndVersion[h
! FS.Error => BEGIN
IF error.code = $badFP
THEN lru.deleted ← lru.deleted+1
ELSE lru.error ← lru.error+1;
GOTO failed;
END ];
[t, dirUsed, entryFP, aF] ← FSDir.AcquireOldGName[lru.vDesc, nameBody, version];
IF t = notFound
THEN { lru.deleted ← lru.deleted+1; GOTO failed };
IF aF = NIL OR aF.fileLock # none
THEN { FSLock.ReleaseRecord[aF]; lru.open ← lru.open+1; GOTO failed };
IF entryFP # v.fp -- wierd case, but I think it can happen
THEN { FSLock.ReleaseRecord[aF]; lru.deleted ← lru.deleted+1; GOTO failed} ;
IF BasicTime.Period[from: dirUsed, to: v.used ] < 0
THEN { FSLock.ReleaseRecord[aF]; lru.superseded ← lru.superseded+1; GOTO failed };
fName ← FSBackdoor.MakeFName[nameBody, version];
FSReport.ReportRemote[startFlushing, fName];
FSDir.DeleteEntry[lru.vDesc, nameBody, version
! BTree.Error, File.Error =>
{ FSLock.ReleaseRecord[aF]; FSReport.ReportRemote[endFlushing, fName] }
];
FSLock.ReleaseRecord[aF];
File.Delete[ h
! File.Error =>
{ lru.error ← lru.error+1; FSReport.ReportRemote[endFlushing, fName]; GOTO failed }
];
FSReport.ReportRemote[endFlushing, fName];
lru.flushed ← lru.flushed+1;
RETURN [TRUE];
EXITS failed => RETURN [FALSE];
END;
LRUOrder: PROC [list: REF UseRecord] RETURNS [first: REF UseRecord] =
BEGIN
Merge: PROC [list1, list2: REF UseRecord] RETURNS [mergeStart: REF UseRecord] =
BEGIN
end: REF UseRecord;
IF list1 = NIL THEN {mergeStart ← list2; RETURN};
IF list2 = NIL THEN {mergeStart ← list1; RETURN};
IF BasicTime.Period[from: list1.used, to: list2.used] > 0
THEN {mergeStart ← list1; list1 ← list1.next}
ELSE {mergeStart ← list2; list2 ← list2.next};
end ← mergeStart;
DO
IF list1 = NIL THEN {end.next ← list2; EXIT};
IF list2 = NIL THEN {end.next ← list1; EXIT};
IF BasicTime.Period[from: list1.used, to: list2.used] > 0
THEN {end.next ← list1; list1 ← list1.next}
ELSE {end.next ← list2; list2 ← list2.next};
end ← end.next;
ENDLOOP;
END;
maxLists: CARDINAL = 22; -- bits in word-address - 2
lists: ARRAY [0 .. maxLists] OF REF UseRecord ← ALL [NIL];
UNTIL list = NIL DO
i: CARDINAL ← 0;
first ← list;
list ← list.next;
first.next ← NIL;
UNTIL lists[i] = NIL DO
first ← Merge[first, lists[i]];
lists[i] ← NIL;
i ← i+1;
ENDLOOP;
lists[i] ← first;
ENDLOOP;
first ← NIL;
FOR i: CARDINAL IN [0 .. maxLists] DO
first ← Merge[first, lists[i]];
lists[i] ← NIL;
ENDLOOP;
END;
END.