Skip to content
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

Build guide: signals #534

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

Build guide: signals #534

wants to merge 10 commits into from

Conversation

pdaoust
Copy link
Collaborator

@pdaoust pdaoust commented Feb 18, 2025

Closes #471 . Covers both local and remote signals.

@pdaoust pdaoust marked this pull request as ready for review February 18, 2025 22:08
@pdaoust pdaoust requested a review from a team February 18, 2025 22:09
@pdaoust pdaoust enabled auto-merge (squash) February 18, 2025 22:15
client.on("signal", (signal: Signal) => {
// There's currently only one useful signal type to listen for -- an
// app signal.
if (!(SignalType.App in signal)) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is about to change in holochain 0.5 with the new enum serialization. Just as a heads up. It will be something like

if (signal.type !== "app") return;

## Remote signals

Agents can also send remote signals to each other using the [`send_remote_signal`](https://docs.rs/hdk/latest/hdk/p2p/fn.send_remote_signal.html) host function and a [`recv_remote_signal` callback](/build/callbacks-and-lifecycle-hooks/#define-a-recv-remote-signal-callback), which takes a single argument of any type and returns `ExternResult<()>`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be worth writing here about the distinction from a remote call, i.e. that these remote signals are not waiting for a response whatsoever and have no guarantee of delivery.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes agree that should be documented but the same is actually true of local signals. No guarantees in either case. I've commented similarly above.

pub fn recv_remote_signal(payload: RemoteSignal) -> ExternResult<()> {
if let RemoteSignal::Heartbeat = payload {
let caller = call_info()?.provenance;
emit_signal(LocalSignal::Heartbeat(caller))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
emit_signal(LocalSignal::Heartbeat(caller))?;
// On the receiving end we forward the remote signal to the front-end by emitting a local signal
emit_signal(LocalSignal::Heartbeat(caller))?;

```

!!! info Remote signal handlers are just zome functions
`send_remote_signal` is sugar for a [remote call](/build/calling-zome-functions/#call-a-zome-function-from-another-agent-in-the-network) to a zome function named `recv_remote_signal` with no capability secret<!-- TODO: link to capabilities page -->. It works differently from a usual remote call, though, in that it's 'send-and-forget' --- it won't return an error if anything fails. In practice, these two are equivalent:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

send_remote_signal is sugar for a remote call to a zome function named recv_remote_signal with no capability secret

Is this really correct? Because as written above, there is no timeout involved so I'm wondering whether there is actually a difference of how it's implemented under the hood but I'm not familiar with that part of the holochain code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm actually sceptical that they are equivalent in practice. Have you tried that out, including situations where peers to which a heartbeat is sent are offline?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmmm, I forgot that there's a timeout that gets ignored at the core/ribosome level. Good point.


### Emit a signal

Your coordinator zome emits a signal with the [`emit_signal`](https://docs.rs/hdk/latest/hdk/p2p/fn.emit_signal.html) host function. You can call this function from a regular [zome function](/build/zome-functions/) or the [`init`](/build/callbacks-and-lifecycle-hooks/#define-an-init-callback), [`recv_remote_signal`](/build/callbacks-and-lifecycle-hooks/#define-a-recv-remote-signal-callback), or [`post_commit`](/build/callbacks-and-lifecycle-hooks/#define-a-post-commit-callback) callbacks.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Your coordinator zome emits a signal with the [`emit_signal`](https://docs.rs/hdk/latest/hdk/p2p/fn.emit_signal.html) host function. You can call this function from a regular [zome function](/build/zome-functions/) or the [`init`](/build/callbacks-and-lifecycle-hooks/#define-an-init-callback), [`recv_remote_signal`](/build/callbacks-and-lifecycle-hooks/#define-a-recv-remote-signal-callback), or [`post_commit`](/build/callbacks-and-lifecycle-hooks/#define-a-post-commit-callback) callbacks.
Your coordinator zome emits a signal with the [`emit_signal`](https://docs.rs/hdk/latest/hdk/p2p/fn.emit_signal.html) host function. It takes any serializable input and you can call this function from a regular [zome function](/build/zome-functions/) or the [`init`](/build/callbacks-and-lifecycle-hooks/#define-an-init-callback), [`recv_remote_signal`](/build/callbacks-and-lifecycle-hooks/#define-a-recv-remote-signal-callback), or [`post_commit`](/build/callbacks-and-lifecycle-hooks/#define-a-post-commit-callback) callbacks.

Copy link
Member

@ThetaSinner ThetaSinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good. I have a few minor comments but mostly happy. I like how focused this one is on giving code examples and just enough description.


### Listen for a signal

The UI subscribes to signals with the [`AppWebsocket.prototype.on`](https://github.com/holochain/holochain-client-js/blob/main/docs/client.appwebsocket.on.md) method. The signal handler should expect signals from any coordinator zome in any cell in the agent's hApp instance, and can discriminate between them by cell ID and zome name.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to say something like "Signals are emitted on by Holochain on connected app websockets. A Holochain client will provide a way to receive these signals." Then, "For example, with the TypeScript client..."

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of my reason for that, is something I can see is missing. If there are no connected app websockets then Holochain doesn't buffer messages. There's no guaranteed delivery, and no way for the app to know if anybody was listening. It's just a broadcast and anything that happens to receive it can act on the information.

I think this would be one of the gotchas that the ticket was asking for.

import type { Signal, AppSignal, AgentPubKey } from "@holochain/client";
import { SignalType, encodeHashToBase64 } from "@holochain/client";

// Duplicate your zome's signal types in the UI.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Duplicate your zome's signal types in the UI.
// Represent your zome's signal types in the UI.

Maybe? It's not really duplication, they can't share code really. People are also free to set up code generation if they want.

Comment on lines +59 to +60
// There's currently only one useful signal type to listen for -- an
// app signal.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just leave this as "Signals coming from a coordinator are of the App type"? I don't think it's that accurate to say system signals aren't useful even though they're currently only used in countersigning. Then there's also the maintenance of comments like this as Holochain changes. The chances that we'll remember to update this is low.

## Remote signals

Agents can also send remote signals to each other using the [`send_remote_signal`](https://docs.rs/hdk/latest/hdk/p2p/fn.send_remote_signal.html) host function and a [`recv_remote_signal` callback](/build/callbacks-and-lifecycle-hooks/#define-a-recv-remote-signal-callback), which takes a single argument of any type and returns `ExternResult<()>`.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes agree that should be documented but the same is actually true of local signals. No guarantees in either case. I've commented similarly above.

```

!!! info Remote signal handlers are just zome functions
`send_remote_signal` is sugar for a [remote call](/build/calling-zome-functions/#call-a-zome-function-from-another-agent-in-the-network) to a zome function named `recv_remote_signal` with no capability secret<!-- TODO: link to capabilities page -->. It works differently from a usual remote call, though, in that it's 'send-and-forget' --- it doesn't block execution waiting for a response, and it doesn't return an error if anything fails. Other than that, the following two are roughly equivalent.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`send_remote_signal` is sugar for a [remote call](/build/calling-zome-functions/#call-a-zome-function-from-another-agent-in-the-network) to a zome function named `recv_remote_signal` with no capability secret<!-- TODO: link to capabilities page -->. It works differently from a usual remote call, though, in that it's 'send-and-forget' --- it doesn't block execution waiting for a response, and it doesn't return an error if anything fails. Other than that, the following two are roughly equivalent.
`send_remote_signal` is sugar for a [remote call](/build/calling-zome-functions/#call-a-zome-function-from-another-agent-in-the-network) to a zome function named `recv_remote_signal`. This target function exists by convention and must be given an `Unrestricted` capability grant for this to work. <!-- TODO: link to capabilities page -->. It works differently from a usual remote call, though, in that it's 'send-and-forget' --- it doesn't block execution waiting for a response, and it doesn't return an error if anything fails. Other than that, the following two are roughly equivalent.

}
```

This means an agent needs to set up an [`Unrestricted` capability grant](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/capability/enum.CapAccess.html#variant.Unrestricted)<!--TODO: link to capabilities page --> for it, so other agents can call it. Take care that this function does as little as possible, to avoid people abusing it. Permissions and privileges are another topic which we'll talk about soon.<!-- TODO: delete this sentence -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh okay, we're saying something about this in two places, maybe we can unify?

}

#[hdk_extern]
pub fn send_heartbeat(receivers: Vec<AgentPubKey>) -> ExternResult<()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're documenting with code like this, is it explicitly worth calling out that this forces Holochain to open and maintain connections for the heartbeat? So you wouldn't want logic that connects to everyone who is using your app. Holochain isn't designed to operate that many connections. It's fine like we do for syn with 2-10 people updating stickies, but in general you shouldn't be deliberately opening connections to every agent on the network.

Maybe that's out of scope or maybe that's a motivation to keep code samples even more minimal? This could just be a message getting sent, rather than a pattern

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Practical docs for using Signals
3 participants