FILE: JukeboxImpl.mesa
Last Edited by: Stewart, December 30, 1983 11:38 am, Cedar5
Last Edited by: Swinehart, September 23, 1984 8:54:07 pm PDT
DIRECTORY
Basics USING [BITAND, BITNOT, BITOR, LowHalf],
BasicTime USING [Now],
FileNames USING [ConvertToSlashFormat, ResolveRelativePath],
FS USING [BytesForPages, Close, Create, Delete, Error, ExpandName, nullOpenFile, Open, OpenFile, Read, SetByteCountAndCreatedTime, Write],
IO USING [int, PutF, PutRope, STREAM],
Jukebox,
Rope USING [Equal, ROPE],
VM USING [AddressForPageNumber, Allocate, Free, Interval, wordsPerPage];
JukeboxImpl: MONITOR LOCKS self.LOCK USING self: Jukebox.Handle
IMPORTS Basics, BasicTime, FileNames, FS, IO, Rope, VM
EXPORTS Jukebox
SHARES Jukebox =
BEGIN OPEN Jukebox;
Global variables
instances: LIST OF Jukebox.Handle ← NIL;
Standard error messages.
FormatErrorMsg: Rope.ROPE = "Internal format error in jukebox.";
OpenErrorMsg: Rope.ROPE = "No jukebox open.";
BadTuneErrorMsg: Rope.ROPE = "Bad tune id.";
BadTunePointerMsg: Rope.ROPE = "Bad tune pointer.";
Standard page numbers:
HeaderPageNumber: Jukebox.PageNumber = 1;
FirstMapPageNumber: Jukebox.PageNumber = 2;
Bit patterns used to manipulate the tune header bit map:
Bits: ARRAY[0..15] OF CARDINAL =
[1,2,4,10B,20B,40B,100B,200B,400B,1000B,2000B,
4000B,10000B,20000B,40000B,100000B];
BitsInUse: CARDINAL = 177777B;
Signal definitions.
Error: PUBLIC ERROR[reason: ErrorCode, rope: Rope.ROPE] = CODE;
MissingChirp: PUBLIC ERROR = CODE;
EOF: PUBLIC ERROR = CODE;
Utility procedures
SHORT: PROC [l: LONG UNSPECIFIED] RETURNS [UNSPECIFIED] = {
RETURN[Basics.LowHalf[l]];
};
Exported procedures
CreateJukebox: PUBLIC PROC [name: Rope.ROPE, nPages: INT, nTunes: INT] = {
space: VM.Interval; -- Holds jukebox disk block.
locWindow: Jukebox.WindowOrigin; -- Used to initialize jukebox.
h: LONG POINTER TO JukeboxHeader;
m: LONG POINTER TO TuneBitMap;
d: LONG POINTER TO TuneDescriptor;
f: LONG POINTER TO FreeList;
nChirps, nMaps: INT;
firstChirp: INT;
t1, t2: INTEGER;
fullFName: Rope.ROPE;
errorMsg: Rope.ROPENIL;
self: Jukebox.Handle ← NEW[Jukebox.JukeboxObject];
nMaps ← (nTunes + 16*bitMapSize - 1)/(16*bitMapSize);
firstChirp ← 2 + nMaps + 2*nTunes;
nChirps ← (nPages - firstChirp)/pagesPerChirp;
IF nChirps < 1 THEN ERROR Error[TooManyTunes, "Too many tunes requested for jukebox size."];
name ← FileNames.ResolveRelativePath[name];
fullFName ← FS.ExpandName[name ! FS.Error => IF error.group = $user THEN {
errorMsg ← error.explanation;
CONTINUE;
}].fullFName;
IF errorMsg # NIL THEN ERROR Error[reason: NoJukebox, rope: errorMsg];
locWindow.file ← FS.nullOpenFile;
locWindow.file ← FS.Open[name: fullFName, lock: $write, wDir: NIL ! FS.Error => {
SELECT error.group FROM
$lock => ERROR Error[reason: JukeboxOpen, rope: error.explanation];
ENDCASE => CONTINUE;
}];
IF locWindow.file # FS.nullOpenFile THEN {
FS.Close[locWindow.file];
ERROR Error[reason: JukeboxOpen, rope: "Cannot creat jukebox, it already exists"];
};
locWindow.file ← FS.Create[name: fullFName, pages: nPages, setKeep: TRUE, keep: 1, wDir: NIL];
FS.SetByteCountAndCreatedTime[file: locWindow.file, bytes: FS.BytesForPages[nPages], created: BasicTime.Now[]];
Allocate a space and set pointers so we can initialize the
various blocks of the jukebox.
space ← VM.Allocate[count: 1];
h ← LOOPHOLE[VM.AddressForPageNumber[page: space.page]];
m ← LOOPHOLE[h];
d ← LOOPHOLE[h];
f ← LOOPHOLE[h];
Initialize the header page.
h.magic ← magicJukeboxHeader;
h.nTunes ← nTunes;
h.nFreeChirps ← h.nChirps ← nChirps;
h.freeListHead ← h.firstChirp ← firstChirp;
locWindow.base ← HeaderPageNumber;
CopyOut[space, locWindow];
Initialize the tune header bit map.
FOR i: INTEGER IN [0..bitMapSize) DO m.bits[i] ← 0 ENDLOOP;
m.magic ← magicTuneBitMap;
FOR i: INT IN [0..nMaps) DO
locWindow.base ← FirstMapPageNumber + i;
CopyOut[space, locWindow];
ENDLOOP;
In the last map page, mark tune headers after the last valid one
as in use.
t1 ← SHORT[nMaps*bitMapSize*16 - nTunes];
t2 ← bitMapSize - t1/16;
t1 ← t1 MOD 16;
FOR i: INTEGER IN [t2..bitMapSize) DO m.bits[i] ← BitsInUse ENDLOOP;
FOR i: INTEGER IN [16-t1..15] DO
m.bits[t2-1] ← Basics.BITOR[m.bits[t2-1], Bits[i]];
ENDLOOP;
CopyOut[space, locWindow];
Initialize the tune header pages.
d.magic ← magicTuneHeader;
d.state ← available;
d.size ← 0;
FOR i: INTEGER IN [0..nHdrChirps) DO d.chirps[i] ← 0 ENDLOOP;
d.indirectChirp ← 0;
d.deepChirp ← 0;
d.openCount ← 0;
FOR i: INT IN [0..nTunes) DO
d.tuneId ← locWindow.base ← 2*i + 2 + nMaps;
CopyOut[space, locWindow];
ENDLOOP;
Initialize the free list.
f.magic ← magicFreeList;
f.nChirps ← freeListSize;
FOR i: INTEGER IN [0..freeListSize) DO
f.chirps[i] ← firstChirp + pagesPerChirp*i;
ENDLOOP;
f.next ← f.chirps[freeListSize-1] + pagesPerChirp;
locWindow.base ← firstChirp;
CopyOut[space, locWindow];
FOR i: INT IN [1..(nChirps + freeListSize - 1)/freeListSize) DO
FOR j: INTEGER IN [0..freeListSize) DO
f.chirps[j] ← f.chirps[j] + freeListSize*pagesPerChirp;
ENDLOOP;
f.next ← f.next + freeListSize*pagesPerChirp;
locWindow.base ← firstChirp + i*freeListSize*pagesPerChirp;
CopyOut[space, locWindow];
ENDLOOP;
Handle the last block of the free list specially, since it may not contain
a full list of chirps.
f.nChirps ← SHORT[nChirps MOD freeListSize];
f.next ← 0;
IF f.nChirps = 0 THEN f.nChirps ← freeListSize;
CopyOut[space, locWindow];
VM.Free[space];
FS.Close[locWindow.file];
locWindow.file ← FS.nullOpenFile;
};
DeleteJukebox: PUBLIC PROC [name: Rope.ROPE] = {
fullFName: Rope.ROPE;
errorMsg: Rope.ROPENIL;
name ← FileNames.ResolveRelativePath[name];
fullFName ← FS.ExpandName[name ! FS.Error => IF error.group = $user THEN {
errorMsg ← error.explanation;
CONTINUE;
}].fullFName;
IF errorMsg # NIL THEN ERROR Error[reason: NoJukebox, rope: errorMsg];
IF FindJukebox[fullFName] # NIL THEN ERROR Error[reason: JukeboxOpen, rope: "Cannot delete an open jukebox"];
FS.Delete[name: fullFName, wDir: NIL];
};
FindJukebox: PUBLIC PROC [name: Rope.ROPE] RETURNS [Jukebox.Handle] = {
fullFName: Rope.ROPE;
errorMsg: Rope.ROPENIL;
name ← FileNames.ResolveRelativePath[name];
fullFName ← FS.ExpandName[name ! FS.Error => IF error.group = $user THEN {
errorMsg ← error.explanation;
CONTINUE;
}].fullFName;
IF errorMsg # NIL THEN ERROR Error[reason: NoJukebox, rope: errorMsg];
fullFName ← FileNames.ConvertToSlashFormat[fullFName];
FOR l: LIST OF Jukebox.Handle ← instances, l.rest WHILE l # NIL DO
IF Rope.Equal[l.first.jukeboxName, fullFName, FALSE] THEN RETURN[l.first];
ENDLOOP;
RETURN[NIL];
};
OpenJukebox: PUBLIC PROC [name: Rope.ROPE] RETURNS [Jukebox.Handle] = {
fullFName: Rope.ROPE;
errorMsg: Rope.ROPENIL;
self: Jukebox.Handle ← NEW[Jukebox.JukeboxObject];
name ← FileNames.ResolveRelativePath[name];
fullFName ← FS.ExpandName[name ! FS.Error => IF error.group = $user THEN {
errorMsg ← error.explanation;
CONTINUE;
}].fullFName;
IF errorMsg # NIL THEN ERROR Error[reason: NoJukebox, rope: errorMsg];
fullFName ← FileNames.ConvertToSlashFormat[fullFName];
IF FindJukebox[fullFName] # NIL THEN ERROR Error[reason: JukeboxOpen, rope: "Jukebox already open"];
self.window.file ← FS.Open[name: fullFName, lock: $write, wDir: NIL ! FS.Error => {
SELECT error.group FROM
$user => ERROR Error[reason: NoJukebox, rope: error.explanation];
$lock => ERROR Error[reason: JukeboxOpen, rope: error.explanation];
ENDCASE => REJECT;
}];
self.jukeboxName ← fullFName;
OpenLocked[self];
RETURN[self];
};
OpenLocked: ENTRY PROC [self: Jukebox.Handle] = {
ourhdr: LONG POINTER TO JukeboxHeader ← NIL;
ourfree: LONG POINTER TO FreeList ← NIL;
{
ENABLE UNWIND => {
IF ourhdr # NIL THEN VM.Free[self.hdrSpace];
IF ourfree # NIL THEN VM.Free[self.freeSpace];
};
Allocate spaces to cache the header block, and a free list block.
self.hdrSpace ← VM.Allocate[count: 1];
self.freeSpace ← VM.Allocate[count: 1];
ourhdr ← LOOPHOLE[VM.AddressForPageNumber[self.hdrSpace.page]];
ourfree ← LOOPHOLE[VM.AddressForPageNumber[self.freeSpace.page]];
Read in the header block, first bit map block, and first free list block.
self.window.base ← HeaderPageNumber;
CopyIn[self.hdrSpace, self.window];
IF ourhdr.magic # magicJukeboxHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
self.currentMapPageNumber ← FirstMapPageNumber;
self.window.base ← self.currentFreeChirpNumber ← ourhdr.freeListHead;
IF self.currentFreeChirpNumber # 0 THEN {
CopyIn[self.freeSpace, self.window];
IF ourfree.magic # magicFreeList THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
}
ELSE {
ourfree.magic ← magicFreeList;
ourfree.nChirps ← 0;
ourfree.next ← 0;
};
self.maxMapPageNumber ← FirstMapPageNumber + (ourhdr.nTunes/(16*bitMapSize));
self.firstDesPageNumber ← self.maxMapPageNumber + 1;
self.openTuneHeaderList ← NIL;
Advance the header to reference the NEXT free list block, and write out the header. This way, if the system crashes we may lose track of a few free blocks but will never think a block is free when it isn't.
ourhdr.freeListHead ← ourfree.next;
ourhdr.nFreeChirps ← ourhdr.nFreeChirps - ourfree.nChirps;
self.window.base ← HeaderPageNumber;
CopyOut[self.hdrSpace, self.window];
self.hdr ← ourhdr;
self.free ← ourfree;
instances ← CONS[self, instances];
}};
Info: PUBLIC ENTRY PROC [self: Jukebox.Handle] RETURNS [name: Rope.ROPE, nPages, nTunes: INT] = {
ENABLE UNWIND => NULL;
loc: Rope.ROPE;
loc ← self.jukeboxName;
IF self.hdr = NIL THEN RETURN[name: NIL, nPages: 0, nTunes: 0];
RETURN [name: loc, nPages: self.hdr.nChirps*pagesPerChirp + self.hdr.firstChirp, nTunes: self.hdr.nTunes];
};
CloseJukebox: PUBLIC ENTRY PROC [self: Jukebox.Handle] RETURNS [Jukebox.Handle] = {
ENABLE UNWIND => NULL;
IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg];
Close all of the open tunes.
WHILE self.openTuneHeaderList # NIL DO
self.openTuneHeaderList.openCount ← 1;
InternalCloseTune[self, self.openTuneHeaderList]
ENDLOOP;
Update the free list and the header block. Order of write-out is important here, if the jukebox is to survive after inopportune crashes.
self.hdr.nFreeChirps ← self.hdr.nFreeChirps + self.free.nChirps;
IF self.free.nChirps # 0 THEN {
self.free.next ← self.hdr.freeListHead;
self.hdr.freeListHead ← self.window.base ← self.currentFreeChirpNumber;
CopyOut[self.freeSpace, self.window];
};
self.window.base ← HeaderPageNumber;
CopyOut[self.hdrSpace, self.window];
Clear out the data structures.
VM.Free[self.freeSpace];
self.free ← NIL;
VM.Free[self.hdrSpace];
self.hdr ← NIL;
FS.Close[self.window.file];
self.window.file ← FS.nullOpenFile;
instances ← DRemove[ref: self, list: instances];
RETURN[NIL];
};
DRemove: INTERNAL PROC [ref: Jukebox.Handle, list: LIST OF Jukebox.Handle] RETURNS[LIST OF Jukebox.Handle] = {
l, l1: LIST OF Jukebox.Handle ← NIL;
l ← list;
UNTIL l = NIL DO
IF l.first = ref THEN {
IF l1 = NIL THEN RETURN[l.rest]; -- ref was first object on list
l1.rest ← l.rest;
RETURN[list];
};
l1 ← l;
l ← l.rest;
ENDLOOP;
RETURN [list];
}; -- of Dremove;
CreateTune: PUBLIC ENTRY PROC [self: Jukebox.Handle, tuneId: INT ← -1]
RETURNS [Tune] = {
This procedure creates a new tune. If tuneId is zero, then this routine selects a new tuneId. If tuneId is non-zero, then the indicated tune is used: if it had previously been allocated, then its old contents are discarded.
ENABLE UNWIND => NULL;
tune: Tune ← NIL;
word, bit: INTEGER;
mapPage: INT;
mapSpace: VM.Interval;
map: LONG POINTER TO TuneBitMap ← NIL;
tuneSpace: VM.Interval;
{ENABLE UNWIND => {
IF map # NIL THEN VM.Free[mapSpace];
IF tune # NIL THEN VM.Free[tuneSpace];
};
IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg];
Allocate a space for a map page and for the new tune.
mapSpace ← VM.Allocate[count: 1];
map ← LOOPHOLE[VM.AddressForPageNumber[mapSpace.page]];
tuneSpace ← VM.Allocate[count: 1];
tune ← LOOPHOLE[VM.AddressForPageNumber[tuneSpace.page]];
If an explicit tune has been specified, then validate it, read it in, and delete its contents. Note: if the tune is currently in the cache, then it is locked against us.
IF tuneId >= 0 THEN {
IF tuneId >= self.hdr.nTunes THEN ERROR Error[BadTune, BadTuneErrorMsg];
IF searchTuneCache[self, tuneId] # NIL THEN ERROR Error[TuneLocked, "Tune is locked."];
self.window.base ← desPage[self, tuneId];
CopyIn[space: tuneSpace, window: self.window];
IF tune.magic # magicTuneHeader
THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
tune.tuneId ← tuneId;
tune.space ← tuneSpace;
IF tune.state = inUse THEN killTune[self: self, tune: tune, updateMap: FALSE]
ELSE {
The tune wasn't previously in use. Mark the bit map to show it being used.
self.window.base ← FirstMapPageNumber + tuneId/(bitMapSize*16);
CopyIn[space: mapSpace, window: self.window];
IF map.magic # magicTuneBitMap THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
word ← SHORT[tuneId MOD (bitMapSize*16)];
bit ← word MOD 16;
word ← word/16;
map.bits[word] ← Basics.BITOR[map.bits[word], Bits[bit]];
CopyOut[mapSpace, self.window];
};
}
ELSE {
Scan through the tune bit map, looking for a free tune. Remember that the bit map is just a HINT: the final authority is the state field in the tune header.
mapPage ← self.window.base ← self.currentMapPageNumber;
CopyIn[mapSpace, self.window];
IF map.magic # magicTuneBitMap THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
WHILE TRUE DO
FOR i: INTEGER IN [0..bitMapSize) DO
IF map.bits[i] # 177777B THEN {
FOR j: INTEGER IN [0..16) DO
IF Basics.BITAND[map.bits[i], Bits[j]] = 0 THEN {
We seem to have a tune header that is free. Rewrite the map page to mark the tune as not available, then read in the tune descriptor to make sure it really is available. If not, just keep on trying.
map.bits[i] ← Basics.BITOR[map.bits[i], Bits[j]];
self.window.base ← mapPage;
CopyOut[mapSpace, self.window];
tuneId ← (mapPage-FirstMapPageNumber)*16*bitMapSize +
i*16 + j;
self.window.base ← desPage[self, tuneId];
CopyIn[tuneSpace, self.window];
IF tune.magic # magicTuneHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
IF tune.state = available THEN GOTO gotTune;
};
ENDLOOP;
};
ENDLOOP;
mapPage ← mapPage+1;
IF mapPage > self.maxMapPageNumber THEN mapPage ← FirstMapPageNumber;
IF mapPage = self.currentMapPageNumber THEN ERROR Error[NoTunesAvailable, "All tune headers in use."];
self.window.base ← mapPage;
CopyIn[mapSpace, self.window];
IF map.magic # magicTuneBitMap THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
REPEAT
gotTune => self.currentMapPageNumber ← mapPage;
ENDLOOP;
};
Now we REALLY have a tune. Initialize the header, write it out again, and link it into the main-memory list.
tune.state ← inUse;
tune.runData ← NIL;
tune.createDate ← tune.appendDate ← tune.playDate ← BasicTime.Now[];
tune.size ← 0;
FOR i: INTEGER IN [0..nHdrChirps) DO
tune.chirps[i] ← 0;
ENDLOOP;
tune.indirectChirp ← tune.deepChirp ← 0;
tune.tuneId ← tuneId;
tune.openCount ← 1;
tune.next ← self.openTuneHeaderList;
tune.space ← tuneSpace;
tune.writable ← TRUE;
tune.kill ← FALSE;
tune.deepCache ← tune.indirectCache ← NIL;
self.window.base ← desPage[self, tuneId];
CopyOut[tuneSpace, self.window];
self.openTuneHeaderList ← tune;
RETURN [tune];
}};
DeleteTune: PUBLIC ENTRY PROC [self: Jukebox.Handle, tuneId: INT] = {
ENABLE UNWIND => NULL;
tuneSpace: VM.Interval;
tune: Tune ← NIL;
{ENABLE UNWIND => {
IF tune # NIL THEN VM.Free[tuneSpace];
};
IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg];
IF (tuneId >= self.hdr.nTunes) OR (tuneId < 0) THEN ERROR Error[BadTune, BadTuneErrorMsg];
tuneSpace ← VM.Allocate[count: 1];
Find and validate the tune. Check on the open list before going to disk.
tune ← searchTuneCache[self, tuneId];
IF tune = NIL THEN {
The tune isn't in memory. Get it from disk.
tune ← LOOPHOLE[VM.AddressForPageNumber[tuneSpace.page]];
self.window.base ← desPage[self, tuneId];
CopyIn[tuneSpace, self.window];
tune.tuneId ← tuneId;
tune.openCount ← 0;
tune.next ← NIL;
tune.space ← tuneSpace;
tune.writable ← tune.kill ← FALSE;
};
We've got the tune. Make sure its create date matches and that its magic number hasn't gotten trashed.
IF tune.magic # magicTuneHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
IF tune.createDate # createDateTHEN ERROR Error[BadTune, BadTuneErrorMsg];
IF tune.state # inUse THEN ERROR Error[BadTune, BadTuneErrorMsg];
Mark the tune to be killed. If no-one is using the tune, then deallocate it.
tune.kill ← TRUE;
IF tune.openCount = 0 THEN killTune[self, tune];
VM.Free[tuneSpace];
}};
killTune: INTERNAL PROC [self: Jukebox.Handle, tune: Tune, updateMap: BOOLTRUE] = {
This procedure will release all the chirp space in a tune, mark the disk copy of the header as available, and update the bit map. (The bit map is only updated if updateMap is TRUE)
mapSpace: VM.Interval;
map: LONG POINTER TO TuneBitMap ← NIL;
deepSpace: VM.Interval;
deepList: LONG POINTER TO ChirpList ← NIL;
word, bit: INTEGER;
{ENABLE UNWIND => IF map # NIL THEN VM.Free[mapSpace];
Mark the header as available and write it out.
tune.state ← available;
tune.openCount ← 0;
tune.kill ← FALSE;
self.window.base ← desPage[self, tune.tuneId];
CopyOut[tune.space, self.window];
Release all of the blocks in the tune.
FOR i: INTEGER IN [0..nHdrChirps) DO
IF tune.chirps[i] # 0 THEN FreeChirp[self, tune.chirps[i]];
ENDLOOP;
IF tune.indirectChirp # 0 THEN FreeIndirect[self, tune.indirectChirp];
IF tune.deepChirp # 0 THEN {
ENABLE UNWIND =>
IF deepList # NIL THEN VM.Free[deepSpace];
deepSpace ← VM.Allocate[count: pagesPerChirp];
deepList ← LOOPHOLE[VM.AddressForPageNumber[deepSpace.page]];
self.window.base ← tune.deepChirp;
CopyIn[space: deepSpace, window: self.window];
IF deepList.magic # magicDeepList
THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
FOR i: INTEGER IN [0..chirpListSize) DO
IF deepList.chirps[i] # 0 THEN FreeIndirect[self, deepList.chirps[i]];
ENDLOOP;
FreeChirp[self, tune.deepChirp];
VM.Free[deepSpace];
};
IF NOT updateMap THEN RETURN;
Now update the tune bit map to show this tune as available.
mapSpace ← VM.Allocate[count: 1];
map ← LOOPHOLE[VM.AddressForPageNumber[mapSpace.page]];
self.window.base ← FirstMapPageNumber + tune.tuneId/(bitMapSize*16);
CopyIn[mapSpace, self.window];
word ← SHORT[tune.tuneId MOD (bitMapSize*16)];
bit ← word MOD 16;
word ← word/16;
map.bits[word] ← Basics.BITAND[map.bits[word], Basics.BITNOT[Bits[bit]]];
CopyOut[mapSpace, self.window];
VM.Free[mapSpace];
}};
OpenTune: PUBLIC ENTRY PROC [self: Jukebox.Handle, tuneId: INT, write: BOOL] RETURNS [Tune] = {
ENABLE UNWIND => NULL;
tune: Tune ← NIL;
tuneSpace: VM.Interval;
{ENABLE UNWIND => {
IF tune # NIL THEN VM.Free[tuneSpace];
};
IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg];
Convert the user tune id into a page number that we can use internally, and make sure that it's valid.
IF (tuneId >= self.hdr.nTunes) OR (tuneId < 0) THEN ERROR Error[BadTune, BadTuneErrorMsg];
tuneSpace ← VM.Allocate[count: 1];
First, go through the list of open tunes to see if this one is already open. If so, just use the open tune. Note that a tune is permitted to be writable by at most one client at a time.
tune ← searchTuneCache[self, tuneId];
IF tune # NIL THEN {
IF tune.createDate # createDate THEN ERROR Error[BadTune, BadTuneErrorMsg];
IF tune.kill THEN ERROR Error[BadTune, BadTuneErrorMsg];
IF tune.writable OR write THEN ERROR Error[TuneLocked, "Tune is locked."];
tune.openCount ← tune.openCount + 1;
VM.Free[tuneSpace];
tune.runData ← NIL;
RETURN [tune];
};
The tune isn't in memory. Check its number, and then get it from disk.
tune ← LOOPHOLE[VM.AddressForPageNumber[tuneSpace.page]];
self.window.base ← desPage[self, tuneId];
CopyIn[tuneSpace, self.window];
IF tune.magic # magicTuneHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
IF tune.createDate # createDate THEN ERROR Error[BadTune. BadTuneErrorMsg];
IF tune.state # inUse THEN ERROR Error[BadTune, BadTuneErrorMsg];
tune.tuneId ← tuneId;
tune.openCount ← 1;
tune.next ← self.openTuneHeaderList;
tune.space ← tuneSpace;
tune.writable ← write;
tune.kill ← FALSE;
tune.deepCache ← tune.indirectCache ← NIL;
self.openTuneHeaderList ← tune;
tune.runData ← NIL;
RETURN [tune];
}};
CloseTune: PUBLIC ENTRY PROC [self: Jukebox.Handle, tune: Tune] = {
ENABLE UNWIND => NULL;
InternalCloseTune[self, tune];
};
InternalCloseTune: INTERNAL PROC [self: Jukebox.Handle, tune: Tune] = {
tuneptr: LONG POINTER TO Tune;
tuneSpace: VM.Interval;
IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg];
IF tune.magic # magicTuneHeader THEN ERROR Error[BadTunePointer, BadTunePointerMsg];
First, make sure that the tune is valid.
IF tune.magic # magicTuneHeader THEN ERROR Error[BadTune, BadTuneErrorMsg];
Decrement the open count. If no-one else is using the tune then flush it from the open list and deallocate it if necessary.
tune.openCount ← tune.openCount - 1;
IF tune.openCount = 0 THEN {
IF tune.writable THEN tune.appendDate ← BasicTime.Now[]
ELSE tune.playDate ← BasicTime.Now[];
IF tune.deepCache # NIL THEN {
IF tune.deepCache.dirty THEN {
self.window.base ← tune.deepChirp;
CopyOut[space: tune.deepSpace, window: self.window];
};
VM.Free[tune.deepSpace];
tune.deepCache ← NIL;
};
IF tune.indirectCache # NIL THEN {
IF tune.indirectCache.dirty THEN {
self.window.base ← tune.indirectCache.page;
CopyOut[space: tune.indirectSpace, window: self.window];
};
VM.Free[tune.indirectSpace];
tune.indirectCache ← NIL;
};
tuneSpace ← tune.space;
tuneptr ← @self.openTuneHeaderList;
WHILE tuneptr^ # tune DO
IF tuneptr^ = NIL THEN ERROR Error[Bug, "Unexpected tune list end."];
tuneptr ← @(tuneptr^.next);
ENDLOOP;
tuneptr^ ← tune.next;
Kill the tune as the very last thing, if it is necessary at all.
IF tune.kill THEN killTune[self, tune]
ELSE {
self.window.base ← desPage[self, tune.tuneId];
CopyOut[tuneSpace, self.window];
};
tune.magic ← 0;
IF tune.runData#NIL THEN { VM.Free[tune.runSpace]; tune.runData ← NIL; };
VM.Free[tuneSpace];
};
};
AllocChirp: ENTRY PROC [self: Jukebox.Handle] RETURNS [Jukebox.PageNumber] = {
Allocates a chirp from the free list.
ENABLE UNWIND => NULL;
chirp: Jukebox.PageNumber;
WHILE self.free.nChirps = 0 DO
IF self.free.next = 0 THEN ERROR Error[NoFreeChirps, "No free chirps left."];
self.window.base ← self.currentFreeChirpNumber ← self.free.next;
CopyIn[space: self.freeSpace, window: self.window];
IF self.free.magic # magicFreeList THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
self.hdr.freeListHead ← self.free.next;
self.hdr.nFreeChirps ← self.hdr.nFreeChirps - self.free.nChirps;
self.window.base ← HeaderPageNumber;
CopyOut[space: self.hdrSpace, window: self.window];
ENDLOOP;
self.free.nChirps ← self.free.nChirps-1;
chirp ← self.free.chirps[self.free.nChirps];
RETURN [chirp];
};
AllocateIndirect: PROC [self: Jukebox.Handle, magic: INTEGER] RETURNS [Jukebox.PageNumber] = {
Allocates a chirp from the free list, initializes it as an empty indirect chirp, and saves it on disk again.
page: Jukebox.PageNumber;
space: VM.Interval;
chirpList: LONG POINTER TO ChirpList;
{ENABLE UNWIND => IF chirpList # NIL THEN VM.Free[space];
page ← AllocChirp[self];
space ← VM.Allocate[count: pagesPerChirp];
chirpList ← LOOPHOLE[VM.AddressForPageNumber[space.page]];
chirpList.magic ← magic;
FOR j: INTEGER IN [0..chirpListSize) DO chirpList.chirps[j] ← 0 ENDLOOP;
chirpList.dirty ← FALSE;
self.window.base ← page;
CopyOut[space: space, window: self.window];
VM.Free[space];
RETURN [page];
}};
FreeChirp: PROC [self: Jukebox.Handle, chirp: Jukebox.PageNumber] = {
Returns a chirp to the free list. Note: the caller must make
sure that the monitor lock is held.
ENABLE UNWIND => NULL;
Write out the current free list block if it is full.
IF self.free.nChirps = freeListSize THEN {
self.window.base ← self.currentFreeChirpNumber;
CopyOut[space: self.freeSpace, window: self.window];
self.free.next ← self.currentFreeChirpNumber;
self.free.nChirps ← 0;
Update the jukebox header.
self.hdr.freeListHead ← self.free.next;
self.hdr.nFreeChirps ← self.hdr.nFreeChirps + freeListSize;
self.window.base ← HeaderPageNumber;
CopyOut[space: self.hdrSpace, window: self.window];
};
IF self.free.nChirps = 0 THEN self.currentFreeChirpNumber ← chirp;
self.free.chirps[self.free.nChirps] ← chirp;
self.free.nChirps ← self.free.nChirps+1;
};
FreeIndirect: PROC [self: Jukebox.Handle, page: Jukebox.PageNumber] = {
Returns all of the chirps in an indirect chirp to the free list.
indirectSpace: VM.Interval;
chirpList: LONG POINTER TO ChirpList ← NIL;
{ENABLE UNWIND => IF chirpList # NIL THEN VM.Free[indirectSpace];
indirectSpace ← VM.Allocate[count: pagesPerChirp];
chirpList ← LOOPHOLE[VM.AddressForPageNumber[indirectSpace.page]];
self.window.base ← page;
CopyIn[space: indirectSpace, window: self.window];
IF chirpList.magic # magicChirpList THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
FOR i: INTEGER IN [0..chirpListSize) DO
IF chirpList.chirps[i] # 0 THEN FreeChirp[self, chirpList.chirps[i]];
ENDLOOP;
FreeChirp[self, page];
VM.Free[indirectSpace];
}};
FindChirp: PUBLIC PROC [self: Jukebox.Handle, tune: Tune, chirp: INT, signalMissingChirp: BOOLFALSE, signalEOF: BOOLFALSE] RETURNS [Jukebox.WindowOrigin] = {
Sets a window to correspond to the location of a chirp in a tune. If the chirp falls past the end of the tune and signalEOF is TRUE, then EOF is signalled. If the chirp hasn't been allocated in the file, then a new chirp is allocated (if signalMissingChirp is FALSE) or a MissingChirp is raised (if signalMissingChirp is TRUE).
index, indirect: INTEGER;
chirpWindow: Jukebox.WindowOrigin;
IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg];
IF tune.magic # magicTuneHeader THEN ERROR Error[BadTunePointer, BadTunePointerMsg];
Check for EOF and update the tune's size if necessary.
IF (tune.size <= chirp) THEN {
IF signalEOF THEN ERROR EOF[];
tune.size ← chirp+1;
};
chirpWindow.file ← self.window.file;
IF chirp < nHdrChirps THEN {
index ← SHORT[chirp];
IF tune.chirps[index] = 0 THEN {
IF signalMissingChirp THEN ERROR MissingChirp[];
tune.chirps[index] ← AllocChirp[self];
};
chirpWindow.base ← tune.chirps[index];
RETURN [chirpWindow];
};
chirp ← chirp - nHdrChirps;
index ← SHORT[chirp MOD chirpListSize];
chirp ← chirp/chirpListSize;
IF chirp > chirpListSize THEN ERROR Error[TuneTooLarge, "Can't have tune that large."];
indirect ← SHORT[chirp];
IF (indirect # tune.indirect) OR (tune.indirectCache = NIL) THEN {
Alas, there isn't a cached indirect block to make everything simple. Read in an indirect block (this may also require a deep block to be read as well).
IF tune.indirectCache = NIL THEN {
tune.indirectSpace ← VM.Allocate[count: pagesPerChirp];
tune.indirectCache ← LOOPHOLE[VM.AddressForPageNumber[tune.indirectSpace.page]];
tune.indirectCache.dirty ← FALSE;
tune.indirect ← -1;
};
IF tune.indirectCache.dirty THEN {
tune.indirectCache.dirty ← FALSE;
self.window.base ← tune.indirectCache.page;
CopyOut[space: tune.indirectSpace, window: self.window];
};
IF indirect = 0 THEN {
IF tune.indirectChirp = 0 THEN {
IF signalMissingChirp THEN ERROR MissingChirp[];
tune.indirectChirp ← AllocateIndirect[self, magicChirpList];
};
self.window.base ← tune.indirectChirp;
}
ELSE {
IF (tune.deepCache = NIL) THEN {
Things couldn't be worse: we'll first have to read the deep indirect block, then an indirect block.
IF tune.deepChirp = 0 THEN {
IF signalMissingChirp THEN ERROR MissingChirp[];
tune.deepChirp ← AllocateIndirect[self, magicDeepList];
};
self.window.base ← tune.deepChirp;
tune.deepSpace ← VM.Allocate[count: pagesPerChirp];
tune.deepCache ← LOOPHOLE[VM.AddressForPageNumber[tune.deepSpace.page]];
CopyIn[space: tune.deepSpace, window: self.window];
};
IF tune.deepCache.magic # magicDeepList THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
IF tune.deepCache.chirps[indirect-1] = 0 THEN {
IF signalMissingChirp THEN ERROR MissingChirp[];
tune.deepCache.chirps[indirect-1] ← AllocateIndirect[self, magicChirpList];
tune.deepCache.dirty ← TRUE;
};
self.window.base ← tune.deepCache.chirps[indirect-1];
};
CopyIn[space: tune.indirectSpace, window: self.window];
tune.indirectCache.page ← self.window.base;
tune.indirect ← indirect;
};
IF tune.indirectCache.magic # magicChirpList THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
IF tune.indirectCache.chirps[index] = 0 THEN {
IF signalMissingChirp THEN ERROR MissingChirp[];
tune.indirectCache.chirps[index] ← AllocChirp[self];
tune.indirectCache.dirty ← TRUE;
};
chirpWindow.base ← tune.indirectCache.chirps[index];
RETURN [chirpWindow];
};
FindClientSpace: PUBLIC PROC [self: Jukebox.Handle, tuneId: INT] RETURNS [clientWindow: Jukebox.WindowOrigin] = {
IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg];
IF (tuneId >= self.hdr.nTunes) OR (tuneId < 0) THEN ERROR Error[BadTune, BadTuneErrorMsg];
clientWindow.file ← self.window.file;
clientWindow.base ← desPage[self, tuneId] + 1;
};
desPage: PROC [self: Jukebox.Handle, tuneId: INT] RETURNS [INT] = {
This local procedure just computes the descriptor page for a tune based on its id.
RETURN[self.firstDesPageNumber + 2*tuneId];
};
searchTuneCache: INTERNAL PROC [self: Jukebox.Handle, tuneId: INT] RETURNS [Tune] = {
This local procedure checks to see if a given tune is in our memory cache. If so, then it returns a pointer to the cached copy. If not, then it returns NIL.
tune: Tune;
tune ← self.openTuneHeaderList;
WHILE tune # NIL DO
IF tune.tuneId = tuneId THEN RETURN [tune];
tune ← tune.next;
ENDLOOP;
RETURN [NIL];
};
Scavenge: PUBLIC ENTRY PROC [self: Jukebox.Handle, stream: IO.STREAM, makeFixes: BOOL] RETURNS [nFree: INT, recovered: INT] = {
This routine scans the jukebox to make sure that each chirp is present in at most one place. When a chirp is found both on the free list and in a tune, or if a chirp is found in two separate tunes, a message is output to stream, and one of the instances of the chirp is deleted (if makeFixes is TRUE). If some kind of format error is discovered, a message is output and the relevant tune is deleted (if makeFixes is TRUE). If makeFixes is FALSE then no corrections are made that would cause information to be lost from a tune. In any event, when the scan is finished, the free list is regenerated and reordered. This will recover chirps that got lost. The return values give information about the state of the free list.
ENABLE UNWIND => NULL;
varArray: TYPE = ARRAY [0..0) OF INT;
chirpMap: LONG POINTER TO varArray ← NIL;
chirpSpace: VM.Interval;
tuneId: INT;
tuneSpace, indirectSpace, deepSpace: VM.Interval;
tune: Tune ← NIL;
indirect: LONG POINTER TO ChirpList ← NIL;
deep: LONG POINTER TO ChirpList ← NIL;
index: INT;
oldNFree, newNFree: INT;
badTunes: BOOLFALSE;
unused: INT ← -1;
gcIndex: PROC [chirp: INT] RETURNS [INT] = {
This local procedure merely computes a chirp index from a chirp number. If the chirp is out of range for the jukebox, generate an error.
result: INT;
chirp ← chirp-self.hdr.firstChirp;
result ← chirp/pagesPerChirp;
IF (result >= self.hdr.nChirps) OR (result < 0) THEN ERROR Error[BadChirpPointer, "Bad chirp pointer."];
IF (result*pagesPerChirp) # chirp THEN ERROR Error[BadChirpPointer, "Bad chirp pointer."];
RETURN[result];
};
gcIndirect: PROC [tuneId: INT, chirp: INT, magic: INTEGER] = {
This local procedure checks all the chirps referenced in an indirect block.
index: INT;
IF chirp = 0 THEN RETURN;
self.window.base ← chirp;
CopyIn[space: indirectSpace, window: self.window];
IF indirect.magic # magic THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
FOR i:INTEGER IN [0..chirpListSize) DO
IF indirect.chirps[i] # 0 THEN {
index ← gcIndex[indirect.chirps[i]];
IF chirpMap[index] = unused THEN chirpMap[index] ← tuneId
ELSE {
IO.PutRope[stream, "Indirect/deep pointer is duplicate:\n"];
IO.PutF[stream, " Chirp: %d, 1st tune: %d, 2nd tune: %d.\n",
IO.int[indirect.chirps[i]], IO.int[chirpMap[index]],
IO.int[tuneId]];
IF makeFixes THEN {
IO.PutRope[stream, " Chirp deleted from 2nd tune.\n"];
indirect.chirps[i] ← 0;
CopyOut[space: indirectSpace, window: self.window];
};
};
};
ENDLOOP;
};
{ENABLE UNWIND => {
IF indirect # NIL THEN VM.Free[indirectSpace];
IF deep # NIL THEN VM.Free[deepSpace];
IF tune # NIL THEN VM.Free[tuneSpace];
IF chirpMap # NIL THEN VM.Free[chirpSpace];
};
Main drag of scavenger:
IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg];
IF self.openTuneHeaderList # NIL THEN ERROR Error[TunesOpen, "Can't scavenge: tune(s) open"];
For starters, allocate space for the chirp map, based on the size of the jukebox.
chirpSpace ← VM.Allocate[count: ((self.hdr.nChirps * SIZE[INT]) / VM.wordsPerPage) + 1];
chirpMap ← LOOPHOLE[VM.AddressForPageNumber[chirpSpace.page]];
FOR i: INT IN [0..self.hdr.nChirps) DO chirpMap[i] ← unused ENDLOOP;
Scan all of the tunes in the jukebox. For each tune, scan all the descriptor information, and label each chirp with the tune id.
tuneSpace ← VM.Allocate[count: 1];
tune ← LOOPHOLE[VM.AddressForPageNumber[tuneSpace.page]];
indirectSpace ← VM.Allocate[count: pagesPerChirp];
indirect ← LOOPHOLE[VM.AddressForPageNumber[indirectSpace.page]];
deepSpace ← VM.Allocate[count: pagesPerChirp];
deep ← LOOPHOLE[VM.AddressForPageNumber[deepSpace.page]];
FOR tuneId IN [0..self.hdr.nTunes) DO
If some error occurs underneath us here, output an error message, and discard the tune if that is allowed. If we can't discard the tune, then leave a note not to reconstruct the free list, since there may be valid chirps unseen in the tune.
ENABLE {
Error => {
IO.PutRope[stream, rope];
IO.PutF[stream, " (in tune %d)\n", IO.int[tuneId]];
IF makeFixes THEN {
IO.PutRope[stream, " Tune is being deleted"];
IO.PutRope[stream, " (some free chirps may not be recovered).\n"];
tune.magic ← magicTuneHeader;
tune.state ← available;
self.window.base ← desPage[self, tuneId];
CopyOut[space: tuneSpace, window: self.window];
}
ELSE badTunes ← TRUE;
CONTINUE;
};
ANY => {
IO.PutF[stream, "Unknown error occurred in scavenging tune %d.\n", IO.int[tuneId]];
IF makeFixes THEN {
IO.PutRope[stream, " Tune is being deleted"];
IO.PutRope[stream, " (some free chirps may not be recovered).\n"];
tune.magic ← magicTuneHeader;
tune.state ← available;
self.window.base ← desPage[self, tuneId];
CopyOut[space: tuneSpace, window: self.window];
}
ELSE badTunes ← TRUE;
CONTINUE;
};
};
self.window.base ← desPage[self, tuneId];
CopyIn[space: tuneSpace, window: self.window];
IF tune.magic # magicTuneHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg];
IF tune.state = available THEN LOOP;
Scan top-level chirp pointers.
FOR i:INTEGER IN [0..nHdrChirps) DO
IF tune.chirps[i] # 0 THEN {
index ← gcIndex[tune.chirps[i]];
IF chirpMap[index] = unused THEN chirpMap[index] ← tuneId
ELSE {
IO.PutRope[stream, "Top level pointer is duplicate:\n"];
IO.PutF[stream, " Chirp: %d, 1st tune: %d, 2nd tune: %d.\n",
IO.int[tune.chirps[i]], IO.int[chirpMap[index]],
IO.int[tuneId]];
IF makeFixes THEN {
IO.PutRope[stream, " Chirp deleted from 2nd tune.\n"];
tune.chirps[i] ← 0;
CopyOut[space: tuneSpace, window: self.window];
};
};
};
ENDLOOP;
Scan first-level indirect pointers.
IF tune.indirectChirp # 0 THEN {
index ← gcIndex[tune.indirectChirp];
IF chirpMap[index] = unused THEN chirpMap[index] ← tuneId
ELSE {
IO.PutRope[stream, "Tune.indirectChirp is duplicate:\n"];
IO.PutF[stream, " Chirp: %d, 1st tune: %d, 2nd tune: %d.\n",
IO.int[tune.indirectChirp], IO.int[chirpMap[index]],
IO.int[tuneId]];
IF makeFixes THEN {
IO.PutRope[stream, " Chirp deleted from 2nd tune.\n"];
tune.indirectChirp ← 0;
self.window.base ← desPage[self, tuneId];
CopyOut[space: tuneSpace, window: self.window];
};
};
gcIndirect[tuneId, tune.indirectChirp, magicChirpList];
};
Scan deep pointers.
IF tune.deepChirp # 0 THEN {
index ← gcIndex[tune.deepChirp];
IF chirpMap[index] = unused THEN chirpMap[index] ← tuneId
ELSE {
IO.PutRope[stream, "Tune.deepChirp is duplicate:\n"];
IO.PutF[stream, " Chirp: %d, 1st tune: %d, 2nd tune: %d.\n",
IO.int[tune.deepChirp], IO.int[chirpMap[index]],
IO.int[tuneId]];
IF makeFixes THEN {
IO.PutRope[stream, " Chirp deleted from 2nd tune.\n"];
tune.deepChirp ← 0;
self.window.base ← desPage[self, tuneId];
CopyOut[space: tuneSpace, window: self.window];
};
};
gcIndirect[tuneId, tune.deepChirp, magicDeepList];
self.window.base ← tune.deepChirp;
CopyIn[space: deepSpace, window: self.window];
FOR i: INTEGER IN [0..chirpListSize)
DO gcIndirect[tuneId, deep.chirps[i], magicChirpList] ENDLOOP;
};
ENDLOOP;
If there are bad tunes right away, quit right now without looking at the free list.
IF badTunes THEN {
IO.PutRope[stream, "Bad tunes weren't deleted, so can't scavenge free list.\n"];
RETURN [nFree: self.hdr.nFreeChirps+self.free.nChirps, recovered: 0];
};
Now, scan the free list to make sure that all the blocks on it are currently free.
oldNFree ← 0;
newNFree ← 0;
DO
FOR i:INTEGER IN [0..self.free.nChirps) DO
oldNFree ← oldNFree + 1;
index ← gcIndex[self.free.chirps[i]];
IF chirpMap[index] # unused THEN {
IO.PutRope[stream, "Free list chirp is duplicate:\n"];
IO.PutF[stream, " Chirp: %d, Tune: %d.\n", IO.int[self.free.chirps[i]],
IO.int[chirpMap[index]]];
};
ENDLOOP;
IF self.free.next = 0 THEN EXIT;
self.currentFreeChirpNumber ← self.window.base ← self.free.next;
CopyIn[space: self.freeSpace, window: self.window];
ENDLOOP;
Now we have the whole chirp map set up. Just rewrite the free list in order.
self.free.next ← 0;
self.free.nChirps ← 0;
self.currentFreeChirpNumber ← 0;
self.hdr.nFreeChirps ← 0;
FOR i:INT IN [0..self.hdr.nChirps) DO
IF chirpMap[i] = unused THEN {
newNFree ← newNFree + 1;
FreeChirp[self, self.hdr.firstChirp + i*pagesPerChirp];
};
ENDLOOP;
Release storage and quit.
VM.Free[tuneSpace];
VM.Free[indirectSpace];
VM.Free[deepSpace];
chirpMap ← NIL;
VM.Free[chirpSpace];
RETURN[nFree: newNFree, recovered: newNFree - oldNFree];
}};
CopyIn: PROC [space: VM.Interval, window: Jukebox.WindowOrigin] = {
FS.Read[file: window.file, from: window.base, nPages: space.count, to: VM.AddressForPageNumber[space.page]];
};
CopyOut: PROC [space: VM.Interval, window: Jukebox.WindowOrigin] = {
FS.Write[file: window.file, to: window.base, nPages: space.count, from: VM.AddressForPageNumber[space.page]];
};
END.
Last Edited by: Swinehart, March 1, 1983 2:51 pm
Last Edited by: Stewart, April 19, 1983 3:03 pm, volume stuff
Last Edited by: Stewart, April 20, 1983 12:02 pm, remove use of Heap.systemZone
Last Edited by: Stewart, May 22, 1983 6:32 pm, add FindClientSpace
December 27, 1983 12:42 pm, Stewart, Cedar 5