-
Notifications
You must be signed in to change notification settings - Fork 469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add docs best effort responses #3865
base: master
Are you sure you want to change the base?
Conversation
We were missing quite a lot of relevant topics: how to discover canister endpoints, guidance on how to perform calls, attaching cycles, no explanation of why the async/await desugaring was important etc. I also now changed the tag to beginner, as the Rust/Motoko language-specific documents are also rated as "beginner". The previous discussion was not really motivated (why should the user care about desugaring?) and quite language dependent, so I removed most of it - the language dependent stuff should go into the language specific docs. Now added all this and also added the difference between best-effort and guaranteed response calls.
🤖 Here's your preview: https://tai3p-vaaaa-aaaam-abema-cai.icp0.io |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only looked at message-execution-properties.mdx
for now and added some nitpicks.
However, I can't help thinking that the topic could be explained more clearly. E.g. start with calls/tasks vs. message executions as we already do, but before we list any properties; just a couple of pictures with simplified sequence diagrams. That way, the difference between call/task and message execution would be immediately clear. It would also immediately make it clear why two requests Req1 and Req2 delivered in that order may produce two responses Rep2 and Rep1, in reverse order (even before the protocol may decide to deliver them in an arbitrary order).
Very likely not for this PR, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left feedback on everything but message-execution-properties.md
as Alin already made a pass over this one. I'll make a pass once you've addressed Alin's comments.
|
||
## Overview of language runtime for asynchronous code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re "true" response from the callee
: Strictly speaking, it is also not guaranteed in the current messaging model that the response will come from the callee. The response could also be generated by the system if the request couldn't be delivered to the callee.
The property the current messaging model provides is that the response is globally unique, i.e., if the system produces a reject one can conclude that the callee never saw the request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re "true" response from the callee: Strictly speaking, it is also not guaranteed in the current messaging model that the response will come from the callee. The response could also be generated by the system if the request couldn't be delivered to the callee.
True, I dropped the "from the callee" part.
The property the current messaging model provides is that the response is globally unique, i.e., if the system produces a reject one can conclude that the callee never saw the request.
Well technically a CANISTER_ERROR
is also produced by the system but it means that the callee has seen the request (whether any mutation was made on the callee side is anyone's guess, though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, I dropped the "from the callee" part.
FWIW, I still see
in this mode the "true" response from the callee is not guaranteed to be delivered
Maybe you just didn't push your change yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have pushed the change, I see below:
but in this mode the "true" response is not guaranteed to be delivered, and can be masked by a system generated error response instead.
Not sure what's going on 🤷
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/best-practices/idempotency.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/best-practices/idempotency.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/best-practices/idempotency.mdx
Outdated
Show resolved
Hide resolved
Co-authored-by: Jessie Mongeon <[email protected]>
Co-authored-by: Alin Sinpalean <[email protected]>
Co-authored-by: Alin Sinpalean <[email protected]>
Co-authored-by: Alin Sinpalean <[email protected]>
Co-authored-by: Alin Sinpalean <[email protected]>
Co-authored-by: Alin Sinpalean <[email protected]>
::: | ||
|
||
:::info | ||
Note that if the code does not `await` the response, the code after the callback is executed in the same message, until the next inter-canister call is triggered using `await`. | ||
Note that if the code does not `await` the response, the code after the callback is executed in the same message execution, until the next inter-canister call is triggered using `await`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I just find it awkward that we talk about a call or a callback without an await
. If there's no await
, nothing is called, you've merely created a future even though it's not obvious from the syntax.
IOW, leaving it at "the code after the await
is executed as a separate message execution (callback)" (which the paragraph above already says) is IMHO cleaner than adding "but if there's no await
, there's no callback". I would rather change this paragraph to say "note that it is actually the await
, not the thing that looks like a function call that triggers the canister call and marks the separation point between message executions". Or something to that effect.
docs/developer-docs/security/security-best-practices/inter-canister-calls.mdx
Outdated
Show resolved
Hide resolved
|
||
## Overview of language runtime for asynchronous code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, I dropped the "from the callee" part.
FWIW, I still see
in this mode the "true" response from the callee is not guaranteed to be delivered
Maybe you just didn't push your change yet.
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/best-practices/idempotency.mdx
Outdated
Show resolved
Hide resolved
…de.mdx Co-authored-by: Alin Sinpalean <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, left some comments.
docs/developer-docs/security/security-best-practices/inter-canister-calls.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/security/security-best-practices/inter-canister-calls.mdx
Outdated
Show resolved
Hide resolved
Co-authored-by: David Derler <[email protected]>
Co-authored-by: David Derler <[email protected]>
|
||
```bash | ||
dfx canister call subscriber setup_subscribe '(principal "<INSERT_PUBLISHER_PRINCIPAL_HERE>", "Apples")' | ||
$ dfx canister call caller stubborn_set '(42)' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity. Can you make a bounded_wait or unbounded_wait call specifically in dfx?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not exactly sure what you mean, since dfx calls get their results by polling the ingress
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
|
||
Best-effort calls address both of these problems. However, they can require more effort on the part of the caller to handle the additional error case. Here are some guidelines how to choose between the two types of calls: | ||
|
||
* Always prefer best-effort response calls for calls that don't change the state of the callee, i.e., reads. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reads -> queries? Or is there a difference I'm not aware of?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can always mark a read as a query (though you don't have to)
One thing I just noticed: It seems we updated In this context we should also provide some pointers back and forth (to and from the tutorial page) in terms of how to interpret the related "unclean" errors as the ledger also uses panics as a tool for controlledly rolling back. |
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
Co-authored-by: Linwei Shang <[email protected]>
…de.mdx Co-authored-by: David Derler <[email protected]>
…de.mdx Co-authored-by: stiegerc <[email protected]>
|
||
let no_unknowns = true; | ||
loop { | ||
match Call::new(ledger, "icrc1_transfer") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be Call::unbounded_wait
instead of Call::new
? If not, what's the difference between the two?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it should be! Sorry for the confusion (Call:new
is what the CDK alpha version at hand was actually using, so I was going back and forth when checking whether the call compiles, and this slipped through)
|
||
A common problem in both distributed and decentralized systems is keeping separate services (or canisters) synchronized with one another. While there are many potential solutions to this problem, a popular one is the **publisher/subscriber** pattern, or "PubSub". PubSub is an especially valuable pattern on ICP as its primary drawback, message delivery failures, does not apply. | ||
We assume that you have the Internet Computer [developer tools](/docs/current/developer-docs/getting-started/install) installed. To follow along, clone the [Git repository](https://github.com/oggy-dfin/icc_rust_docs) of this tutorial. You can find the completed code in the `caller` package. Check out the Git repository of the tutorial. Then, in the tutorial directory, start a local IC instance in the background and install the counter canister. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We assume that you have the Internet Computer [developer tools](/docs/current/developer-docs/getting-started/install) installed. To follow along, clone the [Git repository](https://github.com/oggy-dfin/icc_rust_docs) of this tutorial. You can find the completed code in the `caller` package. Check out the Git repository of the tutorial. Then, in the tutorial directory, start a local IC instance in the background and install the counter canister. | |
To follow along, clone the [Git repository](https://github.com/oggy-dfin/icc_rust_docs) of this tutorial. You can find the completed code in the `caller` package. | |
Check out the Git repository of the tutorial. Then, in the tutorial directory, start a local IC instance in the background and install the counter canister: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that all the Rust docs have a pointer to the dev tools installation; are we getting rid of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
Before getting started, assure you have set up your developer environment according to the instructions in the [developer environment guide](./dev-env.mdx). | ||
## Basic example: getting and setting the counter value | ||
|
||
For the first example, we'll build a `call_get_and_set` method on the caller canister, which just invokes the `get_and_set` method on the counter project and forwards the result. This method sets the counter to the new value, and returns the previous counter value. The finished code for the project is already available in `src/caller`. You can clear the contents of `src/caller/src/lib.rs`, and paste the following code. The explanation is in the comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the first example, we'll build a `call_get_and_set` method on the caller canister, which just invokes the `get_and_set` method on the counter project and forwards the result. This method sets the counter to the new value, and returns the previous counter value. The finished code for the project is already available in `src/caller`. You can clear the contents of `src/caller/src/lib.rs`, and paste the following code. The explanation is in the comments. | |
Our first example is a `call_get_and_set` method on the caller canister, which simply invokes the `get_and_set` method on the counter project and forwards the result. This method sets the counter to the new value and returns the previous counter value. | |
The finished code for the project is already available in `src/caller`. You can clear the contents of `src/caller/src/lib.rs`, and paste the following code. The explanation is within the comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworded the suggestion slightly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not use "we" or "our" in the Dev Docs. This is a stylistic choice from leadership.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the general intent, but I don't see why we'd need to enforce this blindly. Clearly here it's "our" as "yours, the learner's, and mine, the teacher's", and not as "DFINITY's and definitely not yours". So I'd push back because I don't see the point of why we'd hurt readability, though it's certainly not a hill I'll die on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my understanding, this word choice was enforced due to legal concerns, so I'd like to adhere to it.
|
||
For the first example, we'll build a `call_get_and_set` method on the caller canister, which just invokes the `get_and_set` method on the counter project and forwards the result. This method sets the counter to the new value, and returns the previous counter value. The finished code for the project is already available in `src/caller`. You can clear the contents of `src/caller/src/lib.rs`, and paste the following code. The explanation is in the comments. | ||
|
||
```rust |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this example reference a GItHub link rather than inserting the code manually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but it turns out that GH links are a pain to work with while writing the docs, both for the author and the reviewers. It's especially bad for the reviewers, because they can't see the actual code in the PR, and they can't comment on it unless the author has another PR on whatever repo the GH source is, plus they have to context switch. They're also a pain for the author, because to update an example you need to make a push to another branch somewhere, view the commit in GH, select the appropriate line numbers, and paste back.
I'm aware that I pushed for testable code samples myself :) I still think this is more important than author/reviewer convenience because documentation rots so quickly otherwise, and thanks for taking the time to make the GH plugin work! But given the current experience, my ideal solution would be to have the samples in the SAME repository as the portal, and ideally be able to just reference them using relative paths instead of having to deal with commits. Basically what this plugin claims to provide.
In the absence of the ideal solution, my current idea is to stick all the samples directly in the PR during the review process, and then replace them with links to a GH repo before committing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can reference GH files directly in the repo if they are pulled in as submodules, such as how we reference the files from the @dfinity/examples repo. In a perfect world, every example we have lives in that repo, but that is probably not realistic (or at least not currently).
As long as the plan is to replace the code with links before the PR is merged, that is fine :) Thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I referenced this through a submodule, the reviewers still wouldn't get to see the diff of the changes in the example, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but they would have a corresponding Example repo PR to reference that showed just those changes.
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
docs/developer-docs/smart-contracts/advanced-features/async-code.mdx
Outdated
Show resolved
Hide resolved
Co-authored-by: Jessie Mongeon <[email protected]>
Co-authored-by: Jessie Mongeon <[email protected]>
Co-authored-by: Jessie Mongeon <[email protected]>
DO NOT MERGE YET - this should be merged only once best-effort responses go live on mainnet.
This PR contains the documentation for the new best-effort response feature. In the process, I noticed that we were lacking a good introductory page on inter-canister calls, addressing topics such as:
how do I find which endpoints to call?
what's the role of Candid in inter-canister calls?
why do I care about the desugaring of async/await
attaching cycles to calls
Follows the developer docs style guide.
Follows the best practices and guidelines.
New documentation pages include document tags.
New documentation pages include SEO keywords.
New documents are in the
.mdx
file format to support the previous two components.New documents are registered in
/sidebars.js
, otherwise, it will not appear in theside navigation bar.
Make sure that the
.github/CODEOWNERS
file isfilled with new documents that you added. This way we can ensure that future Pull Requests are reviewed by the right people.