1function lqn = lqn_fwd_rendezvous(lqn)
2% LQN = LQN_FWD_RENDEZVOUS(LQN)
3% Port of LQNS Phase::addForwardingRendezvous (phase.cc): replace each
4% forwarding chain reachable from a synchronous call by caller-side pseudo
5% rendezvous (SYNC) calls to the forwarding targets, with mean equal to the
6% original call mean times the product of the forwarding probabilities on
7% the path. After this transformation the forwarded workload
is carried by
8% ordinary SYNC call classes, so layer construction, think times,
9% populations and the interlock analysis all see plain rendezvous arcs.
10% This matches LQNS, which drops FWD arcs from the interlock analysis
11% (
"Drop forward -- keep rnv", interlock.cc) and accounts for forwarding
12% only through these pseudo arcs. FWD calls are kept in the struct but no
13% longer contribute blocking anywhere in SolverLN.
15% Asynchronous calls into a forwarding chain are left untouched (LQNS
16% breaks the backward search at a send-no-reply, phase.cc).
18if ~any(lqn.calltype == CallType.FWD)
24 if lqn.calltype(cidx) ~= CallType.SYNC
27 aidx = lqn.callpair(cidx,1);
28 tidx = lqn.parent(aidx);
29 base_mean = lqn.callproc_mean(cidx);
33 % BFS through the forwarding chain of the sync target
34 frontier = lqn.callpair(cidx,2);
37 while ~isempty(frontier)
38 eidx = frontier(1); frontier(1) = [];
39 p_path = probs(1); probs(1) = [];
40 if ismember(eidx, visitedE),
continue; end
41 visitedE(end+1) = eidx; %#ok<AGROW>
43 if lqn.calltype(fcidx) ~= CallType.FWD || lqn.callpair(fcidx,1) ~= eidx
46 fprob = lqn.callproc_mean(fcidx);
47 tgt = lqn.callpair(fcidx,2);
48 pseudo_mean = base_mean * p_path * fprob;
49 if pseudo_mean > 0 && lqn.parent(tgt) ~= tidx
50 % see _kb/06-solver-catalog.md (LN section)
for rationale
52 for scan = 1:lqn.ncalls
53 if lqn.calltype(scan) == CallType.SYNC && ...
54 lqn.callpair(scan,1) == aidx && lqn.callpair(scan,2) == tgt
60 newmean = lqn.callproc_mean(mrow) + pseudo_mean;
61 d = Geometric(1/newmean);
62 lqn.callproc{mrow,1} = d;
63 lqn.callproc_mean(mrow) = newmean;
64 lqn.callproc_scv(mrow) = d.getSCV();
66 ncall = lqn.ncalls + 1;
68 target_tidx = lqn.parent(tgt);
69 d = Geometric(1/pseudo_mean);
70 lqn.calltype(ncall,1) = CallType.SYNC;
71 lqn.callpair(ncall,1:2) = [aidx, tgt];
72 lqn.callnames{ncall,1} = [lqn.names{aidx},
'=>',lqn.names{tgt}];
73 lqn.callhashnames{ncall,1} = [lqn.hashnames{aidx},
'=>',lqn.hashnames{tgt}];
74 lqn.callproc{ncall,1} = d;
75 % Only the mean (and the process
object) are consumed by
76 % SolverLN; mirror the base call
for the remaining fields
77 lqn.callproc_type(ncall) = lqn.callproc_type(cidx);
78 lqn.callproc_params{ncall} = lqn.callproc_params{cidx};
79 lqn.callproc_mean(ncall) = pseudo_mean;
80 lqn.callproc_scv(ncall) = d.getSCV();
81 lqn.callproc_proc{ncall} = lqn.callproc_proc{cidx};
82 lqn.callsof{aidx}(end+1) = ncall;
83 lqn.iscaller(tidx, target_tidx) =
true;
84 lqn.iscaller(aidx, target_tidx) =
true;
85 lqn.iscaller(tidx, tgt) =
true;
86 lqn.iscaller(aidx, tgt) =
true;
87 lqn.issynccaller(tidx, target_tidx) =
true;
88 lqn.issynccaller(aidx, target_tidx) =
true;
89 lqn.issynccaller(tidx, tgt) =
true;
90 lqn.issynccaller(aidx, tgt) =
true;
91 lqn.graph(aidx, tgt) = 1;
92 lqn.taskgraph(tidx, target_tidx) = 1;
96 if ~ismember(tgt, visitedE) && ~ismember(tgt, frontier)
97 frontier(end+1) = tgt; %#ok<AGROW>
98 probs(end+1) = p_path * fprob; %#ok<AGROW>