Skip to content
This repository has been archived by the owner on Feb 3, 2023. It is now read-only.

Signals (part 1) #982

Merged
merged 71 commits into from Apr 15, 2019
Merged

Signals (part 1) #982

merged 71 commits into from Apr 15, 2019

Conversation

maackle
Copy link
Member

@maackle maackle commented Feb 10, 2019

This adds a notion of Broadcaster, which is something that knows how to send a Signal back to the client. Each Interface implementation's run method returns a Broadcaster. Then, if the Conductor is set up for it, it will start a new thread which continually consumes the signal channel and sends each signal over every interface via its Broadcaster.

In order to maintain backwards compatibility with nodejs_conductor, the conductor can be configured in one of three states, with regard to signals:

  1. Signal Receiver is owned externally -- this is what nodejs_conductor does, managing its own signal stream
  2. Signal Receiver is owned internally by the Conductor API -- this is the new behavior which allows signals to be broadcast over interfaces automatically
  3. Uninitialized

This PR also includes a new module core_types::ugly, which contains a type Initable, representing a value that may or may not be initialized. It is isomorphic to Option. With this type, we can better keep track of which values are truly Optional, and which ones we just used Option for to represent invalid or uninitialized state.

Question

Seems that signals need to be serializable (into JsonString), and currently Action is a big ol' non-serializable struct which gets passed as a signal. Should we bite the bullet and actually implement Serialize for Action and allll its sub-structs, or somehow punt on that since we don't need that for Holo?

If we do want Action to be serializable eventually, let's do it now. Otherwise, we can punt, but that would also be a bit of work, probably meaning we have to manually implement Serialize for Signal and just return some dummy value for the case of Signal::Internal.

  • I have added a summary of my changes to the changelog no new user-facing functionality, only groundwork

Nico's additions

fixes #739

@Connoropolous Connoropolous added the WIP work in progress label Feb 12, 2019
Holo(JsonString),
}

impl Serialize for Signal {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have to implement this by hand? If so, why do we?

Copy link
Member Author

Choose a reason for hiding this comment

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

@sphinxc0re because we can't immediately derive Serialize for Action, which Signal::Internal contains. This is a temporary way to move forward without implementing Serialize for the very big Action enum.

core_types/src/ugly.rs Outdated Show resolved Hide resolved
@maackle maackle changed the title [WIP] Signals (part 1) Signals (part 1) Feb 12, 2019
@maackle maackle added review and removed WIP work in progress labels Feb 12, 2019
Copy link
Collaborator

@Connoropolous Connoropolous left a comment

Choose a reason for hiding this comment

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

My big question is where does the need for this arise?
"https://github.com/holochain/jsonrpc", branch = "broadcaster-getter"
and what is the work of upkeep for that?

@@ -23,8 +23,9 @@ serde_json = { version = "1.0", features = ["preserve_order"] }
toml = "0.4.8"
boolinator = "2.4"
tiny_http = "0.6.0"
jsonrpc-ws-server = { git = "https://github.com/paritytech/jsonrpc" }
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc" }
jsonrpc-core = { git = "https://github.com/holochain/jsonrpc", branch = "broadcaster-getter" }
Copy link
Collaborator

Choose a reason for hiding this comment

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

what's the fork for?

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

We'll have to remember to keep this fork up to date with jsonrpc upstream

@@ -197,7 +197,7 @@ impl Instance {
*state = new_state;
}

// context.log(format!("trace/reduce: {:?}", action_wrapper.action()));
context.log(format!("trace/reduce: {:?}", action_wrapper.action()));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are you playing tag on this line of code? Didn't we uncomment it once now it's recommented and now you're uncommenting it again?

Copy link
Member Author

Choose a reason for hiding this comment

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

I added an "exclude: ^trace/" rule to LogRules::default, which will allow this line to stay in, what do you think @Connoropolous @zippy ?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, approved.

conductor_api/src/conductor/broadcaster.rs Show resolved Hide resolved
// @TODO: if needed for performance, could add a filter predicate here
// to prevent emitting too many unneeded signals
let signal = Signal::Internal(action);
tx.send(signal).unwrap_or(())
// @TODO: once logging is implemented, kick out a warning for SendErrors
tx.send(signal).unwrap_or_else(|_e| {
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

let _ = log_sender.send((String::from("conductor"), message));
error
})
let (kill_switch_tx, kill_switch_rx) = channel();
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you say something (and possibly in a code comment documented) about the kill_switch? where/when does it get called?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nothing new here, just gave it a more descriptive name. I'll find a place to add a comment.

.start_http(&url.parse().expect("Invalid URL!"))
.map_err(|e| e.to_string())?;
let _ = kill_switch.recv();
Ok(())
let broadcaster = Broadcaster::Noop;
Copy link
Collaborator

Choose a reason for hiding this comment

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

So HTTP implements a broadcaster but it's really just a no-op. So you've also set up a pattern that's extendable into different interfaces being implemented...

Copy link
Member

Choose a reason for hiding this comment

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

This sounds right. @maackle it seems like this same method could be used to implement a LongPollHttpInterface that could do broadcasting over http, it just waits to "send" the messages when it gets a poll http request.

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly. Each new Interface has the opportunity to also specify a Broadcaster, or use Noop if it doesn't support two-way messaging.

Copy link
Collaborator

@pdaoust pdaoust Apr 17, 2019

Choose a reason for hiding this comment

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

You could use it to do HTTP/2 server-sent events too...

Copy link
Member

@zippy zippy left a comment

Choose a reason for hiding this comment

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

approved with minor docs request!

}

pub fn config(&self) -> Configuration {
self.config.clone()
}

pub fn start_signal_broadcast(&mut self, signal_rx: SignalReceiver) -> thread::JoinHandle<()> {
Copy link
Member

Choose a reason for hiding this comment

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

this function I think needs some rusdoc comments because it's a pub function and feels like an important place where people will look.

conductor_api/src/conductor/base.rs Outdated Show resolved Hide resolved
.start_http(&url.parse().expect("Invalid URL!"))
.map_err(|e| e.to_string())?;
let _ = kill_switch.recv();
Ok(())
let broadcaster = Broadcaster::Noop;
Copy link
Member

Choose a reason for hiding this comment

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

This sounds right. @maackle it seems like this same method could be used to implement a LongPollHttpInterface that could do broadcasting over http, it just waits to "send" the messages when it gets a poll http request.

@@ -222,6 +252,7 @@ impl Conductor {
}

/// Stop and clear all instances
/// @QUESTION: why don't we care about errors on shutdown?
Copy link
Member Author

Choose a reason for hiding this comment

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

@lucksus I put this in randomly to remind me to ask: I remember this got changed in a PR, but it feels wrong, why are we ignoring the possibility of shutdown errors?

@lucksus
Copy link
Collaborator

lucksus commented Apr 12, 2019

@Connoropolous, the unit under test would be the code that facilitates sending data "backwards" through the websocket. We could test that, as I said, by spawning a conductor and talking to it via a websocket. I actually added some more commits in preparation for writing such a test but I'm a bit stuck finding the right websocket client crate that we could use in such a test. IMHO it would make more sense to test that from JS, which is exactly what we want to do and what this is a preparation step for.

@@ -968,7 +971,11 @@ impl ConductorApiBuilder {
}

pub trait Interface {
Copy link
Collaborator

Choose a reason for hiding this comment

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

@lucksus or @maackle can we get a descriptive code comment of this trait here, since it could be important in the future, for implementing other interfaces?

It can be pulled roughly from the description of the PR I think

Copy link
Collaborator

@Connoropolous Connoropolous left a comment

Choose a reason for hiding this comment

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

Almost good! Just some code comment requests

conductor_api/src/signal_wrapper.rs Show resolved Hide resolved
Err("Error while trying to create instance \"test-instance-1\": Key from file \'holo_tester1.key\' (\'HcSCI7T6wQ5t4nffbjtUk98Dy9fa79Ds6Uzg8nZt8Fyko46ikQvNwfoCfnpuy7z\') does not match public address HoloTester1-----------------------------------------------------------------------AAACZp4xHB mentioned in config!"
.to_string()),
);
}

#[test]
fn test_signals_through_admin_websocket() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

@Connoropolous, I've added this test that shows signals received via websocket.

@Connoropolous
Copy link
Collaborator

Connoropolous commented Apr 12, 2019 via email

Copy link
Collaborator

@Connoropolous Connoropolous left a comment

Choose a reason for hiding this comment

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

with the integration of my code comment suggestion, I'm an approve

conductor_api/src/interface.rs Show resolved Hide resolved
@lucksus lucksus merged commit 88b98ca into develop Apr 15, 2019
@zippy zippy deleted the signals-1 branch October 4, 2019 18:36
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clean up interface for creating Container; leverage the type system
6 participants