-- LockWatchdogImpl.mesa
-- Last edited by
-- MBrown on January 30, 1984 5:41:50 pm PST
-- NOTES
-- Some care has been taken to make these procedures efficient in the (normal) case that
-- waiting happens rarely, and the total number of waiting requests is small. This
-- allows us to check more frequently, which improves response in cases when the wait
-- can be resolved right away (deadlock and inactive blocking transaction).
DIRECTORY
AlpineEnvironment,
AlpineInternal,
BasicTime,
List,
Lock,
LockControl,
LockInternal,
Process,
SafeStorage,
TransactionMap;
LockWatchdogImpl: PROGRAM
IMPORTS
BasicTime,
List,
Lock,
LockInternal,
Process,
SafeStorage,
TransactionMap
EXPORTS
AlpineInternal,
LockControl
= BEGIN
LockID: TYPE = Lock.LockID;
nullLockID: LockID = Lock.nullLockID;
LockMode: TYPE = Lock.LockMode;
ModeReleasableSet: TYPE = Lock.ModeReleasableSet;
Handle: TYPE = LockInternal.Handle;
Object: TYPE = LockInternal.Object;
HeaderHandle: TYPE = LockInternal.HeaderHandle;
RequestHandle: TYPE = LockInternal.RequestHandle;
GrantedRequestHandle: TYPE = LockInternal.GrantedRequestHandle;
WaitingRequestHandle: TYPE = LockInternal.WaitingRequestHandle;
LockTransHeaderHandle: TYPE = LockInternal.LockTransHeaderHandle;
LockTransHeaderObject: PUBLIC TYPE = LockInternal.Object.request.transHeader;
-- AlpineInternal.LockTransHeaderObject
z: ZONE ← SafeStorage.GetSystemZone[];
ForkWatchdogProcess: PUBLIC PROC [
wakeupPeriod: Process.Milliseconds,
abortWaitingRequestInterval: INT--seconds--,
abortInactiveGrantedRequestInterval: INT--seconds--] = {
-- LockControl.ForkWatchdogProcess.
Process.Detach[FORK LockWatchdogProcess[
wakeupPeriod, abortWaitingRequestInterval, abortInactiveGrantedRequestInterval]];
};
LockWatchdogProcess: PROC [
wakeupPeriod: Process.Milliseconds,
abortWaitingRequestInterval: INT--seconds--,
abortInactiveGrantedRequestInterval: INT--seconds--] = {
wakeupPeriodsUntilTimeoutCheck: INT ← 0;
numWaits: INT ← 0;
p: Path ← NIL;
DO
Process.Pause[Process.MsecToTicks[wakeupPeriod] ! ABORTED => GOTO aborted];
-- For each waiting request, abort its transaction if it has waited for more than
-- abortWaitingRequestInterval seconds. (Don't bother to check for timed-out
-- requests unless a request might be timed out, based on information obtained
-- on the last check.)
IF (wakeupPeriodsUntilTimeoutCheck ← wakeupPeriodsUntilTimeoutCheck-1) <= 0 THEN {
l: LIST OF TransactionMap.Handle;
[wakeupPeriodsUntilTimeoutCheck, l] ←
ChooseTimeoutVictims[wakeupPeriod, abortWaitingRequestInterval];
UNTIL l = NIL DO
lNext: LIST OF TransactionMap.Handle ← l.rest;
TransactionMap.AbortUnilaterally[l.first, timeout];
Process.Yield[];
FREE[@l];
l ← lNext;
ENDLOOP;
};
-- For each waiting request, examine the first transaction holding a lock that
-- prevents the request from being satisfied. If this transaction has done no work
-- in the last abortInactiveGrantedRequestInterval seconds, abort it. Repeat until
-- no transaction is aborted by this test.
Process.Yield[];
DO
l: LIST OF TransactionMap.Handle ← GetBlockingTransactions[];
currentTime: BasicTime.GMT ← BasicTime.Now[];
noAbortDone: BOOL ← TRUE;
UNTIL l = NIL DO
lNext: LIST OF TransactionMap.Handle ← l.rest;
elapsedTimeSinceLastStartWork: INT ← BasicTime.Period[
from: TransactionMap.GetTimeOfLastStartWork[l.first], to: currentTime];
IF elapsedTimeSinceLastStartWork > abortInactiveGrantedRequestInterval THEN {
TransactionMap.AbortUnilaterally[l.first, blockingNewLockRequest];
Process.Yield[];
noAbortDone ← FALSE;
};
FREE[@l];
l ← lNext;
ENDLOOP;
IF noAbortDone THEN EXIT;
ENDLOOP;
-- Compute a graph whose vertices are transactions with one or more waiting lock
-- requests, containing a directed edge from t1 to t2 if t2 holds a lock that prevents
-- a waiting lock request or t1 from being satisfied. (Don't bother to compute the
-- graph if no lock requests have waited since the last time the graph was computed.)
-- If the graph contains a cycle, find a cycle and abort the lowest-cost transaction
-- in the cycle. Repeat until the graph contains no cycle.
Process.Yield[];
{
g: WaitingForGraph;
[numWaits, g] ← ComputeWaitingForGraph[numWaits];
IF g # NIL THEN {
DO
t: TransactionMap.Handle;
p ← FindCycle[g, p];
IF (t ← ChooseVictim[p]) = NIL THEN GOTO freeGraph;
TransactionMap.AbortUnilaterally[t, deadlock];
Process.Yield[];
g ← EliminateTrans[g, t];
REPEAT
freeGraph => FreeGraph[g];
ENDLOOP;
};
};
ENDLOOP;
EXITS
aborted => NULL
};
-- Support for timeout and blockingNewLockRequest detection.
ChooseTimeoutVictims: PROC [
wakeupPeriod: INT--msec--, abortWaitingRequestInterval: INT--seconds--]
RETURNS [wakeupPeriodsUntilTimeoutCheck: INT, l: LIST OF TransactionMap.Handle] = {
currentTime: BasicTime.GMT = BasicTime.Now[];
secondsToNextWakeup: INT ← abortWaitingRequestInterval;
NoticeWaitingRequest: PROC [wr: WaitingRequestHandle] RETURNS [stop: BOOL] = {
elapsedTimeSinceWait: INT--seconds-- =
BasicTime.Period[from: wr.startTime, to: currentTime];
IF elapsedTimeSinceWait >= abortWaitingRequestInterval THEN {
IF NOT List.Memb[wr.trans, LOOPHOLE[l]] THEN l ← z.CONS[first: wr.trans, rest: l];
}
ELSE {
secondsToNextWakeup ← MIN[
secondsToNextWakeup, abortWaitingRequestInterval-elapsedTimeSinceWait];
};
RETURN [stop: FALSE];
};
l ← NIL;
LockInternal.GetInfo[waitingRequestEnumProc: NoticeWaitingRequest];
RETURN [(secondsToNextWakeup*1000 + wakeupPeriod-1)/wakeupPeriod, l];
};
GetBlockingTransactions: PROC [] RETURNS [l: LIST OF TransactionMap.Handle] = {
NoticeWaitingRequest: PROC [wr: WaitingRequestHandle] RETURNS [stop: BOOL] = {
-- INTERNAL to LockCoreImpl
m: LockMode = ModeAfterGrantingRequest[wr];
FOR h: Handle ← wr.requestList, h.requestList UNTIL h=wr DO
WITH h SELECT FROM
grh: GrantedRequestHandle =>
IF grh.trans # wr.trans AND NOT Lock.Compat[m][grh.mode]
AND NOT List.Memb[grh.trans, LOOPHOLE[l]] THEN
l ← z.CONS[first: grh.trans, rest: l];
ENDCASE;
ENDLOOP;
RETURN [stop: FALSE];
};
l ← NIL;
LockInternal.GetInfo[waitingRequestEnumProc: NoticeWaitingRequest];
RETURN [l];
};
ModeAfterGrantingRequest: PROC [wr: WaitingRequestHandle] RETURNS [LockMode] = {
-- INTERNAL to LockCoreImpl
FOR h: Handle ← wr.requestList, h.requestList UNTIL h=wr DO
WITH h SELECT FROM
grh: GrantedRequestHandle =>
IF grh.trans = wr.trans THEN RETURN[Lock.Sup[wr.mode][grh.mode]];
ENDCASE;
ENDLOOP;
RETURN [wr.mode];
};
-- Support for deadlock detection.
WaitingTransHandle: TYPE = REF WaitingTransObject;
WaitingTransObject: TYPE = RECORD [
trans: TransactionMap.Handle,
edges: LIST OF--WaitingTransHandle--REF ANY,
visited: BOOL ← FALSE,
onPath: BOOL ← FALSE
];
WaitingForGraph: TYPE = LIST OF--WaitingTransHandle--REF ANY;
ComputeWaitingForGraph: PROC [numWaits: INT]
RETURNS [newNumWaits: INT, g: WaitingForGraph] = {
-- If numWaits equals the number of lock waits reported by the lock manager,
--return newNumWaits = numWaits, g = NIL. Otherwise compute the
--current waiting-for graph, and return it as g.
NoticeGeneralInfo: LockInternal.GeneralInfoProc = {
newNumWaits ← nSetCallsWaited;
};
NoticeWaitingRequest: PROC [wr: WaitingRequestHandle] RETURNS [stop: BOOL] = {
-- If there are new waiters, add this transaction to the list of waiting transactions.
IF newNumWaits = numWaits THEN RETURN [stop: TRUE]
ELSE {
IF (LookupTrans[g, wr.trans] = NIL) THEN {
wt: WaitingTransHandle ← z.NEW[WaitingTransObject ← [trans: wr.trans, edges: NIL]];
g ← z.CONS[first: wt, rest: g];
};
RETURN [stop: FALSE];
};
};
NoticeWaitingRequest2: PROC [wr: WaitingRequestHandle] RETURNS [stop: BOOL] = {
-- If there are new waiters, create an edge for each transaction blocking this request.
IF newNumWaits = numWaits THEN RETURN [stop: TRUE]
ELSE {
waiting: WaitingTransHandle = LookupTrans[g, wr.trans];
m: LockMode = ModeAfterGrantingRequest[wr];
FOR h: Handle ← wr.requestList, h.requestList UNTIL h =wr DO
WITH h SELECT FROM
grh: GrantedRequestHandle =>
IF grh.trans # wr.trans AND NOT Lock.Compat[m][grh.mode] THEN {
granted: WaitingTransHandle ← LookupTrans[g, grh.trans];
IF granted # NIL AND NOT List.Memb[granted, waiting.edges] THEN
waiting.edges ← z.CONS[first: granted, rest: waiting.edges];
};
ENDCASE;
ENDLOOP;
RETURN [stop: FALSE];
};
};
g ← NIL;
LockInternal.GetInfo[
generalInfoProc: NoticeGeneralInfo,
waitingRequestEnumProc: NoticeWaitingRequest,
waitingRequestEnumProc2: NoticeWaitingRequest2];
RETURN [newNumWaits, g];
};
PathRecord: TYPE = RECORD [
length: NAT ← 0,
vertices: SEQUENCE maxLength: NAT OF RECORD [
waitingTrans: WaitingTransHandle,
nextEdge: LIST OF--WaitingTransHandle--REF ANY
]
];
Path: TYPE = REF PathRecord;
FindCycle: PROC [g: WaitingForGraph, oldPath: Path] RETURNS [path: Path] = {
-- If g contains a cycle, returns a cycle as path; returns path.length = 0 if
--g contains no cycle. Reuses oldPath's storage if it can.
top: LIST OF REF ANY ← g;
pathLast: NAT ← 0;
v: WaitingTransHandle;
pathMaxLength: NAT = List.Length[g];
path ← IF oldPath # NIL AND oldPath.length >= pathMaxLength THEN oldPath
ELSE z.NEW[PathRecord[pathMaxLength]];
DO
{ -- EXITS NextTopLevelVertex
v ← NARROW[top.first];
IF v.visited THEN GOTO NextTopLevelVertex;
DO
-- Add the new vertex v to the end of the path.
path[pathLast] ← [waitingTrans: v, nextEdge: v.edges];
v.visited ← v.onPath ← TRUE;
DO
-- Explore the next edge out of the vertex at the end of the path.
e: LIST OF REF ANY ← path[pathLast].nextEdge;
IF e = NIL THEN {
-- Remove a vertex from the end of the path.
path[pathLast].waitingTrans.onPath ← FALSE;
IF pathLast = 0 THEN GOTO NextTopLevelVertex;
pathLast ← pathLast - 1;
}
ELSE {
path[pathLast].nextEdge ← e.rest;
v ← NARROW[e.first];
IF v.onPath THEN GOTO FoundCycle;
IF NOT v.visited THEN {
-- Add v to the end of the path.
pathLast ← pathLast + 1;
EXIT;
};
};
ENDLOOP;
ENDLOOP;
EXITS
NextTopLevelVertex => IF (top ← top.rest) = NIL THEN {
path.length ← 0;
GOTO done;
};
FoundCycle => {
-- Move vertices of cycle down to start of sequence, clean up rest, and return.
cycleStart: NAT;
FOR cycleStart ← 0, cycleStart+1
UNTIL path[cycleStart].waitingTrans = v DO ENDLOOP;
FOR j: NAT IN [cycleStart .. pathLast] DO
path[j-cycleStart] ← [path[j].waitingTrans, NIL];
ENDLOOP;
path.length ← pathLast-cycleStart+1;
GOTO done;
};
};
ENDLOOP;
EXITS done =>
FOR j: NAT IN [path.length .. path.maxLength) DO
path[j] ← [NIL, NIL];
ENDLOOP;
};
ChooseVictim: PROC [p: Path] RETURNS [TransactionMap.Handle] = {
-- Transactions in p are deadlocked. Choose one to be aborted.
-- Return NIL iff p is empty.
minUpdateCost: INT ← LAST[INT];
minUpdateCostTrans: TransactionMap.Handle ← NIL;
FOR i: NAT IN [0 .. p.length) DO
updateCost: INT ← TransactionMap.GetEstimatedUpdateCost[p[i].waitingTrans.trans];
IF updateCost < minUpdateCost OR minUpdateCostTrans = NIL THEN {
minUpdateCost ← updateCost;
minUpdateCostTrans ← p[i].waitingTrans.trans;
};
ENDLOOP;
RETURN [minUpdateCostTrans];
};
LookupTrans: PROC [g: WaitingForGraph, t: TransactionMap.Handle]
RETURNS [wt: WaitingTransHandle] = {
FOR l: LIST OF REF ANY ← g, l.rest UNTIL l = NIL DO
wt: WaitingTransHandle ← NARROW[l.first];
IF wt.trans = t THEN RETURN [wt];
ENDLOOP;
RETURN [NIL];
};
EliminateTrans: PROC [g: WaitingForGraph, t: TransactionMap.Handle]
RETURNS [newG: WaitingForGraph]= {
-- Compute a new graph, eliminating t. Mark all vertices unvisited and not on path.
wt: WaitingTransHandle ← LookupTrans[g, t];
FreeList[wt.edges]; wt.edges ← NIL;
FOR l: LIST OF REF ANY ← g, l.rest UNTIL l = NIL DO
w: WaitingTransHandle ← NARROW[l.first];
w.visited ← w.onPath ← FALSE;
w.edges ← List.DRemove[wt, w.edges];
ENDLOOP;
RETURN [List.DRemove[wt, g]];
};
FreeGraph: PROC [g: WaitingForGraph] = {
-- NILs things out (superstition).
FOR l: LIST OF REF ANY ← g, l.rest UNTIL l = NIL DO
wt: WaitingTransHandle ← NARROW[l.first];
FreeList[wt.edges];
FREE[@wt];
ENDLOOP;
FreeList[g];
};
FreeList: PROC [l: LIST OF REF ANY] = {
UNTIL l = NIL DO
lNext: LIST OF REF ANY ← l.rest;
FREE[@l];
l ← lNext;
ENDLOOP;
};
END.--LockWatchdogImpl
CHANGE LOG
Changed by MBrown on February 6, 1983 10:03 pm
-- Bug in GetBlockingTransactions: always returned the waiting transaction instead of the
--blocking transaction. This meant wait => abort nearly every time.