-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Deferred Service Builder #4587
Comments
The original idea behind the After all, the parameters that the builder passes to the stack are very arbitrary. If for instance you want to use an implementation of a I'm not sure how feasible that is, but my original idea was to create everything on the stack, and then pass that to the |
I got an idea. Let me know if that sounds good:
This makes me conclude that I can probably drop the entire ServiceBuilder and replace it by a few functions (build_service, build_for_import, build_client). This will also simplify greatly the typing. |
So I have to drop it for now to focus on cumulus... I did some progress as I found out that the builder itself don't actually help much and can be entirely removed instead of making it defer the calls to the with_* methods. This is actually more flexible AND it doesn't reduce at all the readability nor the simplicity in the service.rs files that were using it. |
I suppose the only way to actually reduce the code for it would be by using something like |
To give my two cents: I don't even know why there is a The As far as I know, it was initially supposed to be the container that contains all the sub-components, and you would create it in a kind-of-declarative way with a macro. During the six months or so when I worked on it, to me it was supposed to coordinate the various components of Substrate (#2536), but that is obviously not the case, as these components "coordinate each other" in a spaghetti-ish way at the moment (which also contributes to all the issues with unbounded channels that we have, but that's off-topic). Actually reworking that without rewriting Substrate is too big of a task at this point. Now, almost the only thing that |
Hey, I'm gonna be taking over this issue I think. I've made a new PR: #6309. |
@gnunicorn do you think you could assign this to me please? |
I definitely agree with @tomaka that the I've been using wgpu-rs a fair bit recently, and I think it has a really nice API for constructing complex things (swap chains, rendering pipelines etc compared to our import queues and finality proof providers). It puts everything in the top level of the crate so you don't have to go looking in a specific module for what you want. I think it would be good to have a kind of 'substrate prelude' crate that does the same thing. It also uses 'descriptor' structs for a lot of the initialization, which would be a lot better than the following, which is in the current PR: sc_service::build(
config,
client,
backend,
task_manager,
keystore,
None,
Some(select_chain),
import_queue,
Some(finality_proof_request_builder),
Some(finality_proof_provider),
transaction_pool,
(),
Some(remote_blockchain),
background_tasks,
None,
Box::new(|_| ()),
String::new()
) |
I am not generally opposed to getting rid of Secondly, this also means it isn't being used directly, but rather plugged together by third party developers to their needs – and this is exactly where what is currently Thus, the way I currently think of it going forward is less of just expose everything libraries, but a tokio-0.2-meta-crate, where features becomes available through setting them in |
So I've been thinking about the systems that we can replace the service builder with, and I've come up with two that I think are alright.
1.a. There's no way to make sure the full node service and light node service builds are compatible, meaning that someone could try using a BABE block importer for the full node and an AURA block importer for the light node and they either just wouldn't work together or, worse, cause something weird to happen. This was a potential problem with the service builder as well, but it'd be nice to solve it, ideally by having a single build system that you can specialize into full node/light node after you've defined everything. 1.b. The generics can get very very complicated, as lots of things depend on a client type, which itself depends on the block type, etc. There are ways around this problem though: 1.b.i. We setup in type definitions in the 1.b.ii. We create a macro that takes a 1.b.iii. We do this t:rick pub trait PreludeT {
type FullClient;
type LightClient;
type FullBackend;
...
}
pub struct Prelude<Block, RuntimeApi, Executor>(std::marker::PhantomData<(Block, RuntimeApi, Executor)>);
impl<Block, RuntimeApi, Executor> PreludeT for Prelude<Block, RuntimeApi, Executor> {
type FullClient = TFullClient<Block, RuntimeApi, Executor>;
type LightClient = TLightClient<Block, RuntimeApi, Executor>;
type FullBackend = TFullBackend<Block>;
...
} which can then be used like: type Prelude = sc_service::Prelude<MyBlock, MyRuntimeApi, MyExecutor>;
fn whatever() -> <Prelude as PreludeT::FullClient> {
...
} This would be a lot easier with Inherent associated types.
pub struct Builder;
impl sc_service::builder::Builder for Builder {
type Block = Block;
type RuntimeApi = RuntimeApi;
type Executor = Executor;
type TransactionPoolBuilder = BasicPoolBuilder;
type BlockImportBuilder = GrandpaBlockImportBuilder<Self::SelectChainBuilder>;
type ImportQueueBuilder = AuraImportQueueBuilder<Self::BlockImportBuilder>;
type FinalityProofProviderBuilder = GrandpaFinalityProofProviderBuilder;
type SelectChainBuilder = LongestChainBuilder;
type RpcExtensions = NoRpc<Self>;
} This would work really well in the simple cases, where you have a defined builder type for everything you want. 2.a. The main problem with this is that we're defining things in terms of types, not code. While it's pretty extendible as you can replace any of the types or the Any feedback on these ideas would be much appreciated! |
There's also nothing stopping us from removing the service builder now, then adding my builder trait later. |
@seunlanlege @joshua-mir @bkchr I'm interested in hearing your thoughts as well! |
There is a pretty huge hack in Not only the code in |
I'm not asking for this problem to be solved, but I would like people to be aware that this situation exists, and that we need some structure that would make it possible to solve this problem eventually. |
Can we not just almost entirely drop the service builder and just have normal function that we call to initialize the service? Something like:
Did you tried something like this? I think we need to get rid off these traits or whatever that try to force a certain structure of the service. In the end the service is just futures that are spawned and that is something we don't need to combine in struct. |
I'm going to remove the service builder now, and if we want to go through with the builder trait later, we'll do that. |
We can probably go ahead and close this issue now that #6557 has been merged. |
Right now, the
ServiceBuilder
creates the client, backend and all else right at the beginning and executes the configured functions directly. This has some implicatations, we do not want:Instead the pattern should only keep the references to the
fn
s and execute all in one go on.build
only. This would allow us to provide defaults, enforce the order in which set up takes place and would ensure anyfn
is only called once.Further more this might allow us to clean up the api, so you can have one global builder and then call
build_light
andbuild_full
on them rather than having to specify two builder...The text was updated successfully, but these errors were encountered: