-
Notifications
You must be signed in to change notification settings - Fork 267
Conversation
…ible API to parameterize constructed interface intended for bridging
…ntextBuilder in lots of places, where possible (added ContextBuilder::with_logger)
…ntexts. (instantiate_from_config() is tested via container.load_config)
container_api/src/container.rs
Outdated
let callee_config = config.instance_by_id(&bridge.callee_id) | ||
.expect("config.check_consistency()? jumps out if config is broken"); | ||
let callee_instance = self.instances.get(&bridge.callee_id) | ||
.expect(r#" |
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 we are returning a result, do we need these expects? same question of asserts
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 those expectations aren't met, it would not be a runtime error but a fundamental mistake in this approach. I assert that this can never happen with the current code. If it would we need to learn about it asap and fix it. If this panics because of changes to the code base I would want the developer to introduce changes to get drawn to this code and see what's going 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.
I like the simplification of passing around a bare IoHandler
rather than trying to clothe it in another type, I was thinking of doing that too but just wanted to get it out the door so I'm glad you had occasion to touch it and fix that.
Looks good overall but do you think the ContainerApiBuilder
is perhaps a bit too expressive? How often will somebody need to add a single named instance, à la carte? The concern I have with this is that the instances and the instance configs are stored in separate HashMaps which can contradict each other since they can be built up separately. This line would then lead to silent failure.
If I'm reading correctly I would rather see the instances and their configs more tightly coupled, either by requiring instance and config to be passed together, or even having a single HashMap with tuple values for both instance and config. What do you think about this concern?
|
||
/// Creates one specific Holochain instance from a given Configuration, | ||
/// id string and DnaLoader. | ||
pub fn instantiate_from_config( |
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.
Curious why you moved this inside as a method?
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.
Because it now depends on the Container's instances since instances depend on other instances through bridging. Leaving it outside would mean more mutable parameters which all get set to some self.xxx
when called from a container instance method. That resulted in some problems with the borrow checker because of several mutable borrows to self.
/// Add a [InstanceConfig](struct.InstanceConfig.html) for a custom named instance | ||
pub fn with_named_instance_config( | ||
mut self, | ||
instance_name: String, |
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 was confused by seeing instance_name
until I looked around and saw that it's the instance ID from the config, so why not leave it instance_id
? Seems like this is introducing an idea of a "named instance", does that name have any significance outside of the config?
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, that is exactly what is needed for bridging and what is used in this PR. In Container::instantiate_from_config()
this function is called and the instance_name
is set to the handle
as defined in the bridge. The DNA caller code should not depend on a container's config but instead defines a handle for its bridges. This is the main reason I had to break open ContainerApiDispatcher
actually.
.instances | ||
.iter() | ||
.map(|inst| (inst.id.clone(), inst.clone())) | ||
.filter(|&(name, _)| instance_configs.contains_key(name)) |
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.
Isn't it kind of a big deal if the instances
and instance_configs
hashmaps don't have the same keys? See my overall comment.
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.
Hm, not really because the configs are only used in the "info/instances" method. For bridging we could actually leave out the configs alltogether..
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.
The info/instances
is how the UI knows what DNA hashes it can refer to, pretty essential if the UI hard-codes DNA hashes as the fundamental address of a DNA, which I think it should. So if someone writes a container that doesn't pass an config for every instance, it will break the UI.
@maackle Thanks for the thorough response! We hand-pick instances in this line with different names than the instance ID in the config. And this is basically the heart of this PR: making available an instance to another one under a name chosen a priori (in the DNA, like here: #752) by the caller. I'm not super concerned about the config/instance drift since the config is not needed in all cases, like the one implemented here. The result is just that it is not listed in the info output. And maybe that is something we even would want to make possible for certain cases. If it is important for your use-case to have all instances listed there, make sure you provide configs for all.. I kinda prefer having the builder be flexible enough to cater for these different use-cases. Don't you think? |
@lucksus I hadn't considered the possibility of intentionally hiding instances from fn with_named_instance(&self, String, Arc<RwLock<Holochain>>);
fn with_named_instance_and_config(&self, String, (Arc<RwLock<Holochain>>, InstanceConfiguration));
fn with_instances(mut self, instances: HashMap<String, Arc<RwLock<Holochain>>>);
fn with_instance_config_pairs(mut self, pairs: HashMap<String, (Arc<RwLock<Holochain>>, InstanceConfiguration)>) Then you can never pass a config without an instance, but you can pass bare instances. It could be a follow up PR but what do you think? That enforces "configs are a subset of instances". I'm just a huge fan of using the type system to enforce invariants and make unwanted states impossible. |
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.
just some comments for clarity. Nice work!
.expect("config.check_consistency()? jumps out if config is broken"); | ||
let callee_instance = self.instances.get(&bridge.callee_id).expect( | ||
r#" | ||
We have to create instances ordered by bridge dependencies such that we |
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.
really? This is a pretty crazy expectation, sounds more like an error message that would be reported to the user or logged? Or am I missing something...
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 saying the description that this relies on my previous work in #773. Instantiate from config is called in this loop over instances sorted by bridge dependencies.
Am I wrong in making this an explicit invariant? The ContainerApiBuilder needs live instances to reference in the closures that get registered in the IoHandler. I mean, that could be changed, but then there will be bridge genesis that would happen in Holochain::new
a few lines below. At that point we need the instances that this instance depends to be up an running, no?
So currently, this code does enforce this expectation and making it explicit in this expect feels like the right way to communicate it to our future selves looking at the 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.
ok. yep, got it
//persister: Option<Arc<Mutex<Persister>>>, | ||
chain_storage: Option<Arc<RwLock<ContentAddressableStorage>>>, | ||
dht_storage: Option<Arc<RwLock<ContentAddressableStorage>>>, | ||
eav_storage: Option<Arc<RwLock<EntityAttributeValueStorage>>>, | ||
network_config: Option<JsonString>, | ||
container_api: Option<Arc<RwLock<IoHandler>>>, |
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.
why is this called a "container_api" that seems like a weird name. I see that the IoHandler is returned by make_interface_handler
i.e to a specific channel that can be connected on for external calls, so I'm confused to see it called a container_api
here.
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.
Well, it is a "container API". I am using the ContainerApiBuilder
to register different methods for external interfaces in make_interface_hander
vs internal interfaces, i.e. bridges, that are passed into the instances. Here I'm setting this container_api with the version for bridges: https://github.com/holochain/holochain-rust/pull/776/files#diff-9f381fb2edab09fcf479f47ae307a2dbR136.
In yesterday's call I suggested to @maackle that signals could work over the same container API just by using a (yet to be added) builder function ContainerApiBuilder::with_signal_functions()
.
The idea is that this container API is the only thing we need to pass into the instances for all kinds of callbacks. It is the interface the container exposes to instances which I'm sure will grow in the future.
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.
ok.
mock_network_config(), | ||
)), | ||
Arc::new( | ||
ContextBuilder::new() |
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.
nice pattern!
@zippy, I've responded to your comments. I'm waiting for you to read and maybe respond before merging. If you're fine with this please go ahead and hit the merge button :) |
Context
This is about exposing the container API to instances for the purpose of bridging.
This SoA in the tree:
ContainerApiDispatcher
-> BuilderThe main changes here are to what was
ContainerApiDispatcher
before and is nowContainerApiBuilder
. The main thing used outside was theIoHandler
anyway. I am using the same thing that gets exposed to (external) interfaces now also for interfacing from instances. But for that we need more control over the methods that get registered in a particular interface context. Hence the transformation to a builder.Container::instantiate_from_config()
That function moved into
Container
and makes use of theContainerApiBuilder
to add each instance that the current instance depends on. This relies on my last PR #773 that makes sure that we have a topological ordering of instances so we can expect dependencies to have spawned before. (See thoseexpect()
statements there).Changes around
Context
Since I had to change
Context::new
again to include thecontainer_api: IoHandler
I couldn't resist but apply theContextBuilder
in all those places that I had to touch again. Sorry for the noise.