Discussion:
[capnproto] RPC: simple return values and a "finish" roundtrip
g***@gmail.com
2017-08-08 08:40:22 UTC
Permalink
Hi everyone,

I apologize in advance if I have missed this in the docs or other thread,
but I have not been able to understand why, when a method gets called,
there are three packets being sent: call (@2), return (@3), finish (@4)
even if no capabilities are involved (except for the bootstrap). The setup
I have used is just plain "Hello world" type bootstrap interface like
"interface Hello { hello @0 (a :Text) -> (a :Text); }" and I saw it in
Python, C++ and Rust. (Same effect with any numeric call/return type).
(Observing the network traffic with wireshark.)

Why is there the need for a finish message even when the returned content
contains no capabilities? (I figure that message may only point to caps
declared in its capTable.)

When the return payload contains no capability, is it possible to somehow
reuse the server-side content? (I did not figure out how, wireshark shows
the data being sent back and forth.) Is anything from the reply actually
stored server-side after the server sends the return without caps?

Until the client does delete the replied payload (for example because it
wants to keep the data in capnp without copying),,the finish is not sent
which might require the server to retain some info on the call (used ID? or
even the data?), even though I have not found it in the sources.

(I believe it is not because of IDs, as for every client independently, if
the server receives e.g. an abort (actually a finish (@4)) with an ID it
has not receiver a call it has not returned yet, it can be sure the abort
is meant for an ID it has returned recently and can ignore the abort.)

Note that this is not a big performance problem for my use-case but since
we are mostly sending simple one-time messages between long-lived caps, it
seems like a waste.

Thanks for any clarification and all the great work!
Tomáš Gavenčiak
--
You received this message because you are subscribed to the Google Groups "Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to capnproto+***@googlegroups.com.
Visit this group at https://groups.google.com/group/capnproto.
'Kenton Varda' via Cap'n Proto
2017-08-08 16:24:15 UTC
Permalink
Hi Tomáš,

Indeed, there may be some room for optimization here. It's a little tricky
since *normally* the rule is that Question table entries are allocated and
freed strictly by the caller, hence requiring a Call and a Finish. If we
allow releasing a table entry to happen on the opposite side from
allocation (as we do with the export/import tables) then we have to think
carefully about race conditions.

Perhaps we could add a bool to Return which allows the callee to say: "I
don't need you to send a Finish." The callee would only set this bool in
cases where there are no capabilities at all in the results. It would mark
the table entry as "optimistically freed"* and could release all associated
resources.

The caller would still be allowed to send a Finish message, which it might
do for two reasons:
- Because it sent Finish before it had received Return, in an attempt to
cancel the call.
- Because it doesn't implement the new flag, so always sends Finish.

So the callee should be prepared to receive a Finish for an "optimistically
freed" table entry, in which case it changes the table entry's state to
plain "free". It should also be prepared to receive no Finish, which means
it may later receive a Call that re-allocates the table entry.

Also, of course, the callee may receive promise-pipeline messages
referencing this table entry. This would happen if the caller *expected*
the results to contain a capability, which could be the case even if the
results ultimately did not contain any such entry. In this case the callee
would treat any pipelining attempts as if they were trying to pipeline on a
null capability.

I think this could work, but to be sure I'd have to review all the other
places where question IDs are referenced.

* Technically, it's not necessary for "optimistically freed" and "free" to
be separate states, but at least for common low-numbered table entries this
should be "almost free" to store and may help catch bugs.

-Kenton
Post by g***@gmail.com
Hi everyone,
I apologize in advance if I have missed this in the docs or other thread,
but I have not been able to understand why, when a method gets called,
even if no capabilities are involved (except for the bootstrap). The setup
I have used is just plain "Hello world" type bootstrap interface like
Python, C++ and Rust. (Same effect with any numeric call/return type).
(Observing the network traffic with wireshark.)
Why is there the need for a finish message even when the returned content
contains no capabilities? (I figure that message may only point to caps
declared in its capTable.)
When the return payload contains no capability, is it possible to somehow
reuse the server-side content? (I did not figure out how, wireshark shows
the data being sent back and forth.) Is anything from the reply actually
stored server-side after the server sends the return without caps?
Until the client does delete the replied payload (for example because it
wants to keep the data in capnp without copying),,the finish is not sent
which might require the server to retain some info on the call (used ID? or
even the data?), even though I have not found it in the sources.
(I believe it is not because of IDs, as for every client independently, if
has not receiver a call it has not returned yet, it can be sure the abort
is meant for an ID it has returned recently and can ignore the abort.)
Note that this is not a big performance problem for my use-case but since
we are mostly sending simple one-time messages between long-lived caps, it
seems like a waste.
Thanks for any clarification and all the great work!
Tomáš Gavenčiak
--
You received this message because you are subscribed to the Google Groups
"Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at https://groups.google.com/group/capnproto.
--
You received this message because you are subscribed to the Google Groups "Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to capnproto+***@googlegroups.com.
Visit this group at https://groups.google.com/group/capnproto.
Tomáš Gavenčiak
2017-08-09 16:24:33 UTC
Permalink
Hi Kenton,

thanks for the quick reply and clarification! I was not 100% sure the
server-side result (without capabilities) may not be reused in some way. I
was thinking about some flag system and I like your solution. (I was
thinking about a caller-set feature flag in addition but it is likely not
necessary).

How can I help with specifying and implementing this? I would be happy to
give it a shot but would be grateful for some guidance. And how does it
align with any future plans you have for capnp?

All the best,
Tomáš
Post by 'Kenton Varda' via Cap'n Proto
Hi Tomáš,
Indeed, there may be some room for optimization here. It's a little tricky
since *normally* the rule is that Question table entries are allocated and
freed strictly by the caller, hence requiring a Call and a Finish. If we
allow releasing a table entry to happen on the opposite side from
allocation (as we do with the export/import tables) then we have to think
carefully about race conditions.
Perhaps we could add a bool to Return which allows the callee to say: "I
don't need you to send a Finish." The callee would only set this bool in
cases where there are no capabilities at all in the results. It would mark
the table entry as "optimistically freed"* and could release all associated
resources.
The caller would still be allowed to send a Finish message, which it might
- Because it sent Finish before it had received Return, in an attempt to
cancel the call.
- Because it doesn't implement the new flag, so always sends Finish.
So the callee should be prepared to receive a Finish for an
"optimistically freed" table entry, in which case it changes the table
entry's state to plain "free". It should also be prepared to receive no
Finish, which means it may later receive a Call that re-allocates the table
entry.
Also, of course, the callee may receive promise-pipeline messages
referencing this table entry. This would happen if the caller *expected*
the results to contain a capability, which could be the case even if the
results ultimately did not contain any such entry. In this case the callee
would treat any pipelining attempts as if they were trying to pipeline on a
null capability.
I think this could work, but to be sure I'd have to review all the other
places where question IDs are referenced.
* Technically, it's not necessary for "optimistically freed" and "free" to
be separate states, but at least for common low-numbered table entries this
should be "almost free" to store and may help catch bugs.
-Kenton
Post by g***@gmail.com
Hi everyone,
I apologize in advance if I have missed this in the docs or other thread,
but I have not been able to understand why, when a method gets called,
even if no capabilities are involved (except for the bootstrap). The setup
I have used is just plain "Hello world" type bootstrap interface like
Python, C++ and Rust. (Same effect with any numeric call/return type).
(Observing the network traffic with wireshark.)
Why is there the need for a finish message even when the returned content
contains no capabilities? (I figure that message may only point to caps
declared in its capTable.)
When the return payload contains no capability, is it possible to somehow
reuse the server-side content? (I did not figure out how, wireshark shows
the data being sent back and forth.) Is anything from the reply actually
stored server-side after the server sends the return without caps?
Until the client does delete the replied payload (for example because it
wants to keep the data in capnp without copying),,the finish is not sent
which might require the server to retain some info on the call (used ID? or
even the data?), even though I have not found it in the sources.
(I believe it is not because of IDs, as for every client independently,
has not receiver a call it has not returned yet, it can be sure the abort
is meant for an ID it has returned recently and can ignore the abort.)
Note that this is not a big performance problem for my use-case but since
we are mostly sending simple one-time messages between long-lived caps, it
seems like a waste.
Thanks for any clarification and all the great work!
Tomáš Gavenčiak
--
You received this message because you are subscribed to the Google Groups
"Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at https://groups.google.com/group/capnproto.
--
You received this message because you are subscribed to the Google Groups "Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to capnproto+***@googlegroups.com.
Visit this group at https://groups.google.com/group/capnproto.
'Kenton Varda' via Cap'n Proto
2017-08-11 23:05:56 UTC
Permalink
Hi Tomáš,

I think a flag in Return will work better than one in Call because the
library doesn't know whether the caller plans to use pipelining at the time
that the Call is sent, but on the callee end the library definitely knows
when the results don't contain any capabilities.

I think this would be a good optimization to implement.

I would add a field to Return like:

finishNeeded @8 :Bool = true;


Doc comment:

If true, the receiver must send a Finish message for this call in order to
release resources and before reusing the question ID. If false, then a
Finish message is optional; the receiver can choose not to send a Finish
message, in which case it is free to reuse the question ID in a new Call
immediately.

This is useful to implement an optimization: If the call results contain no
capabilities, then there's no need for the callee to retain any state about
the call after the Return message is sent, and therefore no reason for the
caller to send a Finish. Any attempts to pipeline on the original call are
clearly invalid (because there's nothing to pipeline on), and there are no
capabilities that need to be released.


Then update whichever implementations you care about. Note that Python uses
the C++ implementation. rpc.c++ is... pretty complicated, but it might not
be too hard to add this flag. You'll also want to add a test, of course.

Alternatively, file an issue and I'll implement it when I have a chance.

-Kenton
Post by Tomáš Gavenčiak
Hi Kenton,
thanks for the quick reply and clarification! I was not 100% sure the
server-side result (without capabilities) may not be reused in some way. I
was thinking about some flag system and I like your solution. (I was
thinking about a caller-set feature flag in addition but it is likely not
necessary).
How can I help with specifying and implementing this? I would be happy to
give it a shot but would be grateful for some guidance. And how does it
align with any future plans you have for capnp?
All the best,
Tomáš
Post by 'Kenton Varda' via Cap'n Proto
Hi Tomáš,
Indeed, there may be some room for optimization here. It's a little
tricky since *normally* the rule is that Question table entries are
allocated and freed strictly by the caller, hence requiring a Call and a
Finish. If we allow releasing a table entry to happen on the opposite side
from allocation (as we do with the export/import tables) then we have to
think carefully about race conditions.
Perhaps we could add a bool to Return which allows the callee to say: "I
don't need you to send a Finish." The callee would only set this bool in
cases where there are no capabilities at all in the results. It would mark
the table entry as "optimistically freed"* and could release all associated
resources.
The caller would still be allowed to send a Finish message, which it
- Because it sent Finish before it had received Return, in an attempt to
cancel the call.
- Because it doesn't implement the new flag, so always sends Finish.
So the callee should be prepared to receive a Finish for an
"optimistically freed" table entry, in which case it changes the table
entry's state to plain "free". It should also be prepared to receive no
Finish, which means it may later receive a Call that re-allocates the table
entry.
Also, of course, the callee may receive promise-pipeline messages
referencing this table entry. This would happen if the caller *expected*
the results to contain a capability, which could be the case even if the
results ultimately did not contain any such entry. In this case the callee
would treat any pipelining attempts as if they were trying to pipeline on a
null capability.
I think this could work, but to be sure I'd have to review all the other
places where question IDs are referenced.
* Technically, it's not necessary for "optimistically freed" and "free"
to be separate states, but at least for common low-numbered table entries
this should be "almost free" to store and may help catch bugs.
-Kenton
Post by g***@gmail.com
Hi everyone,
I apologize in advance if I have missed this in the docs or other
thread, but I have not been able to understand why, when a method gets
setup I have used is just plain "Hello world" type bootstrap interface like
Python, C++ and Rust. (Same effect with any numeric call/return type).
(Observing the network traffic with wireshark.)
Why is there the need for a finish message even when the returned
content contains no capabilities? (I figure that message may only point to
caps declared in its capTable.)
When the return payload contains no capability, is it possible to
somehow reuse the server-side content? (I did not figure out how, wireshark
shows the data being sent back and forth.) Is anything from the reply
actually stored server-side after the server sends the return without caps?
Until the client does delete the replied payload (for example because it
wants to keep the data in capnp without copying),,the finish is not sent
which might require the server to retain some info on the call (used ID? or
even the data?), even though I have not found it in the sources.
(I believe it is not because of IDs, as for every client independently,
has not receiver a call it has not returned yet, it can be sure the abort
is meant for an ID it has returned recently and can ignore the abort.)
Note that this is not a big performance problem for my use-case but
since we are mostly sending simple one-time messages between long-lived
caps, it seems like a waste.
Thanks for any clarification and all the great work!
Tomáš Gavenčiak
--
You received this message because you are subscribed to the Google
Groups "Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send
Visit this group at https://groups.google.com/group/capnproto.
--
You received this message because you are subscribed to the Google Groups "Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to capnproto+***@googlegroups.com.
Visit this group at https://groups.google.com/group/capnproto.
Loading...