VoiceCleanupImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Doug Terry, November 18, 1986 4:30:43 pm PST
Routines for collecting voice ropes that have no external references to them, i.e. are "garbage". This garbage collection is complicated by the fact that references to voice ropes may reside in arbitrary Tioga documents stored on any Cedar machine.
DIRECTORY
BasicTime USING [Now, Period],
Commander USING [CommandProc, Register],
CommandTool USING [ArgumentVector, Failed, Parse],
IO USING [int, PutF, PutRope, PutChar, rope, STREAM],
Jukebox USING [Handle, DeleteTune, Error, Info, OpenTune, ArchiveCloseTune, Tune],
RecordingServiceRegister USING [database, haveJuke, jukebox],
Rope USING [Equal, Fetch, Length, ROPE],
TuneAccess USING [GetCreateDate, NextTuneNumber],
VoiceRopeDB,
VoiceCleanup;
VoiceCleanupImpl: CEDAR PROGRAM
IMPORTS BasicTime, Commander, CommandTool, IO, Jukebox, RecordingServiceRegister, Rope, TuneAccess, VoiceRopeDB
EXPORTS VoiceCleanup
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
STREAM: TYPE ~ IO.STREAM;
minimumTuneAge: INT ← 300; -- minimum age in seconds
minimumVoiceRopeAge: INT ← 300; -- minimum age in seconds
testing: BOOLEANFALSE; -- when set, things don't actually get deleted
aggressive: BOOLEANTRUE; -- aggressive vertical garbage collection?
RegisteredClass: TYPE ~ REF RegisteredClassRecord;
RegisteredClassRecord: TYPE ~ RECORD[
class: ROPE,
proc: VoiceCleanup.IsGarbageProc
];
classRegistry: LIST OF RegisteredClass ← NIL;
Tunes
This implementation of tune collection assumes that the code runs on the same machine as Bluejay, i.e. the Jukebox and TuneAccess interfaces are locally available.
CollectTunes: PUBLIC PROC [reports: STREAMNIL] RETURNS [] ~ TRUSTED {
The Tune Collector enumerates the complete set of TuneIDs and calls CollectTune for each one.
ENABLE Jukebox.Error => {
IF reports#NIL THEN IO.PutF[reports, "Jukebox error: %g\n", IO.rope[rope]];
CONTINUE;
};
before, after: INT;
db: VoiceRopeDB.Handle ← RecordingServiceRegister.database;
openJukebox: Jukebox.Handle ← RecordingServiceRegister.jukebox;
tune: INT;
IF NOT RecordingServiceRegister.haveJuke THEN {
IF reports#NIL THEN IO.PutRope[reports, "Jukebox not available.\n"];
RETURN;
};
IF db = NIL THEN {
IF reports#NIL THEN IO.PutRope[reports, "Voice rope database not available.\n"];
RETURN;
};
before ← Jukebox.Info[openJukebox].nTunes;
IF reports # NIL THEN IO.PutRope[reports, "Collecting tunes"];
tune ← TuneAccess.NextTuneNumber[jukebox: openJukebox];
WHILE tune # -1 DO
IF reports # NIL THEN IO.PutChar[reports, '.];
[] ← CollectTune[db, tune, reports];
tune ← TuneAccess.NextTuneNumber[jukebox: openJukebox, currentTuneID: tune];
ENDLOOP;
after ← Jukebox.Info[openJukebox].nTunes;
IF reports # NIL THEN
IO.PutF[reports, "%g tunes collected.\n", IO.int[before - after]];
};
CollectTune: PROC [db: VoiceRopeDB.Handle, tune: INT, reports: STREAM] RETURNS [collected: BOOLEANFALSE] ~ TRUSTED {
Queries the VoiceRope database to determine if any voice ropes make use of the tune. If not, then the tune is deleted.
ENABLE {
VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; };
Jukebox.Error => { IF reports#NIL THEN IO.PutF[reports, "Jukebox error: %g\n", IO.rope[rope]]; CONTINUE; };
};
openTune: Jukebox.Tune;
openJukebox: Jukebox.Handle ← RecordingServiceRegister.jukebox;
IF VoiceRopeDB.VoiceRopeContainingTune[db, tune] = NIL THEN {
openTune ← Jukebox.OpenTune[openJukebox, tune, FALSE];
IF BasicTime.Period[from: TuneAccess.GetCreateDate[openTune], to: BasicTime.Now[]] < minimumTuneAge THEN { Jukebox.ArchiveCloseTune[openJukebox, openTune]; RETURN;};
Jukebox.ArchiveCloseTune[openJukebox, openTune];
IF NOT testing THEN
Jukebox.DeleteTune[openJukebox, tune];
IF reports # NIL THEN
IO.PutF[reports, "\nTune garbage: %g\n", IO.int[tune]];
collected ← TRUE;
};
};
Voice ropes
CollectVoiceRopes: PUBLIC PROC [reports: STREAMNIL] RETURNS [] ~ {
The Voice Rope Collector enumerates the VoiceRope database and calls CollectVoiceRope for each entry.
ENABLE VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; };
Internal: VoiceRopeDB.EnumProc ~ {
IF CollectVoiceRope[db, info, reports] THEN
count ← count + 1;
IF reports # NIL THEN IO.PutChar[reports, '.];
};
count: INT ← 0;
db: VoiceRopeDB.Handle ← RecordingServiceRegister.database;
IF db = NIL THEN {
IF reports#NIL THEN IO.PutRope[reports, "Voice rope database not available.\n"];
RETURN;
};
IF reports # NIL THEN IO.PutRope[reports, "Collecting voice ropes"];
VoiceRopeDB.EnumerateVoiceRopes[handle: db, proc: Internal];
IF reports # NIL THEN
IO.PutF[reports, "%g voice ropes collected.\n", IO.int[count]];
};
CollectVoiceRope: PROC [db: VoiceRopeDB.Handle, info: VoiceRopeDB.VoiceRopeInfo, reports: STREAM] RETURNS [collected: BOOLEANFALSE] ~ {
Queries the VoiceInterest database to determine if an interest exists in the voice rope. If not, and the voice rope is older than some period of time, then the entry is deleted from the VoiceRope database. In addition, an aggressive implementation calls CollectTune for each tune component of the voice rope.
ENABLE VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; };
IF VoiceRopeDB.InterestInVoiceRope[db, info.vrID] = NIL AND BasicTime.Period[from: info.timestamp, to: BasicTime.Now[]] > minimumVoiceRopeAge THEN {
IF NOT testing THEN
VoiceRopeDB.DeleteVoiceRope[db, info.vrID];
IF reports # NIL THEN
IO.PutF[reports, "\nVoice rope garbage: %g", IO.rope[info.vrID]];
IF aggressive THEN {
tune: INT;
struct: VoiceRopeDB.TuneList ← info.struct;
UNTIL struct=NIL DO
[tune: tune, rest: struct] ← VoiceRopeDB.NextTuneOnList[struct];
[] ← CollectTune[db, tune, reports];
ENDLOOP;
};
IF reports # NIL THEN IO.PutChar[reports, '\n];
collected ← TRUE;
}
};
Interests
CollectInterests: PUBLIC PROC [class: ROPENIL, reports: STREAMNIL] RETURNS [] ~ {
The Voice Interest Collector enumerates all entries of the specified class in the VoiceInterest database and calls the IsGarbageProc for each one. If this call returns TRUE, then the entry is deleted from the VoiceInterest database. In addition, an aggressive implementation calls CollectVoiceRope for the voice rope referenced in the deleted interest entry.
ENABLE VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; };
Internal: VoiceRopeDB.InterestProc ~ {
IF CollectInterest[db, info, classProc, reports] THEN
count ← count + 1;
IF reports # NIL THEN IO.PutChar[reports, '.];
};
count: INT ← 0;
classProc: VoiceCleanup.IsGarbageProc ← NIL;
db: VoiceRopeDB.Handle ← RecordingServiceRegister.database;
IF db = NIL THEN {
IF reports#NIL THEN IO.PutRope[reports, "Voice rope database not available.\n"];
RETURN;
};
IF class # NIL THEN { -- collecting a single class so lookup garbage proc once
classProc ← LookupClass[class];
IF classProc = NIL THEN {
IF reports # NIL THEN IO.PutF[reports, "No registered garbage procedure for class %g.\n", IO.rope[class]];
RETURN;
};
};
IF reports # NIL THEN IO.PutRope[reports, "Collecting interests"];
VoiceRopeDB.EnumerateInterestClass[handle: db, class: class, proc: Internal];
IF reports # NIL THEN
IO.PutF[reports, "%g interests collected.\n", IO.int[count]];
};
CollectInterest: PROC [db: VoiceRopeDB.Handle, info: VoiceCleanup.InterestInfo, proc: VoiceCleanup.IsGarbageProc ← NIL, reports: STREAM] RETURNS [collected: BOOLEANFALSE] ~ {
ENABLE VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; };
classProc: VoiceCleanup.IsGarbageProc ← proc;
IF proc = NIL THEN { -- lookup garbage proc for the particular class
classProc ← LookupClass[info.class];
IF classProc = NIL THEN {
IF reports # NIL THEN IO.PutF[reports, "No registered garbage procedure for class %g.\n", IO.rope[info.class]];
RETURN;
};
};
IF classProc[info] THEN { -- returns TRUE if info is garbage
IF NOT testing THEN
VoiceRopeDB.DropInterest[db, info];
IF reports # NIL THEN
IO.PutF[reports, "\nInterest garbage: vr=%g, ref=%g", IO.rope[info.vrID], IO.rope[info.refID]];
IF aggressive THEN {
vrEntry: VoiceRopeDB.Header;
vrInfo: VoiceRopeDB.VoiceRopeInfo;
vrEntry ← VoiceRopeDB.ReadVoiceRope[handle: db, ropeID: info.vrID].header;
IF vrEntry # NIL THEN {
vrInfo ← VoiceRopeDB.UnpackHeader[vrEntry];
[] ← CollectVoiceRope[db, vrInfo, reports];
};
};
IF reports # NIL THEN IO.PutChar[reports, '\n];
collected ← TRUE;
};
};
RegisterClass: PUBLIC PROC [class: ROPE, proc: VoiceCleanup.IsGarbageProc] RETURNS [] ~ {
Registers an IsGarbageProc to be used when garbage collecting interests of the given class.
data: RegisteredClass;
FOR l: LIST OF RegisteredClass ← classRegistry, l.rest WHILE l # NIL DO
IF l.first.class = class THEN { -- new proc for a previously registered class
l.first.proc ← proc;
RETURN;
}
ENDLOOP;
data ← NEW[RegisteredClassRecord ← [class, proc]];
classRegistry ← CONS[data, classRegistry];
};
LookupClass: PROC [class: ROPE] RETURNS [proc: VoiceCleanup.IsGarbageProc ← NIL] ~ {
Returns the IsGarbageProc registered for the given class; NIL if none exists.
FOR l: LIST OF RegisteredClass ← classRegistry, l.rest WHILE l # NIL DO
IF Rope.Equal[s1: l.first.class, s2: class, case: FALSE] THEN RETURN[l.first.proc];
ENDLOOP;
};
CommandTool commands
silent: BOOLEANFALSE; -- turns off reporting
ParseSwitches: PROC [argv: CommandTool.ArgumentVector] RETURNS [] ~ {
arg: ROPE;
FOR i: NAT IN [1..argv.argc) DO
arg ← argv[i];
IF Rope.Length[arg] = 0 THEN LOOP;
IF Rope.Fetch[arg, 0] = '- THEN {
FOR index: INT IN [1..Rope.Length[arg]) DO
SELECT Rope.Fetch[arg, index] FROM
't, 'T => testing ← TRUE; -- testing mode
'g, 'G => testing ← FALSE; -- really collect bad entries
'r, 'R => silent ← FALSE; -- turn on reporting
's, 'S => silent ← TRUE; -- turn off reporting
ENDCASE;
ENDLOOP;
};
ENDLOOP;
};
NextNonSwitch: PROC [argv: CommandTool.ArgumentVector, start: NAT] RETURNS [arg: ROPE, p: NAT] ~ {
FOR i: NAT IN [start..argv.argc) DO
arg ← argv[i];
IF Rope.Length[arg] = 0 THEN LOOP;
IF Rope.Fetch[arg, 0] # '- THEN RETURN[arg, i];
ENDLOOP;
RETURN[NIL, argv.argc];
};
TuneProc: Commander.CommandProc = {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
ParseSwitches[argv];
IF testing THEN IO.PutRope[cmd.out, "Test mode:\n"];
CollectTunes[reports: IF silent THEN NIL ELSE cmd.out];
IF testing THEN IO.PutRope[cmd.out, "No garbage actually collected.\n"];
EXITS
failed => {result ← $Failure};
};
VoiceRopeProc: Commander.CommandProc = {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
ParseSwitches[argv];
IF testing THEN IO.PutRope[cmd.out, "Test mode:\n"];
CollectVoiceRopes[reports: IF silent THEN NIL ELSE cmd.out];
IF testing THEN IO.PutRope[cmd.out, "No garbage actually collected.\n"];
EXITS
failed => {result ← $Failure};
};
InterestProc: Commander.CommandProc = {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
class: ROPE;
i: NAT;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
ParseSwitches[argv];
[class, i] ← NextNonSwitch[argv, 1];
IF testing THEN IO.PutRope[cmd.out, "Test mode:\n"];
CollectInterests[class: class, reports: IF silent THEN NIL ELSE cmd.out];
IF testing THEN IO.PutRope[cmd.out, "No garbage actually collected.\n"];
EXITS
failed => {result ← $Failure};
};
Initialization
Commander.Register[key: "CollectVoiceInterests", proc: InterestProc, doc: "Garbage collects interests in voice ropes.\n CollectVoiceInterests {-t | -g} <class>"];
Commander.Register[key: "CollectVoiceRopes", proc: VoiceRopeProc, doc: "Garbage collects voice ropes that are no longer of interest.\n CollectVoiceRopes {-t | -g}"];
Commander.Register[key: "CollectTunes", proc: TuneProc, doc: "Garbage collects unused bluejay tunes.\n CollectTunes {-t | -g}"];
END.
Doug Terry, June 6, 1986 5:20:27 pm PDT
Created to do garbage collection of voice ropes.
changes to: DIRECTORY, VoiceCleanupImpl, IMPORTS, EXPORTS, ~, CollectTunes, CollectTune, CollectVoiceRopes, CollectVoiceRope, CollectInterests, CollectInterest, END
Doug Terry, June 9, 1986 4:07:26 pm PDT
changes to: DIRECTORY, ~, CollectTunes, CollectTune, NoVoiceRope, CollectVoiceRopes, Internal (local of CollectVoiceRopes), CollectVoiceRope, NoInterest, Internal (local of CollectInterests), CollectInterests
Doug Terry, June 13, 1986 1:48:05 pm PDT
Added class registration and feedback; upgraded to new VoiceRopeDB interface.
changes to: ~, CollectTunes, NoVoiceRope, CollectVoiceRopes, CollectInterests, RegisterClass, Open, CollectTune, Internal (local of CollectVoiceRopes), CollectVoiceRope, Internal (local of CollectInterests), RegisterClass, LookupClass
Doug Terry, June 15, 1986 10:12:06 pm PDT
Added CommandTool commands.
changes to: ~, RegisterClass, LookupClass, ParseSwitchesAndArg, TuneProc, VoiceRopeProc, InterestProc, Commander, Commander, Commander, DIRECTORY, IMPORTS, Internal (local of CollectInterests), CollectInterests, CollectInterest, Open, CollectVoiceRope, CollectTunes, CollectTune
Doug Terry, June 16, 1986 4:15:17 pm PDT
changes to: LookupClass, TuneProc, VoiceRopeProc, InterestProc, DIRECTORY, Open, CollectTune, Internal (local of CollectVoiceRopes), CollectVoiceRope, Internal (local of CollectInterests), CollectInterest
Doug Terry, June 26, 1986 5:02:04 pm PDT
changes to: Open, CollectTunes, CollectVoiceRopes, CollectInterests, DIRECTORY, IMPORTS
Doug Terry, June 27, 1986 3:26:27 pm PDT
Added argument to CommandTool commands for specifying a database name.
changes to: ParseSwitches, NextNonSwitch, TuneProc, VoiceRopeProc, InterestProc, Commander, Commander, Commander, ParseSwitches
Doug Terry, June 30, 1986 1:42:43 pm PDT
changes to: CollectInterest
Doug Terry, November 18, 1986 2:25:20 pm PST
Uses RecordingServiceRegister to obtain open Jukebox and VoiceRopeDB handles.
changes to: DIRECTORY, IMPORTS, ~, CollectTunes, CollectTune, Open