DIRECTORY YggEnvironment, YggIdentity USING [myLogVolumeID, myAlpineImportHandle, myFileStore], YggImport, YggInternal, YggTransaction, YggTransMgr, BasicTime, YggClientMap, YggConcreteTransID, ConvertUnsafe, YggCoordinator, YggCoordinatorControl, YggCoordinatorInternal, YggCoordinatorMap, YggLog, YggLogBasic, YggLogControl, YggLogInline, YggDummyProcess USING [Detach, EnableAborts, SetTimeout, SecondsToTicks, Ticks], Rope USING [Cat, Concat, ROPE], YggDummyRPC USING [Conversation, GetCaller], SafeStorage, YggMonitoringLog USING [AbortReason, notice, TransactionAbortInfo, TransactionBeginInfo, TransactionCommitInfo, OpFailureInfo]; YggCoordinatorImpl: CEDAR MONITOR LOCKS c USING c: Handle IMPORTS YggIdentity, YggImport, YggTransaction, BasicTime, YggClientMap, ConvertUnsafe, YggCoordinatorInternal, YggCoordinatorMap, YggLog, YggLogBasic, YggLogControl, YggLogInline, YggDummyProcess, Rope, YggDummyRPC, SafeStorage, YggMonitoringLog EXPORTS YggInternal, YggTransaction, YggTransMgr, YggCoordinatorControl = BEGIN Conversation: TYPE = YggEnvironment.Conversation; TransID: TYPE = YggEnvironment.TransID; nullTransID: TransID = YggEnvironment.nullTransID; RecordID: TYPE = YggLog.RecordID; RecordType: TYPE = YggLog.RecordType; Results: TYPE = YggCoordinator.Results; InternalProgrammingError: ERROR = CODE; Handle: TYPE = YggCoordinator.Handle; nullHandle: Handle = YggCoordinator.nullHandle; AbortReason: TYPE = YggMonitoringLog.AbortReason; CoordinatorObject: PUBLIC TYPE = YggCoordinator.Object; secondsWaitAfterCommFailure: INT = YggCoordinatorInternal.SecondsWaitAfterCommFailure; timeoutForResultsReturned: YggDummyProcess.Ticks = YggDummyProcess.SecondsToTicks[YggCoordinatorInternal.SecondsTimeoutForResultsReturned]; maxCoordinators: INT _ 500; usableLogFraction: REAL _ 0.5; zone: ZONE = SafeStorage.GetSystemZone[]; Create: PUBLIC PROC [conversation: Conversation, createLocalWorker: BOOL] RETURNS [transID: YggTransaction.TransID] = { c: Handle; message: Rope.ROPE; logBeginProc: PROC [YggMonitoringLog.TransactionBeginInfo]; -- (used to prevent race condition) {IF YggCoordinatorMap.Count[] >= maxCoordinators THEN { message _ "hit limit on coordinators"; GOTO serverBusy; }; IF YggLogBasic.LogUsage[] > usableLogFraction*YggLogBasic.LogFileSize[] THEN { message _ "log too full"; GOTO serverBusy; }; c _ ConsCoordinator[]; c.extras _ NEW[YggCoordinator.Info]; IF (c.extras.userRName _ YggDummyRPC.GetCaller[conversation]) = NIL THEN c.extras.userRName _ Rope.Cat["Local - ", YggClientMap.GetName[conversation]]; YggCoordinatorMap.Register[c]; IF createLocalWorker THEN YggTransaction.CreateWorker[conversation, c.transID, YggIdentity.myFileStore ! YggTransaction.Unknown => { message _ SELECT what FROM transID => "worker didn't recognize transID", coordinator => "worker doesn't know who we are" ENDCASE => ERROR; GOTO serverBusy }; ]; IF (logBeginProc _ YggMonitoringLog.notice.beginTransaction) # NIL THEN logBeginProc[[ transID: c.transID, where: "CoordinatorImpl.Create", message: "" ]]; RETURN [c.transID]; EXITS serverBusy => { logProc: PROC [YggMonitoringLog.OpFailureInfo]; IF (logProc _ YggMonitoringLog.notice.operationFailed) # NIL THEN logProc[[ what: busy, where: "CoordinatorImpl.Create", message: message.Cat["; user = ", YggDummyRPC.GetCaller[conversation]] ]]; ERROR YggTransaction.OperationFailed[why: busy]; } } }; ConsCoordinator: PROC [] RETURNS [Handle] = { newTrans: Handle = zone.NEW[YggCoordinator.Object _ []]; TRUSTED {YggDummyProcess.SetTimeout[@newTrans.resultsReturned, timeoutForResultsReturned];}; YggCoordinatorInternal.NextTransID[newTrans]; --writes coordinatorBegin RETURN [newTrans] }; RegisterWorker: PUBLIC PROC [ conversation: Conversation, trans: TransID] RETURNS [YggTransMgr.RegisterWorkerResult] = { RegisterWorkerEntry: ENTRY PROC [c: Handle] RETURNS [YggTransMgr.RegisterWorkerResult] = { worker: YggImport.Handle; IF c.state # active THEN RETURN [transNotActive]; worker _ YggImport.Register[YggClientMap.GetName[conversation]]; FOR l: YggCoordinator.WorkerHandle _ c.workers, l.rest UNTIL l = NIL DO IF worker.Equal[l.first.worker] THEN RETURN [duplicateCall]; ENDLOOP; ConsWorker[c, worker]; RETURN [ok]; };--RegisterWorkerEntry c: Handle = YggCoordinatorMap.GetHandle[trans]; IF c = nullHandle THEN RETURN [transNotActive]; RETURN [RegisterWorkerEntry[c]]; }; ConsWorker: PROC [c: Handle, w: YggImport.Handle] = { l: YggCoordinator.WorkerHandle _ zone.CONS[[worker: w], c.workers]; c.workers _ l; LogCoordinatorRegisterWorker[c, w]; }; CreateWithWorkers: INTERNAL PROC [c: Handle] RETURNS [Handle] = { newTrans: Handle = ConsCoordinator[]; FOR l: YggCoordinator.WorkerHandle _ c.workers, l.rest UNTIL l = NIL DO ConsWorker[newTrans, l.first.worker]; ENDLOOP; RETURN [newTrans] }; Check: PUBLIC PROC [ transID: YggTransaction.TransID] RETURNS [outcome: YggTransaction.Outcome] = { c: Handle = YggCoordinatorMap.GetHandle[trans: transID]; outcome _ c.outcome; }; Finish: PUBLIC PROC [ conversation: YggDummyRPC.Conversation, transID: YggTransaction.TransID, requestedOutcome: YggTransaction.RequestedOutcome, continue: BOOL] RETURNS [outcome: YggTransaction.Outcome, newTrans: YggTransaction.TransID] = { newTransCoordinator: Handle; c: Handle = YggCoordinatorMap.GetHandle[trans: transID]; reason: AbortReason; -- reason for aborting the transaction owner: Rope.ROPE; -- owner of the to-be-finished transaction IF c = nullHandle THEN RETURN [unknown, nullTransID]; owner _ c.extras.userRName; IF requestedOutcome = abort THEN continue _ FALSE; IF 2*YggLogBasic.LogUsage[] > YggLogBasic.LogFileSize[] THEN continue _ FALSE; [outcome, newTransCoordinator, reason] _ FinishEntry[c, requestedOutcome, continue]; IF continue AND outcome = abort AND newTransCoordinator # nullHandle THEN { [] _ FinishEntry[newTransCoordinator, abort, --continue:-- FALSE]; newTransCoordinator _ nullHandle }; IF newTransCoordinator # nullHandle THEN { logProc: PROC [YggMonitoringLog.TransactionBeginInfo]; newTransCoordinator.extras _ NEW[YggCoordinator.Info]; newTransCoordinator.extras.userRName _ owner; IF (logProc _ YggMonitoringLog.notice.beginTransaction) # NIL THEN logProc[[ transID: newTransCoordinator.transID, where: "CoordinatorImpl.Finish", message: "" ]]; }; SELECT outcome FROM abort => { logProc: PROC [YggMonitoringLog.TransactionAbortInfo]; IF (logProc _ YggMonitoringLog.notice.abortTransaction) # NIL THEN logProc[[ transID: c.transID, where: "CoordinatorImpl.Finish", why: reason, message: Rope.Concat["Owner is ", owner] ]]; }; commit => { logProc: PROC [YggMonitoringLog.TransactionCommitInfo]; IF (logProc _ YggMonitoringLog.notice.commitTransaction) # NIL THEN logProc[[ transID: c.transID, where: "CoordinatorImpl.Finish", message: Rope.Concat["Owner is ", owner] ]]; }; ENDCASE => NULL; RETURN [outcome, IF newTransCoordinator = nullHandle THEN nullTransID ELSE newTransCoordinator.transID] }; FinishEntry: ENTRY PROC [ c: Handle, requestedOutcome: YggTransaction.RequestedOutcome, continue: BOOL] RETURNS [YggTransaction.Outcome, Handle, AbortReason] = { newTransCoordinator: Handle _ nullHandle; newTransID: TransID _ nullTransID; reason: AbortReason _ didntAbort; { IF c.finishInProgress THEN { WHILE c.finishInProgress DO WAIT finishComplete; ENDLOOP; RETURN [unknown, nullHandle, unknown]; }; SELECT c.state FROM active => NULL; collecting, completing => NULL; complete => RETURN [unknown, nullHandle, unknown]; ENDCASE => ERROR; c.finishInProgress _ TRUE; IF requestedOutcome = abort THEN { reason _ requested; c.outcome _ abort; }; IF continue THEN { newTransCoordinator _ CreateWithWorkers[c]; newTransID _ newTransCoordinator.transID; }; IF c.state = active THEN { YggLog.Force[followingRecord: c.forceRecord]; c.state _ collecting; }; DO noneActive: BOOL _ TRUE; allComplete: BOOL _ TRUE; FOR w: YggCoordinator.WorkerHandle _ c.workers, w.rest UNTIL w = NIL DO WITH w.first.resultsOfMostRecentCall SELECT FROM n: Results.none => { }; p: Results.prepare => TRUSTED { w.first.resultsOfMostRecentCall _ [none, none[]]; w.first.lastPrepareResult _ p; SELECT p.communicationError FROM none => { w.first.communicationTrouble _ FALSE; SELECT p.prepareResult FROM notReady => { --vote no-- w.first.state _ complete; IF c.outcome = commit THEN ERROR; reason _ workerNotReady; c.outcome _ abort; }; readOnlyReady => { w.first.state _ complete }; ready => { w.first.state _ ready; c.aWorkerBecameReady _ TRUE; }; ENDCASE => ERROR; }; bindingFailed, callFailed, busy => { --vote no-- IF c.outcome = commit THEN ERROR; reason _ commError; c.outcome _ abort; w.first.communicationTrouble _ TRUE; w.first.timeForNextCall _ BasicTime.Update[ base: BasicTime.Now[], period: secondsWaitAfterCommFailure]; }; ENDCASE => ERROR; }; f: Results.finish => TRUSTED { w.first.resultsOfMostRecentCall _ [none, none[]]; w.first.lastFinishResult _ f; SELECT f.communicationError FROM none => { w.first.communicationTrouble _ FALSE; w.first.state _ complete; }; bindingFailed, callFailed, busy => { w.first.communicationTrouble _ TRUE; w.first.timeForNextCall _ BasicTime.Update[ base: BasicTime.Now[], period: secondsWaitAfterCommFailure]; }; ENDCASE => ERROR; }; ENDCASE => ERROR; IF w.first.state = active THEN noneActive _ FALSE; IF w.first.state # complete THEN allComplete _ FALSE; ENDLOOP; IF c.state = collecting AND (c.outcome = abort OR noneActive) THEN { c.state _ completing; IF c.outcome = unknown THEN c.outcome _ commit; LogCoordinatorCompleting[c: c, outcome: c.outcome, force: c.outcome = abort OR c.aWorkerBecameReady]; }; IF c.state = completing AND allComplete THEN { c.state _ complete; LogCoordinatorComplete[c]; GOTO Done; }; FOR w: YggCoordinator.WorkerHandle _ c.workers, w.rest UNTIL w = NIL DO IF w.first.state # complete AND w.first.callInProgress = none AND (NOT w.first.communicationTrouble OR IsTimeForNextCall[w]) THEN { IF c.state = collecting AND w.first.state = active THEN { YggCoordinatorInternal.PassParms[[prepare, c, w, newTransID, commit--ignored--]]; w.first.callInProgress _ prepare; } ELSE IF c.state = completing THEN { YggCoordinatorInternal.PassParms[[finish, c, w, nullTransID--ignored--, IF c.outcome = commit THEN commit ELSE abort]]; w.first.callInProgress _ finish; }; }; ENDLOOP; WAIT c.resultsReturned; ENDLOOP;-- monster DO EXITS Done => { c.finishInProgress _ FALSE; YggCoordinatorMap.Unregister[c]; IF newTransCoordinator # nullHandle THEN YggCoordinatorMap.Register[newTransCoordinator]; RETURN [c.outcome, newTransCoordinator, reason] } }};--FinishEntry finishComplete: CONDITION; IsTimeForNextCall: PROC [w: YggCoordinator.WorkerHandle] RETURNS [BOOL] = INLINE { RETURN [BasicTime.Period[from: w.first.timeForNextCall, to: BasicTime.Now[]] >= 0] }; LogCoordinatorRegisterWorker: PROC [ c: Handle, w: YggImport.Handle] = { followingRecord: YggLog.RecordID; rec: YggCoordinator.RegisterWorkerLogRep _ []; TRUSTED { ConvertUnsafe.AppendRope[to: LOOPHOLE[LONG[@rec.worker]], from: w.Name]; [followingRecord: followingRecord] _ YggLog.CoordinatorWrite[c, coordinatorRegisterWorker, [base: @rec, length: TEXT[rec.length].SIZE]]; }; IF NOT w.Equal[YggIdentity.myAlpineImportHandle] THEN c.forceRecord _ followingRecord; }; LogCoordinatorCompleting: PROC [c: Handle, outcome: YggEnvironment.CommitOrAbort, force: BOOL] = TRUSTED { rec: YggCoordinator.CompletingLogRep _ [outcome: outcome]; [followingRecord: c.forceRecord] _ YggLog.CoordinatorWrite[c, coordinatorCompleting, [base: @rec, length: YggCoordinator.CompletingLogRep.SIZE], force]; }; LogCoordinatorComplete: PROC [c: Handle] = { [followingRecord: c.forceRecord] _ YggLog.CoordinatorWrite[c, coordinatorComplete, YggLog.nullBlock]; }; AnalyzeCoordinatorBegin: PROC [record: RecordID, type: RecordType, trans: TransID] = { c: Handle = zone.NEW[YggCoordinator.Object _ [transID: trans, beginRecord: record]]; YggCoordinatorMap.Register[c]; YggCoordinatorInternal.NoticeCoordinatorBegin[trans]; }; AnalyzeCoordinatorRegisterWorker: PROC [ record: RecordID, type: RecordType, trans: TransID] = { worker: YggImport.Handle; { -- get worker name from log, map it into a YggImport.Handle. rec: YggCoordinator.RegisterWorkerLogRep _ []; status: YggLog.ReadProcStatus; TRUSTED {[status: status] _ YggLog.ReadForRecovery[thisRecord: record, to: [base: @rec, length: YggCoordinator.RegisterWorkerLogRep.SIZE]];}; IF status = destinationFull THEN ERROR InternalProgrammingError; TRUSTED {worker _ YggImport.Register[ server: ConvertUnsafe.ToRope[from: LOOPHOLE[LONG[@rec.worker]]]]; }; }; { -- map trans into a coordinator handle, then add worker to volatile state c: Handle = YggCoordinatorMap.GetHandle[trans]; l: YggCoordinator.WorkerHandle; IF c = nullHandle THEN RETURN; IF c.state # active THEN ERROR; FOR l _ c.workers, l.rest UNTIL l = NIL DO IF worker.Equal[l.first.worker] THEN ERROR InternalProgrammingError; ENDLOOP; l _ zone.CONS[[worker: worker], c.workers]; c.workers _ l }; }; AnalyzeCoordinatorCompleting: PROC [ record: RecordID, type: RecordType, trans: TransID] = { outcome: YggEnvironment.CommitOrAbort; { -- get outcome from log rec: YggCoordinator.CompletingLogRep; status: YggLog.ReadProcStatus; TRUSTED {[status: status] _ YggLog.ReadForRecovery[thisRecord: record, to: [base: @rec, length: YggCoordinator.CompletingLogRep.SIZE]];}; IF status # normal THEN ERROR InternalProgrammingError; outcome _ rec.outcome; }; { c: Handle = YggCoordinatorMap.GetHandle[trans]; IF c = nullHandle THEN RETURN; IF c.state # active THEN ERROR InternalProgrammingError; c.state _ completing; c.outcome _ outcome; IF c.outcome = commit THEN FOR l: YggCoordinator.WorkerHandle _ c.workers, l.rest UNTIL l = NIL DO l.first.state _ ready ENDLOOP; }; }; AnalyzeCoordinatorComplete: PROC [ record: RecordID, type: RecordType, trans: TransID] = { c: Handle = YggCoordinatorMap.GetHandle[trans]; IF c = nullHandle THEN RETURN; IF c.state # completing THEN ERROR InternalProgrammingError; c.state _ complete; YggCoordinatorMap.Unregister[c]; }; CallAfterAnalysisPass: PUBLIC PROC [] = { YggCoordinatorInternal.InitTransIDGenerator[YggIdentity.myLogVolumeID]; }; CallAfterUpdatePass: PUBLIC PROC [] = { EnumProc: PROC [c: Handle] RETURNS [stop: BOOL] = { requestedOutcome: YggTransaction.RequestedOutcome _ abort; SELECT c.state FROM complete => ERROR; active, collecting => NULL; completing => IF c.outcome = commit THEN requestedOutcome _ commit; ENDCASE => ERROR; TRUSTED {YggDummyProcess.Detach[FORK CoordinatorFinishProcess[c, requestedOutcome]];}; RETURN [stop: FALSE]; }; YggCoordinatorMap.LockedEnumerate[EnumProc]; }; CoordinatorFinishProcess: PROC [c: Handle, requestedOutcome: YggTransaction.RequestedOutcome] = { [] _ FinishEntry [c, requestedOutcome, FALSE]; }; CoordinatorCheckpointProc: PROC [] RETURNS [keepRecord, startAnalysisRecord: RecordID] = { oldestBeginRecord: YggLog.RecordID _ YggLog.lastRecordID; EnumProc: PROC [c: Handle] RETURNS [stop: BOOL] = { oldestBeginRecord _ YggLogInline.Min[c.beginRecord, oldestBeginRecord]; RETURN [stop: FALSE]; }; YggCoordinatorMap.UnlockedEnumerate[EnumProc]; RETURN [oldestBeginRecord, oldestBeginRecord]; }; YggLogControl.RegisterAnalysisProc[coordinatorBegin, AnalyzeCoordinatorBegin]; YggLogControl.RegisterAnalysisProc[coordinatorRegisterWorker, AnalyzeCoordinatorRegisterWorker]; YggLogControl.RegisterAnalysisProc[coordinatorCompleting, AnalyzeCoordinatorCompleting]; YggLogControl.RegisterAnalysisProc[coordinatorComplete, AnalyzeCoordinatorComplete]; YggLogControl.RegisterCheckpointProc[CoordinatorCheckpointProc]; TRUSTED { YggDummyProcess.SetTimeout[@finishComplete, YggDummyProcess.SecondsToTicks[5]]; YggDummyProcess.EnableAborts[@finishComplete]; }; END. CHANGE LOG. ΤYggCoordinatorImpl.mesa Copyright Σ 1984, 1988 by Xerox Corporation. All rights reserved. Taft on 1-Feb-82 13:16:03 Kolling on July 13, 1983 3:27 pm MBrown on January 30, 1984 12:08:04 pm PST Kupfer, August 6, 1984 3:05:48 pm PDT Carl Hauser, February 3, 1988 11:03:01 am PST Bob Hagmann May 12, 1988 3:01:16 pm PDT NOTES: FORK CoordinatorFinishProcess[c, requestedOutcome] is not controlled (might try to fork too many) and not synchronized (outside call to Finish might get in). Simultaneous Finish calls should work better (now, second caller gets result = unknown). The mechanism for limiting the number of coordinators is quick and dirty, should be monitored in a different way. YggInternal.CoordinatorObject ! YggTransaction.OperationFailed[why: busy]; YggTransaction.Create. Called by any Alpine client. As an agent of the client who called us, create a worker. Under the current implementation (coordinator and worker always on the same machine), we really oughta know our own RName and the current transaction ID. However, in the glorious future, it is possible that the (remote) server won't know about the transaction yet (or that the server crashed), and it's possible that Grapevine is too busy to tell the server our RName. So if Unknown is raised, convert it to a server-busy error, but record what went wrong via YggMonitoringLog. YggTransaction.OperationFailed[busy] => { message _ "RPC returned `busy'"; GOTO serverBusy } YggDummyProcess.EnableAborts[@newTrans.resultsReturned]; ! (none); YggTransMgr.RegisterWorker. The caller is a worker (identity obtainable via "conversation") with no monitors locked. c # nullHandle. Creates a new transaction, and simulates RegisterWorker for each worker of existing transaction c. Returns the new transaction, without registering it. ! (none) (but may return "unknown" as a result.) YggTransaction.Finish A hack to keep from filling the log: If we asked to continue the transaction but it aborted on us, we have to clean up (well, I think that's what happens next). I guess this means that logged transaction starts might occasionally skip transaction ID's. If we really did create a new transaction, then record the user name in the coordinator object and note the new transaction (if the probe is enabled). (the owner name is given as a message because the transaction has already been unregistered, so the YggMonitoringLog routine can't get at the handle) ! (none); wait for finish to complete, then return unknown normal-- FinishEntry has been called from recovery-- FinishEntry has been called from two nearly simultaneous calls to Finish-- General two-phase commit protocol. c.state IN [collecting .. completing] Try to make progress toward c.state = complete. Get results from previous calls, if any. Make state transitions if possible. Issue new calls. wake up when a result is returned or by timeout YggCoordinatorControl.CallAfterAnalysisPass YggCoordinatorControl.CallAfterUpdatePass Edited June 1984, by Kupfer changes to: Create: record the RName of the user creating the transaction. Add event logging for transaction starts. Edited on July 1, 1984 4:11:19 pm PDT, by Kupfer Be sure to event-log transactions which are created by Finish[]ing old one. Also, remember to record the RName for transactions that are created that way. Edited on July 25, 1984 9:22:20 am PDT, by Kupfer Install new YggMonitoringLog probes and reflect the new version of YggMonitoringLog. Also, be more rigorous about the possible ERRORs that CreateWorker might return. Also, make maxCoordinators really refer to YggCoordinatorInternal. changes to: CoordinatorImpl, Create, FinishEntry, Finish, AbortReason Edited on August 6, 1984 3:04:59 pm PDT, by Kupfer (1) Let the YggMonitoringLog routine get the user RName from the transaction ID, where possible. (2) Remove the possible race condition in YggMonitoringLog probes by assigning the PROC to a temporary variable. changes to: Create, Finish, CoordinatorImpl Carl Hauser, October 4, 1985 1:24:32 pm PDT Change "Log" to "AlpineLog" Κ ˜Icodešœ™šœB™BKšœ™Kšœ ™ Kšœ*™*K™%K™-K™'—K™K˜Kšœ™Kšœ™KšœX™XKšœq™qK˜˜šΟk ˜ Kšœ˜Kšœ œ4˜EKšœ ˜ Kšœ ˜ Kšœ˜Kšœ ˜ K˜ Kšœ ˜ Kšœ˜K˜K˜K˜K˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ ˜ Kšœœ;˜PKšœœœ˜Kšœ œ˜,Kšœ ˜ K•StartOfExpansion[]šœœi˜K˜——šœ œœœ ˜9š˜Kšœ ˜ Kšœ ˜ Kšœ˜K˜ Kšœ ˜ K˜K˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ ˜ Kšœ˜K˜Kš œ˜ K˜ Kšœ˜—š˜Kšœ ˜ Kšœ˜Kšœ ˜ K˜—Kšœ˜Kšœœ˜1Kšœ œ˜'Kšœ2˜2Kšœ œ˜!Kšœ œ˜%Kšœ œ˜'Kšœœœ˜'Kšœœ˜%K˜/Kšœ œ ˜1K˜šœœœ˜7Kšœ™—K˜Kšœœ6˜Všœ2˜2KšœX˜X—Kšœœ˜K˜Kšœœ˜K˜Kšœœ˜)K˜K˜šΟnœœœ1œ˜IKšœ&˜-Kšœ,™,Kšœ™Kšœ™K˜ Jšœœ˜Jšœœ*Οc#˜_Kšœ˜šœ.œ˜7K˜&Kšœ ˜K˜—šœFœ˜NKšœ˜Kšœ ˜Kšœ˜—K˜Kšœ œ˜$–"[conversation: RPC.Conversation]šœ œœ˜HKšœN˜N—Kšœ˜šœ˜Kšœš™ššœN˜Nšœ˜šœ œ˜Kšœ-˜-K˜/Kšœœ˜—Kšœ ˜K˜—šœ)™)Kšœ ™ Kšœ ™K™—Kšœ˜——šœ=œ˜Gšœ˜K˜Kšœ ˜ Kšœ ˜ K˜——Kšœ ˜š˜šœ˜Kšœ œ"˜/šœ7œ˜Ašœ ˜ Kšœ ˜ Kšœ ˜ Kšœ" œ˜FK˜——Kšœ+˜0K˜—K˜—K˜K˜—šžœœœ ˜-Kšœœ˜8Kšœ\˜\Kšœ8™8Kšœ.Ÿ˜GKšœ˜K˜—šžœœœ˜K˜,Kšœ'˜.Kšœ ™ Kšœ™KšœX™Xšžœœœ ˜+Kšœ'˜.Kšœ™Kšœ˜Kšœœœ˜1Kšœ@˜@šœ4œœ˜GKšœœœ˜K˜.Kšœ˜šœF˜FKšœ=œ˜F—Kšœœœ˜@šœ%˜%Kšœ#œœ˜D—K˜KšœŸI˜KKšœ/˜/K˜Kšœœœ˜Kšœœœ˜šœœœ˜*Kšœœœ˜DKšœ˜—Kšœ œ˜+K˜ K˜—K˜K˜—šžœœ˜$K˜7šœ&˜&KšœŸ˜K˜%Kšœ˜šœ?˜FKšœ9œ˜B—Kšœœœ˜7K˜K˜K˜Kšœ/˜/Kšœœœ˜Kšœœœ˜8K˜K˜šœ˜šœ4œœ˜GK˜Kšœ˜——K˜—K˜K˜—šžœœ˜"K˜7Kšœ/˜/Kšœœœ˜Kšœœœ˜