-- File: SequinImplB.mesa -- Last edited by Levin: 6-Jul-81 15:40:10 -- Loosely derived (after extensive discussions with Wobber) from Butterfield's -- Sequin.mesa of August 27, 1979 2:49 PM. DIRECTORY BufferDefs USING [Buffer, Enqueue, PupBufferObject, ReturnFreeBuffer], ControlDefs USING [FrameHandle], FrameOps USING [GetReturnLink, MyLocalFrame], PupDefs USING [ DequeuePup, EnqueuePup, GetFreePupBuffer, GetPupContentsBytes, PupBuffer, ReturnFreePupBuffer, SetPupContentsBytes], PupTypes USING [ cantGetTherePupErrorCode, eightHopsPupErrorCode, hostDownPupErrorCode, PupType, noProcessPupErrorCode], Sequin USING [Buffer, noBuffer], SequinPrivate USING [Handle, maxAllocate, maxBytes, maxPings, RequeueClosure, Seconds, SequenceNumber, SequinID, SequinControl], Time USING [Current]; SequinImplB: MONITOR LOCKS sequin.LOCK USING sequin: SequinPrivate.Handle IMPORTS BufferDefs, FrameOps, PupDefs, SequinPrivate, Time EXPORTS Sequin, SequinPrivate = BEGIN OPEN PupDefs, Sequin, SequinPrivate; -- Types -- Handle: PUBLIC TYPE = SequinPrivate.Handle; -- Miscellaneous Declarations -- BogusBuffer: ERROR = CODE; ServerIsSpeakingGreek: ERROR = CODE; WhereDidIPutThatBuffer: ERROR = CODE; idleLatency: Seconds = 10; -- Procedures and Signals Exported to Sequin -- Broken: PUBLIC ERROR = CODE; Get: PUBLIC ENTRY PROCEDURE [sequin: Handle] RETURNS [Buffer] = BEGIN packet: PupBuffer; DO IF sequin.broken THEN RETURN WITH ERROR Broken; IF (packet ← DequeuePup[@sequin.getQueue]) ~= NIL THEN EXIT; WAIT sequin.goAhead; ENDLOOP; RETURN[PupToSequinBuffer[packet]] END; GetIfAvailable: PUBLIC ENTRY PROCEDURE [sequin: Handle] RETURNS [Buffer] = BEGIN packet: PupBuffer; IF sequin.broken THEN RETURN WITH ERROR Broken; IF (packet ← DequeuePup[@sequin.getQueue]) = NIL THEN RETURN[noBuffer]; RETURN[PupToSequinBuffer[packet]] END; Put: PUBLIC PROCEDURE [sequin: Handle, buffer: Buffer] = BEGIN packet: PupBuffer = SequinToPupBuffer[buffer]; SetPupContentsBytes[packet, buffer.nBytes]; Send[sequin, packet, data]; END; GetEmptyBuffer: PUBLIC PROCEDURE RETURNS [Buffer] = BEGIN packet: PupBuffer = GetFreePupBuffer[]; SetPupContentsBytes[packet, 0]; RETURN[PupToSequinBuffer[packet]] END; ReleaseBuffer: PUBLIC PROCEDURE [buffer: Buffer] = BEGIN IF buffer = noBuffer THEN RETURN; ReturnFreePupBuffer[SequinToPupBuffer[buffer]]; END; -- Procedures exported to SequinPrivate -- Send: PUBLIC ENTRY PROCEDURE [ sequin: Handle, packet: PupBuffer, control: SequinControl] = BEGIN UNTIL Compare[sequin.retransmitSequence + MIN[sequin.allocate, maxAllocate], sequin.id.sendSequence] = ahead OR sequin.broken DO WAIT sequin.goAhead; ENDLOOP; IF sequin.broken THEN {ReturnFreePupBuffer[packet]; RETURN WITH ERROR Broken}; IF control = data AND sequin.state = init THEN {control ← open; sequin.state ← open}; sequin.id.control ← control; LOOPHOLE[@packet.pupID, POINTER TO SequinID]↑ ← sequin.id; IF GetPupContentsBytes[packet] ~= 0 THEN sequin.id.sendSequence ← sequin.id.sendSequence + 1; packet.pupType ← sequin.pupType; SendRequeueable[sequin, packet]; END; SocketWarmer: PUBLIC PROCEDURE [sequin: Handle] = -- This procedure is forked as a separate process which tries to keep the socket -- as "clean" as possible. It absorbs incoming packets and initiates -- retransmissions as necessary. Good packets are moved to the getQueue. BEGIN packet: PupBuffer; packetID: POINTER TO SequinID; alive: BOOLEAN ← TRUE; ProcessPacket: PROCEDURE [sequin: Handle] RETURNS [alive: BOOLEAN] = INLINE BEGIN alive ← TRUE; IF packet = NIL THEN BEGIN SELECT CheckForPing[sequin] FROM no => NULL; retransmit => Retransmit[sequin]; check => RespondWithNewPacket[check]; dead => RETURN[FALSE]; ENDCASE; RETURN END; IF GetPupContentsBytes[packet] > maxBytes THEN RETURN; SELECT packet.pupType FROM error => BEGIN OPEN PupTypes; SELECT packet.errorCode FROM noProcessPupErrorCode, cantGetTherePupErrorCode, hostDownPupErrorCode, eightHopsPupErrorCode => RETURN[FALSE]; ENDCASE => RETURN; END; sequin.pupType => NULL; ENDCASE => RETURN; packetID ← LOOPHOLE[@packet.pupID]; IF packetID.control = broken THEN RETURN[FALSE]; SELECT Compare[packetID.sendSequence, sequin.id.receiveSequence] FROM duplicate => RETURN; ahead => BEGIN DiscardAckedPackets[sequin]; IF packetID.control = restart THEN Retransmit[sequin]; IF ~sequin.restartRequested THEN BEGIN sequin.restartRequested ← TRUE; RespondWithCurrentPacket[sequin, restart]; END; RETURN END; ENDCASE; -- we've seen everything from the server ResetPinging[]; DiscardAckedPackets[sequin]; SELECT packetID.control FROM data => {EnqueueArrival[sequin]; RespondWithNewPacket[ack]}; dallying => {RespondWithCurrentPacket[sequin, quit]; RETURN[FALSE]}; restart => Retransmit[sequin]; check => RespondWithCurrentPacket[sequin, ack]; ack, nop => NULL; ENDCASE => ERROR ServerIsSpeakingGreek; END; CheckForPing: ENTRY PROCEDURE [sequin: Handle] RETURNS [{no, retransmit, check, dead}] = INLINE BEGIN SELECT sequin.pings FROM 0 => IF Time.Current[] - sequin.lastPacketTime <= idleLatency AND sequin.retransmitQueue.length = 0 THEN RETURN[no]; maxPings => RETURN[dead]; ENDCASE; IF sequin.state = init THEN RETURN[no]; -- don't ping until first real packet sequin.pings ← sequin.pings + 1; sequin.recentRestart ← sequin.restartRequested ← FALSE; RETURN[ IF sequin.retransmitQueue.length = 1 AND sequin.pings = 1 THEN retransmit ELSE check] END; ResetPinging: PROCEDURE = INLINE {sequin.pings ← 0; sequin.lastPacketTime ← Time.Current[]}; DiscardAckedPackets: ENTRY PROCEDURE [sequin: Handle] = BEGIN sequin.allocate ← packetID.allocate; IF Compare[packetID.receiveSequence, sequin.retransmitSequence] = ahead THEN BEGIN skipped: CARDINAL ← 0; UNTIL packetID.receiveSequence = sequin.retransmitSequence OR skipped = sequin.retransmitQueue.length DO b: PupBuffer ← DequeuePup[@sequin.retransmitQueue]; IF b = NIL THEN EXIT; -- buffer hasn't made it to queue yet IF sequin.retransmitSequence ~= LOOPHOLE[b.pupID, SequinID].sendSequence THEN {EnqueuePup[@sequin.retransmitQueue, b]; skipped ← skipped + 1; LOOP}; ReturnFreePupBuffer[b]; sequin.retransmitSequence ← sequin.retransmitSequence + 1; skipped ← 0; ENDLOOP; BROADCAST sequin.goAhead; END; END; EnqueueArrival: ENTRY PROCEDURE [sequin: Handle] = INLINE BEGIN EnqueuePup[@sequin.getQueue, packet]; sequin.id.receiveSequence ← sequin.id.receiveSequence + 1; sequin.recentRestart ← sequin.restartRequested ← FALSE; NOTIFY sequin.goAhead; END; RespondWithNewPacket: PROCEDURE [control: SequinControl] = BEGIN packet ← GetFreePupBuffer[]; packetID ← LOOPHOLE[@packet.pupID]; RespondWithCurrentPacket[sequin, control]; END; RespondWithCurrentPacket: ENTRY PROCEDURE [sequin: Handle, control: SequinControl] = BEGIN sequin.id.control ← control; packetID↑ ← sequin.id; packet.pupType ← sequin.pupType; SetPupContentsBytes[packet, 0]; sequin.socket.put[packet]; packet ← NIL; END; MarkDead: ENTRY PROCEDURE [sequin: Handle] = BEGIN sequin.broken ← TRUE; sequin.state ← destroyed; BROADCAST sequin.goAhead; END; Retransmit: ENTRY PROCEDURE [sequin: Handle] = BEGIN skipped: CARDINAL ← 0; seq: SequenceNumber ← sequin.retransmitSequence; IF sequin.recentRestart THEN RETURN; UNTIL skipped = sequin.retransmitQueue.length DO buffer: PupBuffer = DequeuePup[@sequin.retransmitQueue]; bufferID: POINTER TO SequinID; IF buffer = NIL THEN ERROR WhereDidIPutThatBuffer; IF (bufferID ← LOOPHOLE[@buffer.pupID]).sendSequence ~= seq THEN {EnqueuePup[@sequin.retransmitQueue, buffer]; skipped ← skipped + 1; LOOP}; bufferID.inPart ← sequin.id.inPart; SendRequeueable[sequin, buffer]; sequin.recentRestart ← TRUE; skipped ← 0; seq ← IF seq = LAST[SequenceNumber] THEN 0 ELSE seq + 1; ENDLOOP; END; ResetPinging[]; WHILE alive DO packet ← sequin.socket.get[]; IF ~(alive ← ProcessPacket[sequin]) THEN MarkDead[sequin]; IF packet ~= NIL THEN ReturnFreePupBuffer[packet]; ENDLOOP; END; MakeRequeueClosure: PUBLIC PROCEDURE [sequin: Handle] RETURNS [closure: RequeueClosure] = BEGIN OPEN FrameOps; Return: PROCEDURE [RequeueClosure] ← LOOPHOLE[GetReturnLink[]]; Requeue: PROCEDURE [buffer: BufferDefs.Buffer] = BEGIN DoRequeue: ENTRY PROCEDURE [sequin: Handle] = INLINE BEGIN sequin.buffersToRequeue ← sequin.buffersToRequeue - 1; IF sequin.state = destroyed THEN BEGIN BufferDefs.ReturnFreeBuffer[buffer]; IF sequin.buffersToRequeue = 0 THEN {NOTIFY sequin.goAhead; RETURN}; END ELSE BufferDefs.Enqueue[@sequin.retransmitQueue, buffer]; END; DoRequeue[sequin]; END; sequin.requeuer ← Requeue; Return[MyLocalFrame[]]; -- never gets here; see SequinImplA.Destroy END; -- Internal Procedures -- SendRequeueable: INTERNAL PROCEDURE [sequin: Handle, buffer: PupBuffer] = BEGIN buffer.requeueProcedure ← sequin.requeuer; sequin.buffersToRequeue ← sequin.buffersToRequeue + 1; sequin.socket.put[buffer]; END; Compare: PROCEDURE [a, b: SequenceNumber] RETURNS [{equal, duplicate, ahead}] = BEGIN maxGetAhead: CARDINAL = 128; RETURN[ SELECT TRUE FROM a = b => equal, a > b => IF a <= b + maxGetAhead THEN ahead ELSE duplicate, ENDCASE => IF b <= a + maxGetAhead THEN duplicate ELSE ahead] END; PupToSequinBuffer: PROCEDURE [b: PupBuffer] RETURNS [Buffer] = INLINE {RETURN[[data: LOOPHOLE[@b.pupBody], nBytes: GetPupContentsBytes[b], maxBytes: maxBytes]]}; SequinToPupBuffer: PROCEDURE [b: Buffer] RETURNS [PupBuffer] = BEGIN IF b.data = NIL THEN ERROR BogusBuffer; RETURN[LOOPHOLE[b.data-SIZE[pupBytes BufferDefs.PupBufferObject]]] END; END.