-- RPC: Call-oriented packet streams, based on PktExchange -- RPCPktStreams.mesa -- Andrew Birrell March 11, 1983 3:49 pm DIRECTORY BufferDefs USING[ PupBuffer ], Frame USING[ GetReturnLink, MyLocalFrame ], Heap USING[ systemZone ], Inline USING[ BITAND, BITXOR, LongCOPY ], ProcessOperations USING[ HandleToIndex, ReadPSB ], PSB USING[ PsbIndex, PsbNull ], PupDefs USING[ GetFreePupBuffer, GetLocalPupAddress, PupRouterSendThis, ReturnFreePupBuffer], PupTypes USING[ PupAddress, PupHostID, PupNetID, PupSocketID ], MesaRPC USING[ CallFailed, Conversation, unencrypted ], RPCInternal USING[ ConversationObject, DecryptPkt, EncryptPkt, exportTable, firstConversation, GetConnectionState, ImportInstance ], RPCLupine USING[ DataLength, Dispatcher, GetRPCPkt, Header, maxDataLength, maxPupWords, pktOverhead, RPCPkt ], RPCPkt USING[ CallCount, ConnectionID, DispatcherDetails, EnqueueAgain, IdleReceive, Header, Machine, noDispatcher, Outcome, PktExchange, pktLengthOverhead, SetupResponse ], RPCPrivate USING[ rpcSocket, ReturnBuffer ], Runtime USING[ IsBound ], Space USING[ Create, Delete, Handle, LongPointer, Map, virtualMemory, wordsPerPage ], SpecialSpace USING[ MakeResident, MakeSwappable ]; RPCPktStreams: MONITOR IMPORTS Frame, Heap, Inline, ProcessOperations, PupDefs, MesaRPC, RPCLupine, RPCInternal, RPCPkt, RPCPrivate, Runtime, Space, SpecialSpace EXPORTS MesaRPC--Header,ConversationObject--, RPCInternal--DoSignal, ServerMain--, RPCLupine--lots of things-- SHARES BufferDefs, RPCLupine = BEGIN Header: PUBLIC TYPE = RPCPkt.Header; ConcreteHeader: PROC[abstract: LONG POINTER TO RPCLupine.Header] RETURNS[LONG POINTER TO Header] = INLINE { RETURN[ abstract ] }; myHost: RPCPkt.Machine; GiveBackBuffer: PROC[b: BufferDefs.PupBuffer] = -- NOTE: calls of this must be made outside our monitor, because -- RPCPrivate.ReturnBuffer acquires the EthernetDriver monitor, -- and the EthernetDriver may call EnqueueRecvd which acquires -- our monitor! IF Runtime.IsBound[RPCPrivate.ReturnBuffer] THEN RPCPrivate.ReturnBuffer ELSE PupDefs.ReturnFreePupBuffer; -- ******** Caller ******** -- -- For each PSB that initiates a call, record last callee PSB, -- and use that PSB as destPSB hint for next call, to obtain implicit ack of last -- result packet. The fact that the destPSB will be wrong of we next talk to -- a different server host is only a slight pessimization. CallDestHint: TYPE = ARRAY PSB.PsbIndex OF PSB.PsbIndex; lastCallDest: LONG POINTER TO CallDestHint = Heap.systemZone.NEW[CallDestHint_ALL[PSB.PsbNull]]; RecordCallDest: ENTRY PROC[header: LONG POINTER TO Header] = INLINE { lastCallDest[header.destPSB--myPSB--] _ header.srcePSB }; ImportInstance: PUBLIC TYPE = RPCInternal.ImportInstance; -- During a call, a single packet is used for buffering all data sent -- and received. Whenever the client of RPCLupine has possesion of -- the buffer (after StartCall), the buffer is set up correctly for -- transmitting. I.e. buffer.header.dest = the remote machine. Thus, -- this is true on exit from StartCall, and on entry and exit to/from -- SendPrelimPkt, ReceiveExtraPkt, Call, and the dispatchers. This -- causes an extra call of SetupResponse in Call in the case where -- there will be no subsequent call of ReceiveExtraPkt, but it -- preserves my sanity. ConversationObject: PUBLIC TYPE = RPCInternal.ConversationObject; Conversation: TYPE = LONG POINTER TO ConversationObject; MisusedConversation: ERROR = CODE; StartCall: PUBLIC ENTRY PROC[callPkt: RPCLupine.RPCPkt, interface: LONG POINTER TO ImportInstance, localConversation: Conversation _ MesaRPC.unencrypted] = BEGIN myPSB: PSB.PsbIndex = ProcessOperations.HandleToIndex[ProcessOperations.ReadPSB[]]; header: LONG POINTER TO Header = @callPkt.header; header.destHost _ interface.host; header.destSoc _ RPCPrivate.rpcSocket; header.destPSB _ lastCallDest[myPSB]; callPkt.convHandle _ localConversation; IF localConversation = MesaRPC.unencrypted THEN header.conv _ RPCInternal.firstConversation ELSE BEGIN -- ?? header.conv _ RPCInternal.GetPktConversation[localConversation] -- header.conv _ [localConversation.id.count.ls, caller, localConversation.id.count.ms]; IF localConversation.id.originator # myHost THEN BEGIN IF header.destHost # localConversation.id.originator THEN ERROR MisusedConversation[!UNWIND => NULL]; header.conv.originator _ callee; END; END; header.pktID.activity _ myPSB; -- header.pktID.callSeq gets filled in by PktExchange -- header.pktID.pktSeq _ 0; -- => new call -- header.dispatcher _ interface.dispatcher; END; Call: PUBLIC PROC[pkt: RPCLupine.RPCPkt, callLength: RPCLupine.DataLength, maxReturnLength: RPCLupine.DataLength, signalHandler: RPCLupine.Dispatcher _ NIL] RETURNS[ returnLength: RPCLupine.DataLength, lastPkt: BOOLEAN] = BEGIN recvdHeader: LONG POINTER TO Header = @pkt.header; returnLength _ RPCPkt.PktExchange[pkt, callLength, maxReturnLength, call, signalHandler ].newLength; RecordCallDest[recvdHeader]; SELECT recvdHeader.outcome FROM result => NULL; unbound => ERROR MesaRPC.CallFailed[unbound]; protocol => ERROR MesaRPC.CallFailed[runtimeProtocol]; signal => ERROR -- handled inside RPCPkt.PktExchange --; unwind => -- This is legal only if we were called to raise a remote signal; -- UnwindRequested should be caught where we called the dispatcher -- that noticed the signal { RPCPkt.SetupResponse[recvdHeader]; ERROR UnwindRequested[] }; ENDCASE => --unwind,garbage-- ERROR MesaRPC.CallFailed[runtimeProtocol]; RPCPkt.SetupResponse[recvdHeader]; RETURN[ returnLength, recvdHeader.type.eom = end ] END; -- ******** Protocol implementation: multi-packet case ******** -- SendPrelimPkt: PUBLIC PROC[pkt: RPCLupine.RPCPkt, length: RPCLupine.DataLength] = { [] _ RPCPkt.PktExchange[pkt, length, 0, sending] }; ReceiveExtraPkt: PUBLIC PROC[pkt: RPCLupine.RPCPkt] RETURNS[ length: RPCLupine.DataLength, lastPkt: BOOLEAN] = BEGIN recvdHeader: LONG POINTER TO Header; length _ RPCPkt.PktExchange[pkt, 0, RPCLupine.maxDataLength, receiving].newLength; recvdHeader _ @pkt.header; RPCPkt.SetupResponse[recvdHeader]; RETURN[ length, recvdHeader.type.eom = end ] END; -- ******** Protocol implementation: callee and packets-while-notWanting ******** -- idlerAckCount: CARDINAL _ 0; idlerRequeueCount: CARDINAL _ 0; GenerateIdlerResponse: PROC[recvd: RPCLupine.RPCPkt] = BEGIN -- packet is encrypted! -- ackPkt: BufferDefs.PupBuffer = PupDefs.GetFreePupBuffer[]; header: LONG POINTER TO Header = LOOPHOLE[@ackPkt.pupLength]; recvdHeader: LONG POINTER TO Header = @recvd.header; workerPSB: PSB.PsbIndex = recvdHeader.destPSB; -- as adjusted by FindCallee -- idlerAckCount _ idlerAckCount+1; RPCPkt.SetupResponse[recvdHeader]; header^ _ recvdHeader^; header.length _ recvdHeader.length; header.oddByte _ no; header.type _ [0,rpc,end,dontAck,ack]; header.srceHost _ myHost; header.srceSoc _ RPCPrivate.rpcSocket; header.srcePSB _ workerPSB; PupDefs.PupRouterSendThis[ackPkt]; END; EnqueueForNewPSB: PROC[recvd: RPCLupine.RPCPkt] = BEGIN -- packet is encrypted! -- pupPkt: BufferDefs.PupBuffer = PupDefs.GetFreePupBuffer[]; header: LONG POINTER TO Header = LOOPHOLE[@pupPkt.pupLength]; recvdHeader: LONG POINTER TO Header = @recvd.header; idlerRequeueCount _ idlerRequeueCount+1; Inline.LongCOPY[from: recvdHeader, to: header, nwords: recvdHeader.length]; RPCPkt.EnqueueAgain[pupPkt]; END; -- We must maintain globally accessible state indicating current calls in the -- callee, so that the callee can respond to pings. CalleeState: TYPE = RECORD[ next: POINTER TO CalleeState, callee: PSB.PsbIndex, state: LONG POINTER TO Header]; callees: POINTER TO CalleeState _ NIL; EntryAddCallee: ENTRY PROC[stateBlock: POINTER TO CalleeState] = INLINE { AddCallee[stateBlock] }; AddCallee: INTERNAL PROC[stateBlock: POINTER TO CalleeState] = INLINE { stateBlock^.next _ callees; callees _ stateBlock }; RemoveCallee: ENTRY PROC[stateBlock: POINTER TO CalleeState] = BEGIN FOR p: POINTER TO POINTER TO CalleeState _ @callees, @(p^.next) DO SELECT TRUE FROM p^ = stateBlock => { p^ _ p^.next; RETURN }; p^ = NIL => ERROR; ENDCASE => NULL; ENDLOOP; END; FindCallee: ENTRY PROC[given: LONG POINTER TO Header] RETURNS[BOOLEAN] = BEGIN -- Returns TRUE iff there is a current callee for this call, -- even if the callee's pktSeq differs. If result is TRUE, updates -- "given"s destPSB to match callee's. -- Assumes pkt has been decrypted. FOR p: POINTER TO CalleeState _ callees, p.next DO SELECT TRUE FROM p = NIL => RETURN[FALSE]; p.state.conv = given.conv --AND same originator .... -- AND p.state.pktID.activity = given.pktID.activity AND p.state.pktID.callSeq = given.pktID.callSeq => BEGIN given^.destPSB _ p.callee; RETURN[TRUE] END; ENDCASE => NULL; ENDLOOP; END; -- For each calling RPCPkt.ConnectionID we must maintain a sequence number, -- being the last call initiated on that conversation, so that we can eliminate -- duplicate call request packets. -- This information is maintained as a hash table with linked overflow. The -- hash function is (connection.caller XOR connection.activity) MOD 128. -- The hash table is altered by LookupCaller and EndConnection, which are nested -- inside ServerMain for sordid efficiency reasons, and by NoteCaller. HashKey: TYPE = [0..127]; ConnectionData: TYPE = RECORD[next: Connection, id: RPCPkt.ConnectionID, call: RPCPkt.CallCount, conv: MesaRPC.Conversation -- NB: opaque type --]; Connection: TYPE = LONG POINTER TO ConnectionData; connections: LONG POINTER TO ARRAY HashKey OF Connection = Heap.systemZone.NEW[ARRAY HashKey OF Connection _ ALL[NIL]]; ForgetConnections: INTERNAL PROC = -- Forget connection state, so that subsequent calls will cause an RFA -- BEGIN FOR hash: HashKey IN HashKey DO connection: Connection _ connections[hash]; UNTIL connection = NIL DO next: Connection _ connection.next; Heap.systemZone.FREE[@connection]; connection _ next; ENDLOOP; connections[hash] _ NIL; ENDLOOP; END; -- Received packets are dispatched to "ServerMain" processes (through IdleReceive) -- if the addressed process is not wanting to receive any packets at the time, -- or if the destPSB is PsbNull. Thus ServerMain serves both as the listener -- waiting for RFC's on a conventional rendezvous protocol, and as the process -- listening to the incoming per-connection socket in more heavyweight protocols. -- There are several cases. The packet may be the first packet of a new call - -- in this case, this process will handle the call. The packet may be an old -- duplicate packet from a dead call - in this case the packet can be ignored. The -- packet may be a retransmission in a current call - in this case an ack may be -- required. Remember that packets can arrive here in both the caller and -- callee hosts! serverDataLength: RPCLupine.DataLength = RPCLupine.maxDataLength; ServerMain: PUBLIC PROC = BEGIN myPSB: PSB.PsbIndex = ProcessOperations.HandleToIndex[ProcessOperations.ReadPSB[]]; pktSpace: Space.Handle = Space.Create[ size: (serverDataLength+RPCLupine.pktOverhead+Space.wordsPerPage-1)/Space.wordsPerPage, parent: Space.virtualMemory]; myPkt: RPCLupine.RPCPkt = RPCLupine.GetRPCPkt[Space.LongPointer[pktSpace]]; recvdHeader: LONG POINTER TO Header = @myPkt.header; myStateBlock: CalleeState _ [NIL, myPSB, recvdHeader]; newPkt: BOOLEAN _ FALSE; -- Whether packet is valid -- decrypted: BOOLEAN _ FALSE; -- if "newPkt", whether it's been decrypted -- newLength: RPCLupine.DataLength; -- iff "newPkt" and "decrypted", pkt's length -- connection: Connection; Cleanup: PROC = { SpecialSpace.MakeSwappable[pktSpace]; Space.Delete[pktSpace] }; LookupCaller: ENTRY PROC[id: RPCPkt.ConnectionID] RETURNS[{new, old, phoney, unknown}] = INLINE -- Implicitly, recvdHeader is a parameter of LookupCaller. -- If pkt starts call and ConnectionID is unknown, returns "unknown"; -- If pkt starts call and isn't duplicate, adds us as callee, returns "new"; -- If pkt is part of some previously initiated call, returns "old"; -- If pkt is part of some call with unknown ConnectionID, returns "phoney" -- If decrypted pkt is inconsistent, returns "phoney". -- Otherwise, returns "old". -- On entry, packet has previously been decrypted iff "decrypted". -- On exit if result is "new", pkt is decrypted -- On exit if "decrypted", then myPkt.convHandle is set. -- Note that if result is "old", pkt may or may not be decrypted. BEGIN dataPtr: LONG POINTER TO Connection; connection _ (dataPtr_@connections[ Inline.BITAND[Inline.BITXOR[id.caller,id.activity],LAST[HashKey]]])^; DO SELECT TRUE FROM connection = NIL => BEGIN IF recvdHeader.type.class # call THEN RETURN[old]; RETURN[unknown]; END; id.conv = connection.id.conv AND id.caller = connection.id.caller AND recvdHeader.srcePSB = connection.id.activity => BEGIN myPkt.convHandle _ connection.conv; IF NOT decrypted THEN BEGIN IF connection.conv # MesaRPC.unencrypted THEN BEGIN ok: BOOLEAN; [ok, newLength] _ RPCInternal.DecryptPkt[recvdHeader, myPkt.convHandle]; decrypted _ TRUE; IF NOT ok THEN RETURN[phoney]; END ELSE BEGIN newLength _ recvdHeader.length - RPCPkt.pktLengthOverhead; decrypted _ TRUE; END; END; IF recvdHeader.pktID.activity # recvdHeader.srcePSB THEN RETURN[phoney]; IF recvdHeader.type.class # call THEN RETURN[old]; IF recvdHeader.pktID.callSeq > connection.call THEN BEGIN IF recvdHeader.pktID.pktSeq # 1 THEN RETURN[phoney]; connection.call _ recvdHeader.pktID.callSeq; AddCallee[@myStateBlock]; RETURN[new] END ELSE RETURN[old] END; ENDCASE => connection _ (dataPtr_@connection.next)^; ENDLOOP; END; NoteConnection: ENTRY PROC[id: RPCPkt.ConnectionID, call: RPCPkt.CallCount, conv: MesaRPC.Conversation] = BEGIN dataPtr: LONG POINTER TO Connection; connection _ (dataPtr_@connections[ Inline.BITAND[Inline.BITXOR[id.caller,id.activity],LAST[HashKey]]])^; DO SELECT TRUE FROM connection = NIL => BEGIN dataPtr^ _ Heap.systemZone.NEW[ConnectionData _ [ next:NIL, id:id, call:call-1, conv:conv ]]; EXIT END; id.conv = connection.id.conv AND id.caller = connection.id.caller AND id.activity = connection.id.activity => -- already there! -- EXIT; ENDCASE => connection _ (dataPtr_@connection.next)^; ENDLOOP; END; Space.Map[pktSpace]; SpecialSpace.MakeResident[pktSpace]; -- newPkt = TRUE at top of loop iff we have the first pkt of next call already. -- At top of loop, myPkt is decrypted if newPkt = TRUE. -- DO ENABLE { ABORTED => EXIT; UNWIND => Cleanup[] }; IF NOT newPkt THEN { RPCPkt.IdleReceive[myPkt, RPCLupine.maxPupWords]; newPkt _ TRUE; decrypted _ FALSE }; SELECT LookupCaller[ id: [recvdHeader.conv, recvdHeader.srceHost, recvdHeader.srcePSB] ] FROM new => -- start of new call -- BEGIN target: RPCPkt.DispatcherDetails = recvdHeader.dispatcher; resultLength: RPCLupine.DataLength; RPCPkt.SetupResponse[recvdHeader]; IF target.dispatcherHint >= RPCInternal.exportTable.used OR target.dispatcherID = RPCPkt.noDispatcher OR target.dispatcherID # RPCInternal.exportTable[target.dispatcherHint].id THEN { Reject[myPkt, unbound]; resultLength _ 0 } ELSE resultLength _ RPCInternal.exportTable[target.dispatcherHint].dispatcher [myPkt, newLength, recvdHeader.type.eom = end, connection.conv ! MesaRPC.CallFailed => {newPkt _ FALSE; RemoveCallee[@myStateBlock]; LOOP}; UnwindRequested => -- The dispatcher raised a remote signal which the remote machine -- is unwinding { resultLength _ 0; CONTINUE }; RejectUnbound => -- The dispatcher wants caller to get CallFailed[unbound] -- { Reject[myPkt, unbound]; resultLength _ 0; CONTINUE }; RejectProtocol => -- The dispatcher wants caller to get CallFailed[badProtocol] -- { Reject[myPkt, protocol]; resultLength _ 0; CONTINUE }; UNWIND => RemoveCallee[@myStateBlock] ]; RemoveCallee[@myStateBlock]; [newPkt, newLength] _ RPCPkt.PktExchange[myPkt, resultLength, serverDataLength, endCall ! MesaRPC.CallFailed => { newPkt _ FALSE; CONTINUE }]; IF newPkt THEN decrypted _ TRUE; -- now newPkt=FALSE or myPkt is decrypted and contains start of new call -- END; unknown => -- need to ask other end for connection state -- BEGIN ok: BOOLEAN; id: RPCPkt.ConnectionID; call: RPCPkt.CallCount; conv: MesaRPC.Conversation; l: RPCLupine.DataLength; [ok, id, call, conv, l] _ RPCInternal.GetConnectionState[decrypted, myPkt ! MesaRPC.CallFailed => { newPkt_FALSE; LOOP } ]; IF ok THEN BEGIN IF NOT newPkt THEN ERROR; IF NOT decrypted THEN { decrypted _ TRUE; newLength _ l }; NoteConnection[id, call, conv]; END ELSE newPkt _ FALSE; END; phoney => -- ignorable packet -- newPkt _ FALSE; old => BEGIN -- Pkt may or may not have been decrypted. -- If the packet came to us because it had an incorrect destPSB, we should try -- correcting it and giving it to the correct process. This ensures that destPSB -- is only a hint. Also, because of the restrictions on generating ack's (described -- below), there are cases where an ack is required but only the correct worker -- process is allowed to generate it. oldDest: PSB.PsbIndex = recvdHeader.destPSB; knownCallee: BOOL = decrypted AND FindCallee[recvdHeader]--may alter destPSB--; IF knownCallee AND recvdHeader.destPSB # oldDest THEN -- destPSB his was wrong: requeue pkt for correct process -- -- Note that if correct process doesn't want the packet right now, it -- may come back to an idler process, but it will have correct destPSB -- BEGIN IF decrypted THEN recvdHeader.length _ IF myPkt.convHandle = MesaRPC.unencrypted THEN RPCPkt.pktLengthOverhead + newLength ELSE RPCInternal.EncryptPkt[myPkt, newLength]; EnqueueForNewPSB[myPkt]; END ELSE BEGIN -- We're here because the packet doesn't start a new call. We should respond if -- the packet is a retransmission or a ping. -- We generate an ack only if the packet has eom-end. Therefore, -- the last packet in any direction may only be sent when the worker -- process has generated the ack for the preceding packet in that -- direction. Therefore, the last packet in any direction comes to -- an idler process only after the worker process has received a -- previous transmission of that packet (because of the way "wanting" -- is set in PktExchange). We assume that class=data isn't used for -- pings. If we're still working on the call, we -- generate an ack containing the worker process's PSBIndex. Beware -- when caller and callee are on the same host! IF recvdHeader.type.ack = pleaseAck AND recvdHeader.type.eom = end AND( recvdHeader.type.class = data OR knownCallee ) THEN BEGIN recvdHeader.length _ IF NOT decrypted OR myPkt.convHandle = MesaRPC.unencrypted THEN RPCPkt.pktLengthOverhead ELSE RPCInternal.EncryptPkt[myPkt, 0]; GenerateIdlerResponse[myPkt]; END; END; newPkt _ FALSE; END; ENDCASE => ERROR; ENDLOOP; Cleanup[]; END; -- ******** Remote signalling ******** -- StartSignal: PUBLIC PROC[signalPkt: RPCLupine.RPCPkt] = { ConcreteHeader[@signalPkt.header].outcome _ signal }; UnwindRequested: ERROR = CODE; -- internal: remote machine is unwinding a signal -- DoSignal: PUBLIC PROC[b: BufferDefs.PupBuffer, pktLength: RPCLupine.DataLength, signalHandler: RPCLupine.Dispatcher, convHandle: MesaRPC.Conversation] RETURNS[resumePkt: RPCLupine.RPCPkt, resumeLength: RPCLupine.DataLength, myLocalFrame: POINTER] = BEGIN myPktSpace: ARRAY [1..serverDataLength + RPCLupine.pktOverhead] OF WORD; pkt: RPCLupine.RPCPkt = RPCLupine.GetRPCPkt[@myPktSpace]; recvdHeader: LONG POINTER TO Header = @pkt.header; myPSB: PSB.PsbIndex = ProcessOperations.HandleToIndex[ProcessOperations.ReadPSB[]]; -- We must register as a callee, in case other end pings during signal. -- See comments in ServerMain. myStateBlock: CalleeState _ [NIL, myPSB, recvdHeader]; BEGIN -- copy from the Pup buffer into our frame -- IF pktLength > serverDataLength THEN ERROR MesaRPC.CallFailed[runtimeProtocol ! UNWIND => GiveBackBuffer[b]]; Inline.LongCOPY[from: @b.pupLength, to: recvdHeader, nwords: pktLength+SIZE[Header]]; GiveBackBuffer[b]; END; pkt.convHandle _ convHandle; EntryAddCallee[@myStateBlock]; BEGIN ENABLE UNWIND => RemoveCallee[@myStateBlock]; handlerFailed: BOOLEAN _ FALSE; -- CallFailed raised inside signalHandler! -- RPCPkt.SetupResponse[recvdHeader]; IF signalHandler = NIL THEN { Reject[pkt, unbound]; resumeLength _ 0 } ELSE resumeLength _ signalHandler[ pkt, pktLength, recvdHeader.type.eom = end, convHandle ! MesaRPC.CallFailed => handlerFailed _ TRUE; UNWIND => IF NOT handlerFailed THEN BEGIN recvdHeader.outcome _ unwind; resumeLength _ RPCPkt.PktExchange[pkt, 0, serverDataLength, call, signalHandler].newLength; SELECT recvdHeader.outcome FROM result => NULL -- let our UNWIND propagate --; signal => ERROR -- handled inside RPCPkt.PktExchange--; ENDCASE => --unbound,protocol,unwind,garbage-- ERROR MesaRPC.CallFailed[runtimeProtocol]; RPCPkt.SetupResponse[recvdHeader]; END -- ELSE the UNWIND was in response to us raising CallFailed, so -- there's no point in talking to the other machine --; UnwindRequested => -- The signalHandler raised a remote signal which -- the remote machine is unwinding! { resumeLength _ 0; CONTINUE }; RejectUnbound => { Reject[pkt, unbound]; resumeLength _ 0; CONTINUE }; RejectProtocol => { Reject[pkt, protocol]; resumeLength _ 0; CONTINUE }; ]; END; RemoveCallee[@myStateBlock]; -- Magic to return to my caller without freeing my local frame -- (LOOPHOLE[Frame.GetReturnLink[], PROC[RPCLupine.RPCPkt, RPCLupine.DataLength, POINTER]]) [pkt, resumeLength, LOOPHOLE[Frame.MyLocalFrame[]]]; END; -- ******** Remote call failure ******** -- RejectUnbound: PUBLIC ERROR = CODE; RejectProtocol: PUBLIC ERROR = CODE; Reject: PROC[pkt: RPCLupine.RPCPkt, rejection: RPCPkt.Outcome] = BEGIN header: LONG POINTER TO Header = @pkt.header; UNTIL header.type.eom = end DO [,] _ ReceiveExtraPkt[pkt ! MesaRPC.CallFailed => { rejection _ protocol; EXIT }]; ENDLOOP; header.outcome _ rejection; END; -- ******** Initialization ******** -- Initialize: ENTRY PROC = BEGIN myAddr: PupTypes.PupAddress = PupDefs.GetLocalPupAddress[RPCPrivate.rpcSocket,NIL]; myHost _ [net: myAddr.net, host: myAddr.host]; END; Restart: ENTRY PROC = BEGIN ForgetConnections[] END; Initialize[]; DO STOP; Restart[]; ENDLOOP; END.