@@ -1289,34 +1289,14 @@ function followPath(value: unknown, parent: object | undefined,
12891289 } ;
12901290}
12911291
1292- // StubHook wrapping an RpcPayload in local memory.
1293- //
1294- // This is used for:
1295- // - Resolution of a promise.
1296- // - Initially on the server side, where it can be pull()ed and used in pipelining.
1297- // - On the client side, after pull() has transmitted the payload.
1298- // - Implementing RpcTargets, on the server side.
1299- // - Since the payload's root is an RpcTarget, pull()ing it will just duplicate the stub.
1300- export class PayloadStubHook extends StubHook {
1301- constructor ( payload : RpcPayload ) {
1302- super ( ) ;
1303- this . payload = payload ;
1304- }
1305-
1306- private payload ?: RpcPayload ; // cleared when disposed
1307-
1308- private getPayload ( ) : RpcPayload {
1309- if ( this . payload ) {
1310- return this . payload ;
1311- } else {
1312- throw new Error ( "Attempted to use an RPC StubHook after it was disposed." ) ;
1313- }
1314- }
1292+ // Shared base class for PayloadStubHook and TargetStubHook.
1293+ abstract class ValueStubHook extends StubHook {
1294+ protected abstract getValue ( ) : { value : unknown , owner : RpcPayload | null } ;
13151295
13161296 call ( path : PropertyPath , args : RpcPayload ) : StubHook {
13171297 try {
1318- let payload = this . getPayload ( ) ;
1319- let followResult = followPath ( payload . value , undefined , path , payload ) ;
1298+ let { value , owner } = this . getValue ( ) ;
1299+ let followResult = followPath ( value , undefined , path , owner ) ;
13201300
13211301 if ( followResult . hook ) {
13221302 return followResult . hook . call ( followResult . remainingPath , args ) ;
@@ -1339,8 +1319,8 @@ export class PayloadStubHook extends StubHook {
13391319 try {
13401320 let followResult : FollowPathResult ;
13411321 try {
1342- let payload = this . getPayload ( ) ;
1343- followResult = followPath ( payload . value , undefined , path , payload ) ;
1322+ let { value , owner } = this . getValue ( ) ;
1323+ followResult = followPath ( value , undefined , path , owner ) ; ;
13441324 } catch ( err ) {
13451325 // Oops, we need to dispose the captures of which we took ownership.
13461326 for ( let cap of captures ) {
@@ -1362,19 +1342,67 @@ export class PayloadStubHook extends StubHook {
13621342
13631343 get ( path : PropertyPath ) : StubHook {
13641344 try {
1365- let payload = this . getPayload ( ) ;
1366- let followResult = followPath ( payload . value , undefined , path , payload ) ;
1345+ let { value, owner} = this . getValue ( ) ;
1346+
1347+ if ( path . length === 0 && owner === null ) {
1348+ // The only way this happens is if someone sends "pipeline" and references a
1349+ // TargetStubHook, but they shouldn't do that, because TargetStubHook never backs a
1350+ // promise, and a non-promise cannot be converted to a promise.
1351+ // TODO: Is this still correct for rpc-thenable?
1352+ throw new Error ( "Can't dup an RpcTarget stub as a promise." ) ;
1353+ }
1354+
1355+ let followResult = followPath ( value , undefined , path , owner ) ;
13671356
13681357 if ( followResult . hook ) {
13691358 return followResult . hook . get ( followResult . remainingPath ) ;
13701359 }
13711360
1361+ // Note that if `followResult.owner` is null, then we've descended into the contents of an
1362+ // RpcTarget. In that case, if this deep copy discovers an RpcTarget embedded in the result,
1363+ // it will create a new stub for it. If that RpcTarget has a disposer, it'll be disposed when
1364+ // that stub is disposed. If the same RpcTarget is returned in *another* get(), it create
1365+ // *another* stub, which calls the disposer *another* time. This can be quite weird -- the
1366+ // disposer may be called any number of times, including zero if the property is never read
1367+ // at all. Unfortunately, that's just the way it is. The application can avoid this problem by
1368+ // wrapping the RpcTarget in an RpcStub itself, proactively, and using that as the property --
1369+ // then, each time the property is get()ed, a dup() of that stub is returned.
13721370 return new PayloadStubHook ( RpcPayload . deepCopyFrom (
13731371 followResult . value , followResult . parent , followResult . owner ) ) ;
13741372 } catch ( err ) {
13751373 return new ErrorStubHook ( err ) ;
13761374 }
13771375 }
1376+ }
1377+
1378+ // StubHook wrapping an RpcPayload in local memory.
1379+ //
1380+ // This is used for:
1381+ // - Resolution of a promise.
1382+ // - Initially on the server side, where it can be pull()ed and used in pipelining.
1383+ // - On the client side, after pull() has transmitted the payload.
1384+ // - Implementing RpcTargets, on the server side.
1385+ // - Since the payload's root is an RpcTarget, pull()ing it will just duplicate the stub.
1386+ export class PayloadStubHook extends ValueStubHook {
1387+ constructor ( payload : RpcPayload ) {
1388+ super ( ) ;
1389+ this . payload = payload ;
1390+ }
1391+
1392+ private payload ?: RpcPayload ; // cleared when disposed
1393+
1394+ private getPayload ( ) : RpcPayload {
1395+ if ( this . payload ) {
1396+ return this . payload ;
1397+ } else {
1398+ throw new Error ( "Attempted to use an RPC StubHook after it was disposed." ) ;
1399+ }
1400+ }
1401+
1402+ protected getValue ( ) {
1403+ let payload = this . getPayload ( ) ;
1404+ return { value : payload . value , owner : payload } ;
1405+ }
13781406
13791407 dup ( ) : StubHook {
13801408 // Although dup() is documented as not copying the payload, what this really means is that
@@ -1448,7 +1476,7 @@ type BoxedRefcount = { count: number };
14481476// the root of the payload happens to be an RpcTarget), but there can only be one RpcPayload
14491477// pointing at an RpcTarget whereas there can be several TargetStubHooks pointing at it. Also,
14501478// TargetStubHook cannot be pull()ed, because it always backs an RpcStub, not an RpcPromise.
1451- class TargetStubHook extends StubHook {
1479+ class TargetStubHook extends ValueStubHook {
14521480 // Constructs a TargetStubHook that is not duplicated from an existing hook.
14531481 //
14541482 // If `value` is a function, `parent` is bound as its "this".
@@ -1491,82 +1519,8 @@ class TargetStubHook extends StubHook {
14911519 }
14921520 }
14931521
1494- call ( path : PropertyPath , args : RpcPayload ) : StubHook {
1495- try {
1496- let target = this . getTarget ( ) ;
1497- let followResult = followPath ( target , this . parent , path , null ) ;
1498-
1499- if ( followResult . hook ) {
1500- return followResult . hook . call ( followResult . remainingPath , args ) ;
1501- }
1502-
1503- // It's a local function.
1504- if ( typeof followResult . value != "function" ) {
1505- throw new TypeError ( `'${ path . join ( '.' ) } ' is not a function.` ) ;
1506- }
1507- let promise = args . deliverCall ( < Function > followResult . value , followResult . parent ) ;
1508- return new PromiseStubHook ( promise . then ( payload => {
1509- return new PayloadStubHook ( payload ) ;
1510- } ) ) ;
1511- } catch ( err ) {
1512- return new ErrorStubHook ( err ) ;
1513- }
1514- }
1515-
1516- map ( path : PropertyPath , captures : StubHook [ ] , instructions : unknown [ ] ) : StubHook {
1517- try {
1518- let followResult : FollowPathResult ;
1519- try {
1520- let target = this . getTarget ( ) ;
1521- followResult = followPath ( target , this . parent , path , null ) ;
1522- } catch ( err ) {
1523- // Oops, we need to dispose the captures of which we took ownership.
1524- for ( let cap of captures ) {
1525- cap . dispose ( ) ;
1526- }
1527- throw err ;
1528- }
1529-
1530- if ( followResult . hook ) {
1531- return followResult . hook . map ( followResult . remainingPath , captures , instructions ) ;
1532- }
1533-
1534- return mapImpl . applyMap (
1535- followResult . value , followResult . parent , followResult . owner , captures , instructions ) ;
1536- } catch ( err ) {
1537- return new ErrorStubHook ( err ) ;
1538- }
1539- }
1540-
1541- get ( path : PropertyPath ) : StubHook {
1542- try {
1543- if ( path . length == 0 ) {
1544- // The only way this happens is if someone sends "pipeline" and references a
1545- // TargetStubHook, but they shouldn't do that, because TargetStubHook never backs a
1546- // promise, and a non-promise cannot be converted to a promise.
1547- throw new Error ( "Can't dup an RpcTarget stub as a promise." ) ;
1548- }
1549-
1550- let target = this . getTarget ( ) ;
1551- let followResult = followPath ( target , this . parent , path , null ) ;
1552-
1553- if ( followResult . hook ) {
1554- return followResult . hook . get ( followResult . remainingPath ) ;
1555- }
1556-
1557- // Note that this deep copy, if it discovers an RpcTarget embedded in the result, will create
1558- // a new stub for it. If the RpcTarget has a disposer, it'll be disposed when that stub is
1559- // disposed. If the same RpcTarget is returned in *another* get(), it create *another* stub,
1560- // which calls the disposer *another* time. This can be quite weird -- the disposer may be
1561- // called any number of times, including zero if the property is never read at all.
1562- // Unfortunately, that's just the way it is. The application can avoid this problem by
1563- // wrapping the RpcTarget in an RpcStub itself, proactively, and using that as the property --
1564- // then, each time the property is get()ed, a dup() of that stub is returned.
1565- return new PayloadStubHook ( RpcPayload . deepCopyFrom (
1566- followResult . value , followResult . parent , followResult . owner ) ) ;
1567- } catch ( err ) {
1568- return new ErrorStubHook ( err ) ;
1569- }
1522+ protected getValue ( ) {
1523+ return { value : this . getTarget ( ) , owner : null } ;
15701524 }
15711525
15721526 dup ( ) : StubHook {
0 commit comments