From 2bc6a36857a83610f091892960483fd597c2ddb2 Mon Sep 17 00:00:00 2001 From: AgraVator Date: Tue, 23 Dec 2025 14:07:36 +0530 Subject: [PATCH 01/19] propose child channel plugins --- A110-child-channel-plugins.md | 235 ++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 A110-child-channel-plugins.md diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md new file mode 100644 index 000000000..d8152c61f --- /dev/null +++ b/A110-child-channel-plugins.md @@ -0,0 +1,235 @@ +# **gRFC AXX: Child Channel Configuration Plugins** + +* **Author(s)**: [Abhishek Agrawal](mailto:agrawalabhi@google.com) +* **Status**: Draft +* **To be implemented in**: Core, Java, Go +* **Last updated**: 2025-12-22 + +## **Abstract** + +This proposal introduces a mechanism to configure "child channels"—channels created internally by gRPC components (such as xDS control plane channel). Currently, these internal channels cannot easily inherit configuration (like metric sinks and interceptors) from the user application without relying on global state. This design proposes a language-specific approach to injecting configuration: using `functional interfaces` in Java, `DialOptions` in Go, and `ChannelArgs` in Core. + +## **Background** + +Complex gRPC ecosystems often require the creation of auxiliary channels that are not directly instantiated by the user application. Two primary examples are: + +1. **xDS (Extensible Discovery Service)**: When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control plane. Currently, users have limited ability to configure this internal control plane channel. +2. **Advanced Load Balancing (RLS, GrpcLB):** Policies like RLS (Route Lookup Service) and GrpcLB, as well as other high-level libraries built on top of gRPC, frequently create internal channels to communicate with look-aside load balancers or backends. + +### **The Problem** + +There is currently no standardized way to configure behavior for these child channels. + +* **Metrics**: Users need to configure metric sinks so that telemetry from internal channels can be read and exported. +* **Interceptors**: Users may need to apply specific interceptors (e.g., for authentication, logging, or tracing) to internal traffic. +* **No Global State**: These configurations cannot be set globally (e.g., using static singletons) because different parts of an application may require different configurations, such as different metric backends or security credentials. + +## **Proposal** + +The proposal creates a "plugin" or configuration injection style for internal channels. The implementation varies by language to match existing idioms, but the goal remains consistent: allow the user to pass a configuration object or function that the internal channel factory applies during creation. + +### **Java** + +In Java, the configuration will be achieved by accepting functions (callbacks). The API allows users to pass a `Consumer>` (or a similar functional interface). When an internal library (e.g., xDS, RLS, gRPCLB) creates a child channel, it applies this user-provided function to the builder before building the channel. + + + * #### 1\. Configuration Interface + + Use the standard `java.util.function.Consumer` and define a new public API interface, `ChildChannelConfigurer`, to encapsulate the configuration logic for auxiliary channels. + + ```java + import java.util.function.Consumer; + import io.grpc.ManagedChannelBuilder; + + // Captures the intent of the plugin. + // Consumes a builder to modify it before the channel is built + public interface ChildChannelConfigurer extends Consumer> { + // Inherits accept(T t) from Consumer + } + ``` + + * #### 2\. API Changes + + Add `ManagedChannelBuilder#childChannelConfigurer()` to allow users to register this configurer, and `ManagedChannelBuilder#configureChannel(ManagedChannel parent)` to allow a new builder to inherit configuration (including the `ChildChannelConfigurer` and `MetricRecorder`) from an existing parent channel. + + * #### 3\. Internal Implementation + + The implementation propagates these configurations when creating internal channels. It leverages `configureChannel()` to act as a fusion point, automatically applying the `ChildChannelConfigurer` and other parent properties to the new builder. The implementation follows the pattern for global configurators and calls `.accept()` as soon as the builder is available. + + * #### 4\. Usage Example + + ```java + // 1. Define the configurer for internal child channels + ChildChannelConfigurer myInternalConfig = (builder) -> { + // Apply interceptors or configuration to the child channel builder + builder.intercept(new MyAuthInterceptor()); + builder.maxInboundMessageSize(1024 * 1024); + }; + + // 2. Apply it to the parent channel + ManagedChannel channel = ManagedChannelBuilder.forTarget("xds:///my-service") + .childChannelConfigurer(myInternalConfig) // <--- Configuration injected here + .build(); + ``` + + * #### 5. Out-of-Band (OOB) Channels + + We do not propose applying child channel configurations to Out-of-Band (OOB) channels at this time. To maintain architectural flexibility and avoid breaking changes in the future, we will modify the implementation to use a `noOp()` MetricSink for OOB channels rather than inheriting the parent channel's sink. + +Furthermore, we acknowledge that certain configurations will not function out-of-the-box for `resolvingOobChannel` due to its specific initialization requirements. + + + +### **Go** + +In Go, configuration for child channels will be achieved by passing a specific `DialOption` to the parent channel. This option will encapsulate a slice of *other* `DialOption`s that should be applied exclusively to any internal child channels (like xDS control plane connections) created by the parent. + +* #### 1\. Define a new API for configuring the childChannelOptions + + * **New Public API** We will introduce a new function in the `grpc` package that returns a `DialOption` specifically for child channels. + +```go +// WithChildChannelOptions returns a DialOption that specifies a list of options +// to be applied to any internal child channels (e.g., xDS control plane channels) +// created by this ClientConn. +// +// These options are NOT applied to the ClientConn returned by NewClient. +func WithChildChannelOptions(opts ...DialOption) DialOption { + return newFuncDialOption(func(o *dialOptions) { + o.childChannelOptions = opts + }) +} +``` + +### + +* #### 2\. Internal Mechanics + + * We need to add a field to the internal `dialOptions` struct to hold these options, and then ensure internal components (like the xDS client) read them. + + * ##### A. Update `dialOptions` struct + +```go +● type dialOptions struct { +● // ... existing fields ... +● // childChannelOptions holds options intended for internal child channels. +● childChannelOptions []DialOption +● } +``` + + * ##### B. Update Internal Channel Creation (e.g., in `xds/client`) + + * When the xDS client (or any internal component) needs to dial the control plane, it must merge its own required options with the user-provided child options. + +* #### 3\. Usage Example (User-Side Code) + + * This design gives the user complete flexibility. They can configure the parent channel one way (e.g., mTLS) and the child channel another way (e.g., OAuth token \+ Metrics), all in one `NewClient` call. + +```go +import ( + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +func main() { + // 1. Define configuration specifically for the internal control plane + // (e.g., a specific metrics interceptor or custom authority) + internalOpts := []grpc.DialOption{ + grpc.WithUnaryInterceptor(MyMonitoringInterceptor), + grpc.WithAuthority("xds-authority.example.com"), + } + + // 2. Create the Parent Channel + // Pass the internal options using the new wrapper. + conn, err := grpc.NewClient("xds:///my-service", + // Parent configuration + grpc.WithTransportCredentials(insecure.NewCredentials()), + + // Child configuration (injected here) + grpc.WithChildChannelOptions(internalOpts...), + ) + + if err != nil { + panic(err) + } + // ... use conn ... +} +``` + +### **Core (C/C++)** + +In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pass configuration to internal channels. Instead of introducing a new configuration class or interface, we define a standard argument key whose value is a pointer to another `grpc_channel_args` structure. This "Nested Arguments" pattern allows the parent channel to carry a specific subset of arguments intended solely for its children (e.g., xDS control plane connections). + +* #### 1\. Define the Configuration Mechanism + + * We define a new channel argument key. The value associated with this key is a pointer to a `grpc_channel_args` struct (or the C++ `ChannelArgs` wrapper), managed via a pointer vtable to ensure correct ownership and copying. + +```c +// A pointer argument key that internal components (like xDS) look for. +// The value is a pointer to a grpc_channel_args struct containing the subset +// of options (e.g., specific socket mutators, user agents) for child channels. +#define GRPC_ARG_CHILD_CHANNEL_ARGS "grpc.internal.child_channel_args" +``` + +#### + +* #### 2\. Internal Implementation + + * Internal components that create channels (specifically `XdsClient`) are updated to look for this argument. If present, these arguments are merged with the default arguments required by the component. + * **Extraction:** The component queries the parent's args for `GRPC_ARG_CHILD_CHANNEL_ARGS`. + * **Merge:** The extracted args are layered on top of the default internal args (e.g., bootstrap configuration). + * **Creation:** The combined arguments are passed to `grpc_channel_create`. + +* #### **3\. Usage Example (User-Side Code)** + + * A user configures the parent channel by creating a subset of arguments for the child, packing them into a standard `grpc_arg`, and passing them to the parent. + +```c +// 1. Prepare the Child Config (The Subset) +// Example: We want the internal control plane to use a specific Socket Mutator +grpc_socket_mutator* my_mutator = CreateMySocketMutator(); +grpc_arg child_arg = grpc_channel_arg_socket_mutator_create(my_mutator); +grpc_channel_args child_args_struct = {1, &child_arg}; + +// 2. Pack the Subset into the Parent's Arguments +// We use a helper (conceptually similar to grpc_channel_arg_pointer_create) +// to wrap the child_args_struct safely with a VTable for ownership. +grpc_arg parent_arg = grpc_channel_arg_pointer_create( + GRPC_ARG_CHILD_CHANNEL_ARGS, + &child_args_struct, + &grpc_channel_args_pointer_vtable // VTable handles copy/destroy of the struct +); + +// 3. Create the Parent Channel +grpc_channel_args parent_args = {1, &parent_arg}; +auto channel = grpc::CreateCustomChannel( + "xds:///my-service", + grpc::InsecureChannelCredentials(), + grpc::ChannelArguments::FromC(parent_args) +); + +// Result: The 'channel' (parent) does NOT use 'my_mutator'. +// However, when it creates the internal xDS channel, it extracts 'child_args_struct' +// and applies 'my_mutator' to that connection. +``` + +## **Rationale** + +### **Why not Global Configuration?** + +Global configuration (static variables) was rejected because it prevents multi-tenant applications from isolating configurations. For example, one GCS client might need to export metrics to Prometheus, while another in the same process exports to Cloud Monitoring. + +### **Why Language-Specific Approaches?** + +While the concept (configuring child channels) is cross-language, the mechanism for channel creation differs significantly: + +* **Java** relies heavily on the `Builder` pattern, making functional callbacks the most natural fit. +* **Go** uses functional options (`DialOption`), so passing a slice of options is idiomatic. +* **Core** uses a generic key-value map (`ChannelArgs`) for almost all configurations, maintaining consistency with the core C-core design. + +## **Open Issues** + +* Should there be distinct configurations for different(i.e. different target strings) child channels ? +* For xDS client pool the first since it only depends on the target string, any subsequent usage within the same process having the same target string will end up using the previously created client with possibly different configuration. + +[image1]: \ No newline at end of file From bec26c95148360a99f9d5f07bbcd29e4fb9fa331 Mon Sep 17 00:00:00 2001 From: AgraVator Date: Tue, 23 Dec 2025 14:24:35 +0530 Subject: [PATCH 02/19] correct formatting --- A110-child-channel-plugins.md | 306 ++++++++++++++++------------------ 1 file changed, 143 insertions(+), 163 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index d8152c61f..2d934e6df 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -1,9 +1,9 @@ # **gRFC AXX: Child Channel Configuration Plugins** -* **Author(s)**: [Abhishek Agrawal](mailto:agrawalabhi@google.com) -* **Status**: Draft -* **To be implemented in**: Core, Java, Go -* **Last updated**: 2025-12-22 +* **Author(s)**: [Abhishek Agrawal](mailto:agrawalabhi@google.com) +* **Status**: Draft +* **To be implemented in**: Core, Java, Go +* **Last updated**: 2025-12-23 ## **Abstract** @@ -13,15 +13,15 @@ This proposal introduces a mechanism to configure "child channels"—channels cr Complex gRPC ecosystems often require the creation of auxiliary channels that are not directly instantiated by the user application. Two primary examples are: -1. **xDS (Extensible Discovery Service)**: When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control plane. Currently, users have limited ability to configure this internal control plane channel. +1. **xDS (Extensible Discovery Service)**: When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control plane. Currently, users have limited ability to configure this internal control plane channel. 2. **Advanced Load Balancing (RLS, GrpcLB):** Policies like RLS (Route Lookup Service) and GrpcLB, as well as other high-level libraries built on top of gRPC, frequently create internal channels to communicate with look-aside load balancers or backends. ### **The Problem** There is currently no standardized way to configure behavior for these child channels. -* **Metrics**: Users need to configure metric sinks so that telemetry from internal channels can be read and exported. -* **Interceptors**: Users may need to apply specific interceptors (e.g., for authentication, logging, or tracing) to internal traffic. +* **Metrics**: Users need to configure metric sinks so that telemetry from internal channels can be read and exported. +* **Interceptors**: Users may need to apply specific interceptors (e.g., for authentication, logging, or tracing) to internal traffic. * **No Global State**: These configurations cannot be set globally (e.g., using static singletons) because different parts of an application may require different configurations, such as different metric backends or security credentials. ## **Proposal** @@ -33,203 +33,183 @@ The proposal creates a "plugin" or configuration injection style for internal ch In Java, the configuration will be achieved by accepting functions (callbacks). The API allows users to pass a `Consumer>` (or a similar functional interface). When an internal library (e.g., xDS, RLS, gRPCLB) creates a child channel, it applies this user-provided function to the builder before building the channel. - * #### 1\. Configuration Interface +* #### 1\. Configuration Interface - Use the standard `java.util.function.Consumer` and define a new public API interface, `ChildChannelConfigurer`, to encapsulate the configuration logic for auxiliary channels. + Use the standard `java.util.function.Consumer` and define a new public API interface, `ChildChannelConfigurer`, to encapsulate the configuration logic for auxiliary channels. - ```java - import java.util.function.Consumer; - import io.grpc.ManagedChannelBuilder; + ```java + import java.util.function.Consumer; + import io.grpc.ManagedChannelBuilder; - // Captures the intent of the plugin. - // Consumes a builder to modify it before the channel is built - public interface ChildChannelConfigurer extends Consumer> { - // Inherits accept(T t) from Consumer - } - ``` + // Captures the intent of the plugin. + // Consumes a builder to modify it before the channel is built + public interface ChildChannelConfigurer extends Consumer> { + // Inherits accept(T t) from Consumer + } + ``` - * #### 2\. API Changes +* #### 2\. API Changes - Add `ManagedChannelBuilder#childChannelConfigurer()` to allow users to register this configurer, and `ManagedChannelBuilder#configureChannel(ManagedChannel parent)` to allow a new builder to inherit configuration (including the `ChildChannelConfigurer` and `MetricRecorder`) from an existing parent channel. + Add `ManagedChannelBuilder#childChannelConfigurer()` to allow users to register this configurer, and `ManagedChannelBuilder#configureChannel(ManagedChannel parent)` to allow a new builder to inherit configuration (including the `ChildChannelConfigurer` and `MetricRecorder`) from an existing parent channel. - * #### 3\. Internal Implementation +* #### 3\. Internal Implementation - The implementation propagates these configurations when creating internal channels. It leverages `configureChannel()` to act as a fusion point, automatically applying the `ChildChannelConfigurer` and other parent properties to the new builder. The implementation follows the pattern for global configurators and calls `.accept()` as soon as the builder is available. + The implementation propagates these configurations when creating internal channels. It leverages `configureChannel()` to act as a fusion point, automatically applying the `ChildChannelConfigurer` and other parent properties to the new builder. The implementation follows the pattern for global configurators and calls `.accept()` as soon as the builder is available. - * #### 4\. Usage Example +* #### 4\. Usage Example - ```java - // 1. Define the configurer for internal child channels - ChildChannelConfigurer myInternalConfig = (builder) -> { - // Apply interceptors or configuration to the child channel builder - builder.intercept(new MyAuthInterceptor()); - builder.maxInboundMessageSize(1024 * 1024); - }; + ```java + // 1. Define the configurer for internal child channels + ChildChannelConfigurer myInternalConfig = (builder) -> { + // Apply interceptors or configuration to the child channel builder + builder.intercept(new MyAuthInterceptor()); + builder.maxInboundMessageSize(1024 * 1024); + }; - // 2. Apply it to the parent channel - ManagedChannel channel = ManagedChannelBuilder.forTarget("xds:///my-service") - .childChannelConfigurer(myInternalConfig) // <--- Configuration injected here - .build(); - ``` + // 2. Apply it to the parent channel + ManagedChannel channel = ManagedChannelBuilder.forTarget("xds:///my-service") + .childChannelConfigurer(myInternalConfig) // <--- Configuration injected here + .build(); + ``` - * #### 5. Out-of-Band (OOB) Channels +* #### 5\. Out-of-Band (OOB) Channels - We do not propose applying child channel configurations to Out-of-Band (OOB) channels at this time. To maintain architectural flexibility and avoid breaking changes in the future, we will modify the implementation to use a `noOp()` MetricSink for OOB channels rather than inheriting the parent channel's sink. + We do not propose applying child channel configurations to Out-of-Band (OOB) channels at this time. To maintain architectural flexibility and avoid breaking changes in the future, we will modify the implementation to use a `noOp()` MetricSink for OOB channels rather than inheriting the parent channel's sink. -Furthermore, we acknowledge that certain configurations will not function out-of-the-box for `resolvingOobChannel` due to its specific initialization requirements. - - + Furthermore, we acknowledge that certain configurations will not function out-of-the-box for `resolvingOobChannel` due to its specific initialization requirements. ### **Go** -In Go, configuration for child channels will be achieved by passing a specific `DialOption` to the parent channel. This option will encapsulate a slice of *other* `DialOption`s that should be applied exclusively to any internal child channels (like xDS control plane connections) created by the parent. - -* #### 1\. Define a new API for configuring the childChannelOptions - - * **New Public API** We will introduce a new function in the `grpc` package that returns a `DialOption` specifically for child channels. +In Go, configuration for child channels is be achieved by passing a specific `DialOption` to the parent channel. This option encapsulates a slice of other a slice of *other* `DialOption`s that are applied exclusively to any internal child channels created by the parent. -```go -// WithChildChannelOptions returns a DialOption that specifies a list of options -// to be applied to any internal child channels (e.g., xDS control plane channels) -// created by this ClientConn. -// -// These options are NOT applied to the ClientConn returned by NewClient. -func WithChildChannelOptions(opts ...DialOption) DialOption { - return newFuncDialOption(func(o *dialOptions) { - o.childChannelOptions = opts - }) -} -``` +* #### 1\. New API for Child Channel Options -### + We introduce a new function in the grpc package that returns a `DialOption` specifically for child channels. -* #### 2\. Internal Mechanics + ```go + // WithChildChannelOptions returns a DialOption that specifies a list of options + // to be applied to any internal child channels (e.g., xDS control plane channels) + // created by this ClientConn. + // + // These options are NOT applied to the ClientConn returned by NewClient. + func WithChildChannelOptions(opts ...DialOption) DialOption { + return newFuncDialOption(func(o *dialOptions) { + o.childChannelOptions = opts + }) + } + ``` - * We need to add a field to the internal `dialOptions` struct to hold these options, and then ensure internal components (like the xDS client) read them. +* #### 2\. Internal Mechanics - * ##### A. Update `dialOptions` struct + A new field is added to the internal `dialOptions` struct to hold these options. When the xDS client (or any internal component) dials the control plane, it merges its own required options with the user-provided child options. -```go -● type dialOptions struct { -● // ... existing fields ... -● // childChannelOptions holds options intended for internal child channels. -● childChannelOptions []DialOption -● } -``` - - * ##### B. Update Internal Channel Creation (e.g., in `xds/client`) - - * When the xDS client (or any internal component) needs to dial the control plane, it must merge its own required options with the user-provided child options. + ```go + type dialOptions struct { + // ... existing fields ... + // childChannelOptions holds options intended for internal child channels. + childChannelOptions []DialOption + } + ``` * #### 3\. Usage Example (User-Side Code) - * This design gives the user complete flexibility. They can configure the parent channel one way (e.g., mTLS) and the child channel another way (e.g., OAuth token \+ Metrics), all in one `NewClient` call. - -```go -import ( - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -func main() { - // 1. Define configuration specifically for the internal control plane - // (e.g., a specific metrics interceptor or custom authority) - internalOpts := []grpc.DialOption{ - grpc.WithUnaryInterceptor(MyMonitoringInterceptor), - grpc.WithAuthority("xds-authority.example.com"), - } - - // 2. Create the Parent Channel - // Pass the internal options using the new wrapper. - conn, err := grpc.NewClient("xds:///my-service", - // Parent configuration - grpc.WithTransportCredentials(insecure.NewCredentials()), - - // Child configuration (injected here) - grpc.WithChildChannelOptions(internalOpts...), - ) + This design provides users with the flexibility to define independent configurations for parent and child channels within a single NewClient call. For example, a parent channel can be configured with transport security (mTLS) while the internal child channels (such as the xDS control plane connection) are configured with specific interceptors or a custom authority. + + ```go + import ( + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + ) + + func main() { + // 1. Define configuration specifically for the internal control plane + // (e.g., a specific metrics interceptor or custom authority) + internalOpts := []grpc.DialOption{ + grpc.WithUnaryInterceptor(MyMonitoringInterceptor), + grpc.WithAuthority("xds-authority.example.com"), + } + + // 2. Create the Parent Channel + // Pass the internal options using the WithChildChannelOptions wrapper. + conn, err := grpc.NewClient("xds:///my-service", + // Parent channel configuration + grpc.WithTransportCredentials(insecure.NewCredentials()), + + // Child channel configuration (injected here) + grpc.WithChildChannelOptions(internalOpts...), + ) + + if err != nil { + log.Fatalf("failed to create client: %v", err) + } + defer conn.Close() - if err != nil { - panic(err) - } - // ... use conn ... -} -``` + // ... use conn ... + } + ``` ### **Core (C/C++)** -In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pass configuration to internal channels. Instead of introducing a new configuration class or interface, we define a standard argument key whose value is a pointer to another `grpc_channel_args` structure. This "Nested Arguments" pattern allows the parent channel to carry a specific subset of arguments intended solely for its children (e.g., xDS control plane connections). - -* #### 1\. Define the Configuration Mechanism +In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pass configuration to internal channels. We define a standard argument key whose value is a pointer to another `grpc_channel_args` structure. This "Nested Arguments" pattern allows the parent channel to carry a specific subset of arguments intended solely for its children. - * We define a new channel argument key. The value associated with this key is a pointer to a `grpc_channel_args` struct (or the C++ `ChannelArgs` wrapper), managed via a pointer vtable to ensure correct ownership and copying. +* #### 1\. Configuration Mechanism -```c -// A pointer argument key that internal components (like xDS) look for. -// The value is a pointer to a grpc_channel_args struct containing the subset -// of options (e.g., specific socket mutators, user agents) for child channels. -#define GRPC_ARG_CHILD_CHANNEL_ARGS "grpc.internal.child_channel_args" -``` + We define a new channel argument key. The value associated with this key is a pointer to a `grpc_channel_args` struct, managed via a pointer vtable to ensure correct ownership and copying. -#### + ```c + // A pointer argument key that internal components (like xDS) look for. + // The value is a pointer to a grpc_channel_args struct containing the subset + // of options (e.g., specific socket mutators, user agents) for child channels. + #define GRPC_ARG_CHILD_CHANNEL_ARGS "grpc.internal.child_channel_args" + ``` * #### 2\. Internal Implementation - * Internal components that create channels (specifically `XdsClient`) are updated to look for this argument. If present, these arguments are merged with the default arguments required by the component. - * **Extraction:** The component queries the parent's args for `GRPC_ARG_CHILD_CHANNEL_ARGS`. - * **Merge:** The extracted args are layered on top of the default internal args (e.g., bootstrap configuration). - * **Creation:** The combined arguments are passed to `grpc_channel_create`. - -* #### **3\. Usage Example (User-Side Code)** - - * A user configures the parent channel by creating a subset of arguments for the child, packing them into a standard `grpc_arg`, and passing them to the parent. - -```c -// 1. Prepare the Child Config (The Subset) -// Example: We want the internal control plane to use a specific Socket Mutator -grpc_socket_mutator* my_mutator = CreateMySocketMutator(); -grpc_arg child_arg = grpc_channel_arg_socket_mutator_create(my_mutator); -grpc_channel_args child_args_struct = {1, &child_arg}; - -// 2. Pack the Subset into the Parent's Arguments -// We use a helper (conceptually similar to grpc_channel_arg_pointer_create) -// to wrap the child_args_struct safely with a VTable for ownership. -grpc_arg parent_arg = grpc_channel_arg_pointer_create( - GRPC_ARG_CHILD_CHANNEL_ARGS, - &child_args_struct, - &grpc_channel_args_pointer_vtable // VTable handles copy/destroy of the struct -); - -// 3. Create the Parent Channel -grpc_channel_args parent_args = {1, &parent_arg}; -auto channel = grpc::CreateCustomChannel( - "xds:///my-service", - grpc::InsecureChannelCredentials(), - grpc::ChannelArguments::FromC(parent_args) -); - -// Result: The 'channel' (parent) does NOT use 'my_mutator'. -// However, when it creates the internal xDS channel, it extracts 'child_args_struct' -// and applies 'my_mutator' to that connection. -``` + Internal components that create channels (specifically `XdsClient`) are updated to look for this argument. When present, these arguments are merged with the default internal arguments required by the component. + +* #### 3\. Usage Example (User-Side Code) + + In this design, a user configures the parent channel by defining a subset of arguments intended for child channels, packing them into a standard `grpc_arg`, and passing them to the parent. This allows for granular control over internal components, such as the xDS control plane connection, without affecting the parent channel stack. + + ```c + // 1. Prepare the Child Config (The Subset) + // In this example, we configure a specific Socket Mutator for the child channel. + grpc_socket_mutator* my_mutator = CreateMySocketMutator(); + grpc_arg child_arg = grpc_channel_arg_socket_mutator_create(my_mutator); + grpc_channel_args child_args_struct = {1, &child_arg}; + + // 2. Pack the Subset into the Parent's Arguments + // The child_args_struct is wrapped as a pointer argument. + // We use a VTable to ensure the nested struct is safely copied or + // destroyed during the parent channel's lifetime. + grpc_arg parent_arg = grpc_channel_arg_pointer_create( + GRPC_ARG_CHILD_CHANNEL_ARGS, + &child_args_struct, + &grpc_channel_args_pointer_vtable + ); + + // 3. Create the Parent Channel + // The parent channel receives the nested argument but does not apply + // the socket mutator to itself. + grpc_channel_args parent_args = {1, &parent_arg}; + auto channel = grpc::CreateCustomChannel( + "xds:///my-service", + grpc::InsecureChannelCredentials(), + grpc::ChannelArguments::FromC(parent_args) + ); + + ``` ## **Rationale** ### **Why not Global Configuration?** -Global configuration (static variables) was rejected because it prevents multi-tenant applications from isolating configurations. For example, one GCS client might need to export metrics to Prometheus, while another in the same process exports to Cloud Monitoring. +We reject global configuration (static variables) because it prevents multi-tenant applications from isolating configurations. For example, one client may need to export metrics to Prometheus, while another in the same process exports to Cloud Monitoring. ### **Why Language-Specific Approaches?** -While the concept (configuring child channels) is cross-language, the mechanism for channel creation differs significantly: - -* **Java** relies heavily on the `Builder` pattern, making functional callbacks the most natural fit. -* **Go** uses functional options (`DialOption`), so passing a slice of options is idiomatic. -* **Core** uses a generic key-value map (`ChannelArgs`) for almost all configurations, maintaining consistency with the core C-core design. - -## **Open Issues** - -* Should there be distinct configurations for different(i.e. different target strings) child channels ? -* For xDS client pool the first since it only depends on the target string, any subsequent usage within the same process having the same target string will end up using the previously created client with possibly different configuration. +While the concept is cross-language, the mechanisms for channel creation differs significantly: -[image1]: \ No newline at end of file +* **Java** relies heavily on the `Builder` pattern, making functional callbacks the most natural fit. +* **Go** uses functional options (`DialOption`), so passing a slice of options is idiomatic. +* **Core** uses a generic key-value map (`ChannelArgs`), maintaining consistency with the core C-core design. \ No newline at end of file From 29bcc2a5726cb0b7d8bf4c1a9a17673b4a3c0638 Mon Sep 17 00:00:00 2001 From: AgraVator Date: Tue, 23 Dec 2025 14:26:45 +0530 Subject: [PATCH 03/19] use actual gRFC number --- A110-child-channel-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index 2d934e6df..106cdf905 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -1,4 +1,4 @@ -# **gRFC AXX: Child Channel Configuration Plugins** +# **A110: Child Channel Configuration Plugins** * **Author(s)**: [Abhishek Agrawal](mailto:agrawalabhi@google.com) * **Status**: Draft From 82a4b0b7040af038645080ec95a38537fba43aac Mon Sep 17 00:00:00 2001 From: AgraVator Date: Mon, 29 Dec 2025 21:24:29 +0530 Subject: [PATCH 04/19] suggested changes --- A110-child-channel-plugins.md | 134 ++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 48 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index 106cdf905..b54186b18 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -1,39 +1,75 @@ -# **A110: Child Channel Configuration Plugins** +# A110: Child Channel Options -* **Author(s)**: [Abhishek Agrawal](mailto:agrawalabhi@google.com) -* **Status**: Draft -* **To be implemented in**: Core, Java, Go -* **Last updated**: 2025-12-23 +* Author(s): [Abhishek Agrawal](mailto:agrawalabhi@google.com) +* Approver: a11r +* Status: In Review +* Implemented in: Core, Java, Go +* Last updated: 2025-12-24 +* Discussion at: [Mailing List Link TBD] -## **Abstract** +## Abstract -This proposal introduces a mechanism to configure "child channels"—channels created internally by gRPC components (such as xDS control plane channel). Currently, these internal channels cannot easily inherit configuration (like metric sinks and interceptors) from the user application without relying on global state. This design proposes a language-specific approach to injecting configuration: using `functional interfaces` in Java, `DialOptions` in Go, and `ChannelArgs` in Core. +This proposal introduces a mechanism to configure "child channels"—channels created internally by gRPC components (such as xDS control plane channel). Currently, these channels are often opaque to the user, making it difficult to inject necessary configurations for metrics, tracing etc from the user application without relying on global state. This design proposes an approach for users to pass configuration options to these internal channels. -## **Background** +## Background Complex gRPC ecosystems often require the creation of auxiliary channels that are not directly instantiated by the user application. Two primary examples are: -1. **xDS (Extensible Discovery Service)**: When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control plane. Currently, users have limited ability to configure this internal control plane channel. -2. **Advanced Load Balancing (RLS, GrpcLB):** Policies like RLS (Route Lookup Service) and GrpcLB, as well as other high-level libraries built on top of gRPC, frequently create internal channels to communicate with look-aside load balancers or backends. +1. xDS (Extensible Discovery Service): When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control plane. +2. External Authorization (ext_authz): As described in [gRFC A92](https://github.com/grpc/proposal/pull/481), the gRPC server or client may create an internal channel to contact an external authorization service. +3. External Processing (ext_proc): As described in [gRFC A93](https://github.com/grpc/proposal/pull/484), filters may create internal channels to call external processing servers. -### **The Problem** +### Related Proposals -There is currently no standardized way to configure behavior for these child channels. +* [A27: xDS-Based Global Load Balancing](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md) +* [A66: Otel Stats](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) +* [A72: OpenTelemetry Tracing](https://github.com/grpc/proposal/blob/master/A72-open-telemetry-tracing.md) +* [A92: xDS ExtAuthz Support](https://github.com/grpc/proposal/pull/481) +* [A93: xDS ExtProc Support](https://github.com/grpc/proposal/pull/484) -* **Metrics**: Users need to configure metric sinks so that telemetry from internal channels can be read and exported. -* **Interceptors**: Users may need to apply specific interceptors (e.g., for authentication, logging, or tracing) to internal traffic. -* **No Global State**: These configurations cannot be set globally (e.g., using static singletons) because different parts of an application may require different configurations, such as different metric backends or security credentials. +### The Problem -## **Proposal** +The primary motivation for this feature is the need to configure observability on a per-channel basis and propagate them to child channels. -The proposal creates a "plugin" or configuration injection style for internal channels. The implementation varies by language to match existing idioms, but the goal remains consistent: allow the user to pass a configuration object or function that the internal channel factory applies during creation. +* StatsPlugins & Tracing: Users need to configure metric sinks (as described in gRFC A66 and A72) so that telemetry from internal channels is correctly tagged and exported. +* Interceptors: Users may need to apply specific interceptors (e.g., for authentication, logging, or tracing) to internal traffic. -### **Java** +These configurations cannot be set globally because different parts of an application may require different configurations, such as different metric backends or security credentials. -In Java, the configuration will be achieved by accepting functions (callbacks). The API allows users to pass a `Consumer>` (or a similar functional interface). When an internal library (e.g., xDS, RLS, gRPCLB) creates a child channel, it applies this user-provided function to the builder before building the channel. +## Proposal +We introduce the concept of **Child Channel Options**. This is a configuration container attached to a parent channel that is strictly designated for use by its children. -* #### 1\. Configuration Interface +### Encapsulation +The user API must allow "nesting" of channel options. A user creating a Parent Channel `P` can provide a set of options `O_child`. +* `O_child` is opaque to `P`. `P` does not apply these options to itself. +* `O_child` is carried in `P`'s state, available for extraction by internal components. + +### Propagation +When an internal component (e.g., an xDS client factory or an auth filter) attached to `P` needs to create a Child Channel `C`: +1. It retrieves `O_child` from `P`. +2. It applies `O_child` to the configuration of `C`. + +### Precedence and Merging +The Child Channel `C` typically requires some internal configuration `O_internal` (e.g., specific target URIs, bootstrap credentials, or internal User-Agent strings). +* Merge Rule: `O_child` and `O_internal` are merged. +* Conflict Resolution: Mandatory internal settings (`O_internal`) generally take precedence over user-provided child options (`O_child`) to ensure correctness. However, additive options (like Interceptors, StatsPlugins, or Metric Sinks) are combined. + +### Shared Resources +Certain internal channels, specifically the **xDS Control Plane Client**, are often pooled and shared across multiple parent channels within a process based on the target URI (see [gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md)). + +If multiple Parent Channels (`P1`, `P2`) point to the same xDS target but provide *different* Child Channel Options (`O_child1`, `O_child2`): +* Behavior: The shared client is created using the options from the first parent channel that triggers its creation (e.g., `O_child1`). +* Subsequent Usage: When `P2` requests the client, it receives the existing shared client. `O_child2` is effectively ignored for that specific shared resource. + +### Language Implementations + +#### Java + +In Java, the configuration will be achieved by accepting functions (callbacks). The API allows users to pass a `Consumer>` (or a similar functional interface). When an internal library (e.g., xDS, gRPCLB) creates a child channel, it applies this user-provided function to the builder before building the channel. + + +* ##### Configuration Interface Use the standard `java.util.function.Consumer` and define a new public API interface, `ChildChannelConfigurer`, to encapsulate the configuration logic for auxiliary channels. @@ -48,41 +84,43 @@ In Java, the configuration will be achieved by accepting functions (callbacks). } ``` -* #### 2\. API Changes +* ##### API Changes + + * ManagedChannelBuilder: Add `ManagedChannelBuilder#childChannelConfigurer(ChildChannelConfigurer childChannelConfigurer)` to allow users to register this configurer, and `ManagedChannelBuilder#configureChannel(ManagedChannel parent)` to allow a new builder to inherit configuration from an existing parent channel. + * ServerBuilder: Add `ServerBuilder#childChannelConfigurer(ChildChannelConfigurer configurer)` to allow users to provide configuration for any internal channels created by the server (e.g., connections to external authorization or processing services). - Add `ManagedChannelBuilder#childChannelConfigurer()` to allow users to register this configurer, and `ManagedChannelBuilder#configureChannel(ManagedChannel parent)` to allow a new builder to inherit configuration (including the `ChildChannelConfigurer` and `MetricRecorder`) from an existing parent channel. -* #### 3\. Internal Implementation +* ##### Internal Implementation The implementation propagates these configurations when creating internal channels. It leverages `configureChannel()` to act as a fusion point, automatically applying the `ChildChannelConfigurer` and other parent properties to the new builder. The implementation follows the pattern for global configurators and calls `.accept()` as soon as the builder is available. -* #### 4\. Usage Example +* ##### Usage Example ```java - // 1. Define the configurer for internal child channels + // Define the configurer for internal child channels ChildChannelConfigurer myInternalConfig = (builder) -> { - // Apply interceptors or configuration to the child channel builder - builder.intercept(new MyAuthInterceptor()); - builder.maxInboundMessageSize(1024 * 1024); + InternalManagedChannelBuilder.addMetricSink(builder, sink); + InternalManagedChannelBuilder.interceptWithTarget( + builder, openTelemetryMetricsModule::getClientInterceptor); }; - // 2. Apply it to the parent channel + // Apply it to the parent channel ManagedChannel channel = ManagedChannelBuilder.forTarget("xds:///my-service") - .childChannelConfigurer(myInternalConfig) // <--- Configuration injected here - .build(); + .childChannelConfigurer(myInternalConfig) // <--- Configuration injected here + .build(); ``` -* #### 5\. Out-of-Band (OOB) Channels +* ##### Out-of-Band (OOB) Channels We do not propose applying child channel configurations to Out-of-Band (OOB) channels at this time. To maintain architectural flexibility and avoid breaking changes in the future, we will modify the implementation to use a `noOp()` MetricSink for OOB channels rather than inheriting the parent channel's sink. Furthermore, we acknowledge that certain configurations will not function out-of-the-box for `resolvingOobChannel` due to its specific initialization requirements. -### **Go** +#### Go -In Go, configuration for child channels is be achieved by passing a specific `DialOption` to the parent channel. This option encapsulates a slice of other a slice of *other* `DialOption`s that are applied exclusively to any internal child channels created by the parent. +In Go, configuration for child channels is be achieved by passing a specific `DialOption` to the parent channel. This option encapsulates a slice of *other* `DialOption`s that are applied exclusively to any internal child channels created by the parent. -* #### 1\. New API for Child Channel Options +* ##### New API for Child Channel Options We introduce a new function in the grpc package that returns a `DialOption` specifically for child channels. @@ -99,7 +137,7 @@ In Go, configuration for child channels is be achieved by passing a specific `Di } ``` -* #### 2\. Internal Mechanics +* ##### Internal Mechanics A new field is added to the internal `dialOptions` struct to hold these options. When the xDS client (or any internal component) dials the control plane, it merges its own required options with the user-provided child options. @@ -111,7 +149,7 @@ In Go, configuration for child channels is be achieved by passing a specific `Di } ``` -* #### 3\. Usage Example (User-Side Code) +* ##### Usage Example (User-Side Code) This design provides users with the flexibility to define independent configurations for parent and child channels within a single NewClient call. For example, a parent channel can be configured with transport security (mTLS) while the internal child channels (such as the xDS control plane connection) are configured with specific interceptors or a custom authority. @@ -148,11 +186,11 @@ In Go, configuration for child channels is be achieved by passing a specific `Di } ``` -### **Core (C/C++)** +##### Core (C/C++) In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pass configuration to internal channels. We define a standard argument key whose value is a pointer to another `grpc_channel_args` structure. This "Nested Arguments" pattern allows the parent channel to carry a specific subset of arguments intended solely for its children. -* #### 1\. Configuration Mechanism +* ##### Configuration Mechanism We define a new channel argument key. The value associated with this key is a pointer to a `grpc_channel_args` struct, managed via a pointer vtable to ensure correct ownership and copying. @@ -160,14 +198,14 @@ In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pas // A pointer argument key that internal components (like xDS) look for. // The value is a pointer to a grpc_channel_args struct containing the subset // of options (e.g., specific socket mutators, user agents) for child channels. - #define GRPC_ARG_CHILD_CHANNEL_ARGS "grpc.internal.child_channel_args" + #define GRPC_ARG_CHILD_CHANNEL_ARGS "grpc.child_channel_args" ``` -* #### 2\. Internal Implementation +* ##### Internal Implementation Internal components that create channels (specifically `XdsClient`) are updated to look for this argument. When present, these arguments are merged with the default internal arguments required by the component. -* #### 3\. Usage Example (User-Side Code) +* ##### Usage Example (User-Side Code) In this design, a user configures the parent channel by defining a subset of arguments intended for child channels, packing them into a standard `grpc_arg`, and passing them to the parent. This allows for granular control over internal components, such as the xDS control plane connection, without affecting the parent channel stack. @@ -200,16 +238,16 @@ In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pas ``` -## **Rationale** +## Rationale -### **Why not Global Configuration?** +### Why not Global Configuration? We reject global configuration (static variables) because it prevents multi-tenant applications from isolating configurations. For example, one client may need to export metrics to Prometheus, while another in the same process exports to Cloud Monitoring. -### **Why Language-Specific Approaches?** +### Why Language-Specific Approaches? While the concept is cross-language, the mechanisms for channel creation differs significantly: -* **Java** relies heavily on the `Builder` pattern, making functional callbacks the most natural fit. -* **Go** uses functional options (`DialOption`), so passing a slice of options is idiomatic. -* **Core** uses a generic key-value map (`ChannelArgs`), maintaining consistency with the core C-core design. \ No newline at end of file +* Java relies heavily on the `Builder` pattern, making functional callbacks the most natural fit. +* Go uses functional options (`DialOption`), so passing a slice of options is idiomatic. +* Core uses a generic key-value map (`ChannelArgs`), maintaining consistency with the core C-core design. \ No newline at end of file From 2038e4358580400ad041e6c43ffc327db8e6078e Mon Sep 17 00:00:00 2001 From: AgraVator Date: Mon, 29 Dec 2025 21:25:55 +0530 Subject: [PATCH 05/19] suggested changes --- A110-child-channel-plugins.md | 144 +++++++++++++--------------------- 1 file changed, 54 insertions(+), 90 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index b54186b18..aad38634c 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -9,7 +9,7 @@ ## Abstract -This proposal introduces a mechanism to configure "child channels"—channels created internally by gRPC components (such as xDS control plane channel). Currently, these channels are often opaque to the user, making it difficult to inject necessary configurations for metrics, tracing etc from the user application without relying on global state. This design proposes an approach for users to pass configuration options to these internal channels. +This proposal introduces a mechanism to configure "child channels", channels created internally by gRPC components (such as xDS control plane channel). Currently, these channels are often opaque to the user, making it difficult to inject necessary configurations for metrics, tracing etc from the user application. This design proposes an approach for users to pass configuration options to these internal channels. ## Background @@ -29,9 +29,9 @@ Complex gRPC ecosystems often require the creation of auxiliary channels that ar ### The Problem -The primary motivation for this feature is the need to configure observability on a per-channel basis and propagate them to child channels. +The primary motivation for this feature is the need to configure observability on a per-child-channel basis. -* StatsPlugins & Tracing: Users need to configure metric sinks (as described in gRFC A66 and A72) so that telemetry from internal channels is correctly tagged and exported. +* StatsPlugins & Tracing: Users need to configure metric sinks (as described in gRFC [A66](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) and [A72](https://github.com/grpc/proposal/blob/master/A72-open-telemetry-tracing.md)) so that telemetry from internal channels is correctly tagged and exported. * Interceptors: Users may need to apply specific interceptors (e.g., for authentication, logging, or tracing) to internal traffic. These configurations cannot be set globally because different parts of an application may require different configurations, such as different metric backends or security credentials. @@ -53,7 +53,7 @@ When an internal component (e.g., an xDS client factory or an auth filter) attac ### Precedence and Merging The Child Channel `C` typically requires some internal configuration `O_internal` (e.g., specific target URIs, bootstrap credentials, or internal User-Agent strings). * Merge Rule: `O_child` and `O_internal` are merged. -* Conflict Resolution: Mandatory internal settings (`O_internal`) generally take precedence over user-provided child options (`O_child`) to ensure correctness. However, additive options (like Interceptors, StatsPlugins, or Metric Sinks) are combined. +* Conflict Resolution: Mandatory internal settings (`O_internal`) generally take precedence over user-provided child options (`O_child`) to ensure correctness. ### Shared Resources Certain internal channels, specifically the **xDS Control Plane Client**, are often pooled and shared across multiple parent channels within a process based on the target URI (see [gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md)). @@ -68,7 +68,6 @@ If multiple Parent Channels (`P1`, `P2`) point to the same xDS target but provid In Java, the configuration will be achieved by accepting functions (callbacks). The API allows users to pass a `Consumer>` (or a similar functional interface). When an internal library (e.g., xDS, gRPCLB) creates a child channel, it applies this user-provided function to the builder before building the channel. - * ##### Configuration Interface Use the standard `java.util.function.Consumer` and define a new public API interface, `ChildChannelConfigurer`, to encapsulate the configuration logic for auxiliary channels. @@ -86,22 +85,15 @@ In Java, the configuration will be achieved by accepting functions (callbacks). * ##### API Changes - * ManagedChannelBuilder: Add `ManagedChannelBuilder#childChannelConfigurer(ChildChannelConfigurer childChannelConfigurer)` to allow users to register this configurer, and `ManagedChannelBuilder#configureChannel(ManagedChannel parent)` to allow a new builder to inherit configuration from an existing parent channel. + * ManagedChannelBuilder: Add `ManagedChannelBuilder#childChannelConfigurer(ChildChannelConfigurer childChannelConfigurer)` to allow users to register this configurer. * ServerBuilder: Add `ServerBuilder#childChannelConfigurer(ChildChannelConfigurer configurer)` to allow users to provide configuration for any internal channels created by the server (e.g., connections to external authorization or processing services). - -* ##### Internal Implementation - - The implementation propagates these configurations when creating internal channels. It leverages `configureChannel()` to act as a fusion point, automatically applying the `ChildChannelConfigurer` and other parent properties to the new builder. The implementation follows the pattern for global configurators and calls `.accept()` as soon as the builder is available. - * ##### Usage Example ```java // Define the configurer for internal child channels ChildChannelConfigurer myInternalConfig = (builder) -> { - InternalManagedChannelBuilder.addMetricSink(builder, sink); - InternalManagedChannelBuilder.interceptWithTarget( - builder, openTelemetryMetricsModule::getClientInterceptor); + builder.addMetricSink(sink); }; // Apply it to the parent channel @@ -118,62 +110,59 @@ In Java, the configuration will be achieved by accepting functions (callbacks). #### Go -In Go, configuration for child channels is be achieved by passing a specific `DialOption` to the parent channel. This option encapsulates a slice of *other* `DialOption`s that are applied exclusively to any internal child channels created by the parent. +In Go, both the Client (`grpc.NewClient`) and the Server (`NewGRPCServer`) create internal child channels. We introduce mechanisms to pass `DialOption`s into these internal channels from both entry points. * ##### New API for Child Channel Options - We introduce a new function in the grpc package that returns a `DialOption` specifically for child channels. +* Client-Side: `WithChildChannelOptions` - ```go - // WithChildChannelOptions returns a DialOption that specifies a list of options - // to be applied to any internal child channels (e.g., xDS control plane channels) - // created by this ClientConn. - // - // These options are NOT applied to the ClientConn returned by NewClient. - func WithChildChannelOptions(opts ...DialOption) DialOption { - return newFuncDialOption(func(o *dialOptions) { - o.childChannelOptions = opts - }) - } - ``` + For standard clients, we introduce a `DialOption` wrapper. -* ##### Internal Mechanics + ```go + // WithChildChannelOptions returns a DialOption that specifies a list of + // DialOptions to be applied to any internal child channels. + func WithChildChannelOptions(opts ...DialOption) DialOption { + return newFuncDialOption(func(o *dialOptions) { + o.childChannelOptions = opts + }) + } + ``` - A new field is added to the internal `dialOptions` struct to hold these options. When the xDS client (or any internal component) dials the control plane, it merges its own required options with the user-provided child options. +* Server-Side: `WithChildDialOptions` - ```go - type dialOptions struct { - // ... existing fields ... - // childChannelOptions holds options intended for internal child channels. - childChannelOptions []DialOption - } - ``` + For xDS-enabled servers, we introduce a `ServerOption` wrapper. Since `xds.NewGRPCServer` creates an internal xDS client to fetch listener configurations, it requires a way to apply `DialOptions` (such as **Socket Options** or **Stats Handlers**) to that internal connection. + + ```go + // WithChildDialOptions returns a ServerOption that specifies a list of + // DialOptions to be applied to the server's internal child channels + // (e.g., the xDS control plane connection). + func WithChildDialOptions(opts ...DialOption) ServerOption { + return newFuncServerOption(func(o *serverOptions) { + o.childDialOptions = opts + }) + } + ``` * ##### Usage Example (User-Side Code) This design provides users with the flexibility to define independent configurations for parent and child channels within a single NewClient call. For example, a parent channel can be configured with transport security (mTLS) while the internal child channels (such as the xDS control plane connection) are configured with specific interceptors or a custom authority. ```go - import ( - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - ) - func main() { - // 1. Define configuration specifically for the internal control plane - // (e.g., a specific metrics interceptor or custom authority) + // Define configuration specifically for the internal control plane internalOpts := []grpc.DialOption{ - grpc.WithUnaryInterceptor(MyMonitoringInterceptor), - grpc.WithAuthority("xds-authority.example.com"), + // Inject the OTel handler here. It will only measure traffic on the + // internal child channels (e.g., to the xDS server). + grpc.WithStatsHandler(otelHandler) } - // 2. Create the Parent Channel - // Pass the internal options using the WithChildChannelOptions wrapper. + // Create the Parent Channel conn, err := grpc.NewClient("xds:///my-service", - // Parent channel configuration + // Parent channel configuration (Data Plane) grpc.WithTransportCredentials(insecure.NewCredentials()), - // Child channel configuration (injected here) + // Child channel configuration (Control Plane) + // The OTel handler inside here applies ONLY to the child channels. grpc.WithChildChannelOptions(internalOpts...), ) @@ -195,47 +184,24 @@ In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pas We define a new channel argument key. The value associated with this key is a pointer to a `grpc_channel_args` struct, managed via a pointer vtable to ensure correct ownership and copying. ```c - // A pointer argument key that internal components (like xDS) look for. - // The value is a pointer to a grpc_channel_args struct containing the subset - // of options (e.g., specific socket mutators, user agents) for child channels. - #define GRPC_ARG_CHILD_CHANNEL_ARGS "grpc.child_channel_args" + // A pointer argument key. The value is a pointer to a grpc_channel_args + // struct containing the subset of options for child channels. + #define GRPC_ARG_CHILD_CHANNEL_ARGS "grpc.child_channel.args" ``` -* ##### Internal Implementation +* **API Changes** - Internal components that create channels (specifically `XdsClient`) are updated to look for this argument. When present, these arguments are merged with the default internal arguments required by the component. + We add a helper method to the C++ `ChannelArguments` class to simplify packing the nested arguments safely. -* ##### Usage Example (User-Side Code) + ```cpp + // Sets the channel arguments to be used for child channels. + void SetChildChannelArgs(const ChannelArguments& args); + ``` - In this design, a user configures the parent channel by defining a subset of arguments intended for child channels, packing them into a standard `grpc_arg`, and passing them to the parent. This allows for granular control over internal components, such as the xDS control plane connection, without affecting the parent channel stack. +* ##### Usage Example (User-Side Code) ```c - // 1. Prepare the Child Config (The Subset) - // In this example, we configure a specific Socket Mutator for the child channel. - grpc_socket_mutator* my_mutator = CreateMySocketMutator(); - grpc_arg child_arg = grpc_channel_arg_socket_mutator_create(my_mutator); - grpc_channel_args child_args_struct = {1, &child_arg}; - - // 2. Pack the Subset into the Parent's Arguments - // The child_args_struct is wrapped as a pointer argument. - // We use a VTable to ensure the nested struct is safely copied or - // destroyed during the parent channel's lifetime. - grpc_arg parent_arg = grpc_channel_arg_pointer_create( - GRPC_ARG_CHILD_CHANNEL_ARGS, - &child_args_struct, - &grpc_channel_args_pointer_vtable - ); - - // 3. Create the Parent Channel - // The parent channel receives the nested argument but does not apply - // the socket mutator to itself. - grpc_channel_args parent_args = {1, &parent_arg}; - auto channel = grpc::CreateCustomChannel( - "xds:///my-service", - grpc::InsecureChannelCredentials(), - grpc::ChannelArguments::FromC(parent_args) - ); - + // TODO(AgraVator): add example for configuring child channel observability ``` ## Rationale @@ -244,10 +210,8 @@ In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pas We reject global configuration (static variables) because it prevents multi-tenant applications from isolating configurations. For example, one client may need to export metrics to Prometheus, while another in the same process exports to Cloud Monitoring. -### Why Language-Specific Approaches? - -While the concept is cross-language, the mechanisms for channel creation differs significantly: +## Implementation -* Java relies heavily on the `Builder` pattern, making functional callbacks the most natural fit. -* Go uses functional options (`DialOption`), so passing a slice of options is idiomatic. -* Core uses a generic key-value map (`ChannelArgs`), maintaining consistency with the core C-core design. \ No newline at end of file +@AgraVator will be implementing immediately in grpc-java. A draft is available in +[PR 12578](https://github.com/grpc/grpc-java/pull/12578). Other languages will +follow, with work starting potentially in a few weeks. From 0c76ab82def1afa247f8eeef8ee470656e6cac11 Mon Sep 17 00:00:00 2001 From: AgraVator Date: Fri, 16 Jan 2026 09:13:45 +0530 Subject: [PATCH 06/19] suggested changes --- A110-child-channel-plugins.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index aad38634c..12949431b 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -5,7 +5,7 @@ * Status: In Review * Implemented in: Core, Java, Go * Last updated: 2025-12-24 -* Discussion at: [Mailing List Link TBD] +* Discussion at: https://groups.google.com/g/grpc-io/c/EBIp3uud-Bo ## Abstract @@ -13,11 +13,13 @@ This proposal introduces a mechanism to configure "child channels", channels cre ## Background -Complex gRPC ecosystems often require the creation of auxiliary channels that are not directly instantiated by the user application. Two primary examples are: +Complex gRPC ecosystems often require the creation of auxiliary channels that are not directly instantiated by the +user application. The primary examples are: 1. xDS (Extensible Discovery Service): When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control plane. 2. External Authorization (ext_authz): As described in [gRFC A92](https://github.com/grpc/proposal/pull/481), the gRPC server or client may create an internal channel to contact an external authorization service. -3. External Processing (ext_proc): As described in [gRFC A93](https://github.com/grpc/proposal/pull/484), filters may create internal channels to call external processing servers. +3. External Processing (ext_proc): As described in [gRFC A93](https://github.com/grpc/proposal/pull/484), filters may + create internal channels to call external processing servers. ### Related Proposals @@ -32,7 +34,7 @@ Complex gRPC ecosystems often require the creation of auxiliary channels that ar The primary motivation for this feature is the need to configure observability on a per-child-channel basis. * StatsPlugins & Tracing: Users need to configure metric sinks (as described in gRFC [A66](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) and [A72](https://github.com/grpc/proposal/blob/master/A72-open-telemetry-tracing.md)) so that telemetry from internal channels is correctly tagged and exported. -* Interceptors: Users may need to apply specific interceptors (e.g., for authentication, logging, or tracing) to internal traffic. +* Interceptors: Users may need to apply specific interceptors (e.g., for logging, or tracing) to internal traffic. These configurations cannot be set globally because different parts of an application may require different configurations, such as different metric backends or security credentials. @@ -66,7 +68,7 @@ If multiple Parent Channels (`P1`, `P2`) point to the same xDS target but provid #### Java -In Java, the configuration will be achieved by accepting functions (callbacks). The API allows users to pass a `Consumer>` (or a similar functional interface). When an internal library (e.g., xDS, gRPCLB) creates a child channel, it applies this user-provided function to the builder before building the channel. +In Java, the configuration will be achieved by accepting functions (callbacks). The API allows users to pass a `Consumer>` (or a similar functional interface). When an internal library (e.g., xDS, gRPCLB) creates a child channel, it applies this user-provided function to the builder before further configuring the channel. * ##### Configuration Interface @@ -78,21 +80,28 @@ In Java, the configuration will be achieved by accepting functions (callbacks). // Captures the intent of the plugin. // Consumes a builder to modify it before the channel is built - public interface ChildChannelConfigurer extends Consumer> { - // Inherits accept(T t) from Consumer + public interface ChannelConfigurer { + /** + * Configures the given channel builder. + * + * @param builder the channel builder to configure + */ + void configure(ManagedChannelBuilder builder); } ``` * ##### API Changes - * ManagedChannelBuilder: Add `ManagedChannelBuilder#childChannelConfigurer(ChildChannelConfigurer childChannelConfigurer)` to allow users to register this configurer. - * ServerBuilder: Add `ServerBuilder#childChannelConfigurer(ChildChannelConfigurer configurer)` to allow users to provide configuration for any internal channels created by the server (e.g., connections to external authorization or processing services). + * ManagedChannelBuilder: Add `ManagedChannelBuilder#childChannelConfigurer(ChannelConfigurer channelConfigurer)` to + allow users to register this configurer. + * XdsServerBuilder: Add `XdsServerBuilder#childChannelConfigurer(ChannelConfigurer configurer)` to allow users to + provide configuration for any internal channels created by the server (e.g., connections to external authorization or processing services). * ##### Usage Example ```java // Define the configurer for internal child channels - ChildChannelConfigurer myInternalConfig = (builder) -> { + ChannelConfigurer myInternalConfig = (builder) -> { builder.addMetricSink(sink); }; @@ -102,12 +111,6 @@ In Java, the configuration will be achieved by accepting functions (callbacks). .build(); ``` -* ##### Out-of-Band (OOB) Channels - - We do not propose applying child channel configurations to Out-of-Band (OOB) channels at this time. To maintain architectural flexibility and avoid breaking changes in the future, we will modify the implementation to use a `noOp()` MetricSink for OOB channels rather than inheriting the parent channel's sink. - - Furthermore, we acknowledge that certain configurations will not function out-of-the-box for `resolvingOobChannel` due to its specific initialization requirements. - #### Go In Go, both the Client (`grpc.NewClient`) and the Server (`NewGRPCServer`) create internal child channels. We introduce mechanisms to pass `DialOption`s into these internal channels from both entry points. From bae2fcff37a1bd98684add1a85d6ef47adf66322 Mon Sep 17 00:00:00 2001 From: agravator Date: Tue, 17 Mar 2026 09:19:15 +0530 Subject: [PATCH 07/19] fix: suggested changes --- A110-child-channel-plugins.md | 63 ++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index 12949431b..f97ed6abe 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -13,13 +13,11 @@ This proposal introduces a mechanism to configure "child channels", channels cre ## Background -Complex gRPC ecosystems often require the creation of auxiliary channels that are not directly instantiated by the -user application. The primary examples are: +Complex gRPC ecosystems often require the creation of auxiliary channels that are not directly instantiated by the user application. The primary examples are: 1. xDS (Extensible Discovery Service): When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control plane. 2. External Authorization (ext_authz): As described in [gRFC A92](https://github.com/grpc/proposal/pull/481), the gRPC server or client may create an internal channel to contact an external authorization service. -3. External Processing (ext_proc): As described in [gRFC A93](https://github.com/grpc/proposal/pull/484), filters may - create internal channels to call external processing servers. +3. External Processing (ext_proc): As described in [gRFC A93](https://github.com/grpc/proposal/pull/484), filters may create internal channels to call external processing servers. ### Related Proposals @@ -36,7 +34,7 @@ The primary motivation for this feature is the need to configure observability o * StatsPlugins & Tracing: Users need to configure metric sinks (as described in gRFC [A66](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) and [A72](https://github.com/grpc/proposal/blob/master/A72-open-telemetry-tracing.md)) so that telemetry from internal channels is correctly tagged and exported. * Interceptors: Users may need to apply specific interceptors (e.g., for logging, or tracing) to internal traffic. -These configurations cannot be set globally because different parts of an application may require different configurations, such as different metric backends or security credentials. +These configurations cannot be set globally because different parts of an application may require different configurations, such as different metric backends. ## Proposal @@ -46,6 +44,7 @@ We introduce the concept of **Child Channel Options**. This is a configuration c The user API must allow "nesting" of channel options. A user creating a Parent Channel `P` can provide a set of options `O_child`. * `O_child` is opaque to `P`. `P` does not apply these options to itself. * `O_child` is carried in `P`'s state, available for extraction by internal components. +* The configuration provided by `O_child` is strictly uniform across all child channels of a particular parent channel. ### Propagation When an internal component (e.g., an xDS client factory or an auth filter) attached to `P` needs to create a Child Channel `C`: @@ -53,8 +52,8 @@ When an internal component (e.g., an xDS client factory or an auth filter) attac 2. It applies `O_child` to the configuration of `C`. ### Precedence and Merging -The Child Channel `C` typically requires some internal configuration `O_internal` (e.g., specific target URIs, bootstrap credentials, or internal User-Agent strings). -* Merge Rule: `O_child` and `O_internal` are merged. +The Child Channel `C` typically requires some internal configuration `O_internal` (e.g., target URIs, or internal interceptors). +* Merge Rule: `O_child` and `O_internal` are merged. If the environment supports global channel options, `O_child` options override global channel options. * Conflict Resolution: Mandatory internal settings (`O_internal`) generally take precedence over user-provided child options (`O_child`) to ensure correctness. ### Shared Resources @@ -72,37 +71,46 @@ In Java, the configuration will be achieved by accepting functions (callbacks). * ##### Configuration Interface - Use the standard `java.util.function.Consumer` and define a new public API interface, `ChildChannelConfigurer`, to encapsulate the configuration logic for auxiliary channels. + Define a new public API interface, `ChannelConfigurer`, to encapsulate the configuration logic for channels. ```java - import java.util.function.Consumer; + import io.grpc.ManagedChannelBuilder; + import io.grpc.ServerBuilder; // Captures the intent of the plugin. - // Consumes a builder to modify it before the channel is built + // Consumes a builder to modify it before further configuring the channel or server public interface ChannelConfigurer { /** * Configures the given channel builder. * * @param builder the channel builder to configure */ - void configure(ManagedChannelBuilder builder); + default void configureChannelBuilder(ManagedChannelBuilder builder) {} + + /** + * Configures the given server builder. + * + * @param builder the server builder to configure + */ + default void configureServerBuilder(ServerBuilder builder) {} } ``` * ##### API Changes - * ManagedChannelBuilder: Add `ManagedChannelBuilder#childChannelConfigurer(ChannelConfigurer channelConfigurer)` to - allow users to register this configurer. - * XdsServerBuilder: Add `XdsServerBuilder#childChannelConfigurer(ChannelConfigurer configurer)` to allow users to - provide configuration for any internal channels created by the server (e.g., connections to external authorization or processing services). + * ManagedChannelBuilder: Add `ManagedChannelBuilder#childChannelConfigurer(ChannelConfigurer channelConfigurer)` to allow users to register this configurer. + * XdsServerBuilder: Add `XdsServerBuilder#childChannelConfigurer(ChannelConfigurer configurer)` to allow users to provide configuration for any internal channels created by the server (e.g., connections to external authorization or processing services). * ##### Usage Example ```java // Define the configurer for internal child channels - ChannelConfigurer myInternalConfig = (builder) -> { - builder.addMetricSink(sink); + ChannelConfigurer myInternalConfig = new ChannelConfigurer() { + @Override + public void configureChannelBuilder(ManagedChannelBuilder builder) { + builder.addMetricSink(sink); + } }; // Apply it to the parent channel @@ -203,18 +211,21 @@ In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pas * ##### Usage Example (User-Side Code) - ```c - // TODO(AgraVator): add example for configuring child channel observability + ```cpp + grpc::ChannelArguments child_channel_args; + // E.g., add a custom tracing interceptor specifically for child channels + child_args.SetPointer(GRPC_ARG_TRACING_PROVIDER, my_tracing_provider); + + grpc::ChannelArguments parent_args; + // Pass the nested args up + parent_args.SetChildChannelArgs(child_channel_args); + + std::shared_ptr channel = + grpc::CreateCustomChannel("xds:///my-service", credentials, parent_args); ``` ## Rationale ### Why not Global Configuration? -We reject global configuration (static variables) because it prevents multi-tenant applications from isolating configurations. For example, one client may need to export metrics to Prometheus, while another in the same process exports to Cloud Monitoring. - -## Implementation - -@AgraVator will be implementing immediately in grpc-java. A draft is available in -[PR 12578](https://github.com/grpc/grpc-java/pull/12578). Other languages will -follow, with work starting potentially in a few weeks. +We reject global configuration (static variables) because it prevents multi-tenant applications from isolating configurations. For example, one client may need to export metrics to Prometheus, while another in the same process exports to Cloud Monitoring. Furthermore, libraries want to configure their channels but cannot do so globally without affecting the host application. From 909878b4ad70533654f86d83eb75b244670d21de Mon Sep 17 00:00:00 2001 From: agravator Date: Tue, 17 Mar 2026 09:38:23 +0530 Subject: [PATCH 08/19] fix: formatting --- A110-child-channel-plugins.md | 150 +++++++++++++++++++++++++--------- 1 file changed, 113 insertions(+), 37 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index f97ed6abe..353b67923 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -1,5 +1,5 @@ -# A110: Child Channel Options - +A110: Child Channel Options +---- * Author(s): [Abhishek Agrawal](mailto:agrawalabhi@google.com) * Approver: a11r * Status: In Review @@ -9,15 +9,29 @@ ## Abstract -This proposal introduces a mechanism to configure "child channels", channels created internally by gRPC components (such as xDS control plane channel). Currently, these channels are often opaque to the user, making it difficult to inject necessary configurations for metrics, tracing etc from the user application. This design proposes an approach for users to pass configuration options to these internal channels. +This proposal introduces a mechanism to configure "child channels", channels +created internally by gRPC components (such as xDS control +user, making it difficult to inject necessary configurations for +metrics, tracing etc from the user application. This design proposes +an approach for users to pass configuration options to these +internal channels. +gRPC willl support the xDS Extension Config Discovery Service (ECDS), ## Background -Complex gRPC ecosystems often require the creation of auxiliary channels that are not directly instantiated by the user application. The primary examples are: +Complex gRPC ecosystems often require the creation of auxiliary channels that +are not directly instantiated by the user application. The primary examples are: -1. xDS (Extensible Discovery Service): When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control plane. -2. External Authorization (ext_authz): As described in [gRFC A92](https://github.com/grpc/proposal/pull/481), the gRPC server or client may create an internal channel to contact an external authorization service. -3. External Processing (ext_proc): As described in [gRFC A93](https://github.com/grpc/proposal/pull/484), filters may create internal channels to call external processing servers. +1. xDS (Extensible Discovery Service): When a user creates a channel with an xDS + target, the gRPC library internally creates a separate channel to communicate + with the xDS control plane. +2. External Authorization (ext_authz): As described + in [gRFC A92](https://github.com/grpc/proposal/pull/481), the gRPC server or + client may create an internal channel to contact an external authorization + service. +3. External Processing (ext_proc): As described + in [gRFC A93](https://github.com/grpc/proposal/pull/484), filters may create + internal channels to call external processing servers. ### Related Proposals @@ -29,49 +43,85 @@ Complex gRPC ecosystems often require the creation of auxiliary channels that ar ### The Problem -The primary motivation for this feature is the need to configure observability on a per-child-channel basis. +The primary motivation for this feature is the need to configure observability +on a per-child-channel basis. -* StatsPlugins & Tracing: Users need to configure metric sinks (as described in gRFC [A66](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) and [A72](https://github.com/grpc/proposal/blob/master/A72-open-telemetry-tracing.md)) so that telemetry from internal channels is correctly tagged and exported. -* Interceptors: Users may need to apply specific interceptors (e.g., for logging, or tracing) to internal traffic. +* StatsPlugins & Tracing: Users need to configure metric sinks (as described in + gRFC [A66](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) + and [A72](https://github.com/grpc/proposal/blob/master/A72-open-telemetry-tracing.md)) + so that telemetry from internal channels is correctly tagged and exported. +* Interceptors: Users may need to apply specific interceptors (e.g., for + logging, or tracing) to internal traffic. -These configurations cannot be set globally because different parts of an application may require different configurations, such as different metric backends. +These configurations cannot be set globally because different parts of an +application may require different configurations, such as different metric +backends. ## Proposal -We introduce the concept of **Child Channel Options**. This is a configuration container attached to a parent channel that is strictly designated for use by its children. +We introduce the concept of **Child Channel Options**. This is a configuration +container attached to a parent channel that is strictly designated for use by +its children. ### Encapsulation -The user API must allow "nesting" of channel options. A user creating a Parent Channel `P` can provide a set of options `O_child`. + +The user API must allow "nesting" of channel options. A user creating a Parent +Channel `P` can provide a set of options `O_child`. + * `O_child` is opaque to `P`. `P` does not apply these options to itself. -* `O_child` is carried in `P`'s state, available for extraction by internal components. -* The configuration provided by `O_child` is strictly uniform across all child channels of a particular parent channel. +* `O_child` is carried in `P`'s state, available for extraction by internal + components. +* The configuration provided by `O_child` is strictly uniform across all child + channels of a particular parent channel. ### Propagation -When an internal component (e.g., an xDS client factory or an auth filter) attached to `P` needs to create a Child Channel `C`: -1. It retrieves `O_child` from `P`. -2. It applies `O_child` to the configuration of `C`. + +When an internal component (e.g., an xDS client factory or an auth filter) +attached to `P` needs to create a Child Channel `C`: + +1. It retrieves `O_child` from `P`. +2. It applies `O_child` to the configuration of `C`. ### Precedence and Merging -The Child Channel `C` typically requires some internal configuration `O_internal` (e.g., target URIs, or internal interceptors). -* Merge Rule: `O_child` and `O_internal` are merged. If the environment supports global channel options, `O_child` options override global channel options. -* Conflict Resolution: Mandatory internal settings (`O_internal`) generally take precedence over user-provided child options (`O_child`) to ensure correctness. + +The Child Channel `C` typically requires some internal +configuration `O_internal` (e.g., target URIs, or internal interceptors). + +* Merge Rule: `O_child` and `O_internal` are merged. If the environment supports + global channel options, `O_child` options override global channel options. +* Conflict Resolution: Mandatory internal settings (`O_internal`) generally take + precedence over user-provided child options (`O_child`) to ensure correctness. ### Shared Resources -Certain internal channels, specifically the **xDS Control Plane Client**, are often pooled and shared across multiple parent channels within a process based on the target URI (see [gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md)). -If multiple Parent Channels (`P1`, `P2`) point to the same xDS target but provide *different* Child Channel Options (`O_child1`, `O_child2`): -* Behavior: The shared client is created using the options from the first parent channel that triggers its creation (e.g., `O_child1`). -* Subsequent Usage: When `P2` requests the client, it receives the existing shared client. `O_child2` is effectively ignored for that specific shared resource. +Certain internal channels, specifically the **xDS Control Plane Client**, are +often pooled and shared across multiple parent channels within a process based +on the target URI ( +see [gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md)). + +If multiple Parent Channels (`P1`, `P2`) point to the same xDS target but +provide *different* Child Channel Options (`O_child1`, `O_child2`): + +* Behavior: The shared client is created using the options from the first parent + channel that triggers its creation (e.g., `O_child1`). +* Subsequent Usage: When `P2` requests the client, it receives the existing + shared client. `O_child2` is effectively ignored for that specific shared + resource. ### Language Implementations #### Java -In Java, the configuration will be achieved by accepting functions (callbacks). The API allows users to pass a `Consumer>` (or a similar functional interface). When an internal library (e.g., xDS, gRPCLB) creates a child channel, it applies this user-provided function to the builder before further configuring the channel. +In Java, the configuration will be achieved by accepting functions (callbacks). +The API allows users to pass a `Consumer>` (or a +similar functional interface). When an internal library (e.g., xDS, gRPCLB) +creates a child channel, it applies this user-provided function to the builder +before further configuring the channel. * ##### Configuration Interface - Define a new public API interface, `ChannelConfigurer`, to encapsulate the configuration logic for channels. + Define a new public API interface, `ChannelConfigurer`, to encapsulate the + configuration logic for channels. ```java @@ -99,8 +149,14 @@ In Java, the configuration will be achieved by accepting functions (callbacks). * ##### API Changes - * ManagedChannelBuilder: Add `ManagedChannelBuilder#childChannelConfigurer(ChannelConfigurer channelConfigurer)` to allow users to register this configurer. - * XdsServerBuilder: Add `XdsServerBuilder#childChannelConfigurer(ChannelConfigurer configurer)` to allow users to provide configuration for any internal channels created by the server (e.g., connections to external authorization or processing services). + * ManagedChannelBuilder: + Add `ManagedChannelBuilder#childChannelConfigurer(ChannelConfigurer channelConfigurer)` + to allow users to register this configurer. + * XdsServerBuilder: + Add `XdsServerBuilder#childChannelConfigurer(ChannelConfigurer configurer)` + to allow users to provide configuration for any internal channels created + by the server (e.g., connections to external authorization or processing + services). * ##### Usage Example @@ -121,13 +177,15 @@ In Java, the configuration will be achieved by accepting functions (callbacks). #### Go -In Go, both the Client (`grpc.NewClient`) and the Server (`NewGRPCServer`) create internal child channels. We introduce mechanisms to pass `DialOption`s into these internal channels from both entry points. +In Go, both the Client (`grpc.NewClient`) and the Server (`NewGRPCServer`) +create internal child channels. We introduce mechanisms to pass `DialOption`s +into these internal channels from both entry points. * ##### New API for Child Channel Options * Client-Side: `WithChildChannelOptions` - For standard clients, we introduce a `DialOption` wrapper. + For standard clients, we introduce a `DialOption` wrapper. ```go // WithChildChannelOptions returns a DialOption that specifies a list of @@ -141,7 +199,10 @@ In Go, both the Client (`grpc.NewClient`) and the Server (`NewGRPCServer`) creat * Server-Side: `WithChildDialOptions` - For xDS-enabled servers, we introduce a `ServerOption` wrapper. Since `xds.NewGRPCServer` creates an internal xDS client to fetch listener configurations, it requires a way to apply `DialOptions` (such as **Socket Options** or **Stats Handlers**) to that internal connection. + For xDS-enabled servers, we introduce a `ServerOption` wrapper. + Since `xds.NewGRPCServer` creates an internal xDS client to fetch listener + configurations, it requires a way to apply `DialOptions` (such as **Socket + Options** or **Stats Handlers**) to that internal connection. ```go // WithChildDialOptions returns a ServerOption that specifies a list of @@ -156,7 +217,11 @@ In Go, both the Client (`grpc.NewClient`) and the Server (`NewGRPCServer`) creat * ##### Usage Example (User-Side Code) - This design provides users with the flexibility to define independent configurations for parent and child channels within a single NewClient call. For example, a parent channel can be configured with transport security (mTLS) while the internal child channels (such as the xDS control plane connection) are configured with specific interceptors or a custom authority. + This design provides users with the flexibility to define independent + configurations for parent and child channels within a single NewClient call. + For example, a parent channel can be configured with transport security (mTLS) + while the internal child channels (such as the xDS control plane connection) + are configured with specific interceptors or a custom authority. ```go func main() { @@ -188,11 +253,17 @@ In Go, both the Client (`grpc.NewClient`) and the Server (`NewGRPCServer`) creat ##### Core (C/C++) -In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pass configuration to internal channels. We define a standard argument key whose value is a pointer to another `grpc_channel_args` structure. This "Nested Arguments" pattern allows the parent channel to carry a specific subset of arguments intended solely for its children. +In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to +pass configuration to internal channels. We define a standard argument key whose +value is a pointer to another `grpc_channel_args` structure. This "Nested +Arguments" pattern allows the parent channel to carry a specific subset of +arguments intended solely for its children. * ##### Configuration Mechanism - We define a new channel argument key. The value associated with this key is a pointer to a `grpc_channel_args` struct, managed via a pointer vtable to ensure correct ownership and copying. + We define a new channel argument key. The value associated with this key is a + pointer to a `grpc_channel_args` struct, managed via a pointer vtable to + ensure correct ownership and copying. ```c // A pointer argument key. The value is a pointer to a grpc_channel_args @@ -202,7 +273,8 @@ In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pas * **API Changes** - We add a helper method to the C++ `ChannelArguments` class to simplify packing the nested arguments safely. + We add a helper method to the C++ `ChannelArguments` class to simplify packing + the nested arguments safely. ```cpp // Sets the channel arguments to be used for child channels. @@ -228,4 +300,8 @@ In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pas ### Why not Global Configuration? -We reject global configuration (static variables) because it prevents multi-tenant applications from isolating configurations. For example, one client may need to export metrics to Prometheus, while another in the same process exports to Cloud Monitoring. Furthermore, libraries want to configure their channels but cannot do so globally without affecting the host application. +We reject global configuration (static variables) because it prevents +multi-tenant applications from isolating configurations. For example, one client +may need to export metrics to Prometheus, while another in the same process +exports to Cloud Monitoring. Furthermore, libraries want to configure their +channels but cannot do so globally without affecting the host application. From e29251cfc3ccfe55f534e64f52e69920d248a944 Mon Sep 17 00:00:00 2001 From: agravator Date: Wed, 18 Mar 2026 13:19:16 +0530 Subject: [PATCH 09/19] fix: suggested changes --- A110-child-channel-plugins.md | 177 ++++++++++++++++++++-------------- 1 file changed, 104 insertions(+), 73 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index 353b67923..f3117498a 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -1,30 +1,30 @@ A110: Child Channel Options ---- + * Author(s): [Abhishek Agrawal](mailto:agrawalabhi@google.com) -* Approver: a11r +* Approver: @markdroth, @ejona86, @dfawley * Status: In Review -* Implemented in: Core, Java, Go -* Last updated: 2025-12-24 +* Implemented in: +* Last updated: 2026-03-18 * Discussion at: https://groups.google.com/g/grpc-io/c/EBIp3uud-Bo ## Abstract -This proposal introduces a mechanism to configure "child channels", channels -created internally by gRPC components (such as xDS control -user, making it difficult to inject necessary configurations for -metrics, tracing etc from the user application. This design proposes -an approach for users to pass configuration options to these -internal channels. -gRPC willl support the xDS Extension Config Discovery Service (ECDS), +There are several use cases where gRPC internally creates a "child channel". +Because these channels are created internally rather than being created by the +application, it is currently difficult to inject necessary configuration for +these channels, which makes it hard to configure things like metrics or tracing +from the application. This design proposes a mechanism to allow applications to +pass configuration options to these child channels. ## Background Complex gRPC ecosystems often require the creation of auxiliary channels that are not directly instantiated by the user application. The primary examples are: -1. xDS (Extensible Discovery Service): When a user creates a channel with an xDS - target, the gRPC library internally creates a separate channel to communicate - with the xDS control plane. +1. xDS: When a user creates a channel with an xDS target, the gRPC library + internally creates a separate channel to communicate with the xDS control + plane. 2. External Authorization (ext_authz): As described in [gRFC A92](https://github.com/grpc/proposal/pull/481), the gRPC server or client may create an internal channel to contact an external authorization @@ -33,56 +33,49 @@ are not directly instantiated by the user application. The primary examples are: in [gRFC A93](https://github.com/grpc/proposal/pull/484), filters may create internal channels to call external processing servers. -### Related Proposals - -* [A27: xDS-Based Global Load Balancing](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md) -* [A66: Otel Stats](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) -* [A72: OpenTelemetry Tracing](https://github.com/grpc/proposal/blob/master/A72-open-telemetry-tracing.md) -* [A92: xDS ExtAuthz Support](https://github.com/grpc/proposal/pull/481) -* [A93: xDS ExtProc Support](https://github.com/grpc/proposal/pull/484) - -### The Problem - The primary motivation for this feature is the need to configure observability on a per-child-channel basis. -* StatsPlugins & Tracing: Users need to configure metric sinks (as described in +* StatsPlugins: Users use these plugins to configure metrics and tracing (as + described in gRFC [A66](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) and [A72](https://github.com/grpc/proposal/blob/master/A72-open-telemetry-tracing.md)) so that telemetry from internal channels is correctly tagged and exported. * Interceptors: Users may need to apply specific interceptors (e.g., for logging, or tracing) to internal traffic. -These configurations cannot be set globally because different parts of an -application may require different configurations, such as different metric -backends. +Global configuration is not sufficient for these use cases for the reasons +described in the 'Rationale' section below. + +### Related Proposals + +* [A27: xDS-Based Global Load Balancing](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md) +* [A66: Otel Stats](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) +* [A72: OpenTelemetry Tracing](https://github.com/grpc/proposal/blob/master/A72-open-telemetry-tracing.md) +* [A92: xDS ExtAuthz Support](https://github.com/grpc/proposal/pull/481) +* [A93: xDS ExtProc Support](https://github.com/grpc/proposal/pull/484) ## Proposal We introduce the concept of **Child Channel Options**. This is a configuration -container attached to a parent channel that is strictly designated for use by -its children. - -### Encapsulation +container attached to a parent channel/server that is strictly designated for +use by its children. The user API must allow "nesting" of channel options. A user creating a Parent -Channel `P` can provide a set of options `O_child`. +Channel/Server `P` can provide a set of options `O_child`. * `O_child` is opaque to `P`. `P` does not apply these options to itself. * `O_child` is carried in `P`'s state, available for extraction by internal components. * The configuration provided by `O_child` is strictly uniform across all child - channels of a particular parent channel. - -### Propagation + channels of a particular parent channel/server. -When an internal component (e.g., an xDS client factory or an auth filter) -attached to `P` needs to create a Child Channel `C`: +When an internal component (e.g., an xDS client factory) attached to `P` +needs to create a Child Channel `C`: 1. It retrieves `O_child` from `P`. 2. It applies `O_child` to the configuration of `C`. - -### Precedence and Merging +3. It should also configure channel `C` to use `O_child` for its children. The Child Channel `C` typically requires some internal configuration `O_internal` (e.g., target URIs, or internal interceptors). @@ -92,18 +85,16 @@ configuration `O_internal` (e.g., target URIs, or internal interceptors). * Conflict Resolution: Mandatory internal settings (`O_internal`) generally take precedence over user-provided child options (`O_child`) to ensure correctness. -### Shared Resources - Certain internal channels, specifically the **xDS Control Plane Client**, are -often pooled and shared across multiple parent channels within a process based -on the target URI ( -see [gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md)). +often pooled and shared across multiple parent channels or servers within a +process based on the target URI (see +[gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md)). -If multiple Parent Channels (`P1`, `P2`) point to the same xDS target but -provide *different* Child Channel Options (`O_child1`, `O_child2`): +If multiple Parent Channels/Servers (`P1`, `P2`) point to the same xDS target +but provide *different* Child Channel Options (`O_child1`, `O_child2`): * Behavior: The shared client is created using the options from the first parent - channel that triggers its creation (e.g., `O_child1`). + channel or server that triggers its creation (e.g., `O_child1`). * Subsequent Usage: When `P2` requests the client, it receives the existing shared client. `O_child2` is effectively ignored for that specific shared resource. @@ -112,11 +103,11 @@ provide *different* Child Channel Options (`O_child1`, `O_child2`): #### Java -In Java, the configuration will be achieved by accepting functions (callbacks). -The API allows users to pass a `Consumer>` (or a -similar functional interface). When an internal library (e.g., xDS, gRPCLB) -creates a child channel, it applies this user-provided function to the builder -before further configuring the channel. +In Java, the configuration will be achieved by accepting functions. +The API allows users to pass a `ManagedChannelBuilder builder` or +`ServerBuilder builder` (or a similar functional interface). When an internal +library (e.g., xDS, gRPCLB) creates a child channel, it applies this +user-provided function to the builder before further configuring the channel. * ##### Configuration Interface @@ -251,15 +242,15 @@ into these internal channels from both entry points. } ``` -##### Core (C/C++) +#### Core (C/C++) In gRPC Core, we utilize the existing `ChannelArgs` mechanism recursively to pass configuration to internal channels. We define a standard argument key whose value is a pointer to another `grpc_channel_args` structure. This "Nested -Arguments" pattern allows the parent channel to carry a specific subset of -arguments intended solely for its children. +Arguments" pattern allows the parent channel or server to carry a specific +subset of arguments intended solely for its children. -* ##### Configuration Mechanism +* #### Configuration Mechanism We define a new channel argument key. The value associated with this key is a pointer to a `grpc_channel_args` struct, managed via a pointer vtable to @@ -271,37 +262,77 @@ arguments intended solely for its children. #define GRPC_ARG_CHILD_CHANNEL_ARGS "grpc.child_channel.args" ``` -* **API Changes** +* #### API Changes - We add a helper method to the C++ `ChannelArguments` class to simplify packing - the nested arguments safely. + We add a helper method to the C++ `ChannelArguments` and `ServerBuilder` + classes to simplify packing the nested arguments safely. ```cpp // Sets the channel arguments to be used for child channels. void SetChildChannelArgs(const ChannelArguments& args); ``` -* ##### Usage Example (User-Side Code) +* #### Usage Example (User-Side Code) - ```cpp - grpc::ChannelArguments child_channel_args; - // E.g., add a custom tracing interceptor specifically for child channels - child_args.SetPointer(GRPC_ARG_TRACING_PROVIDER, my_tracing_provider); + An example of how this will work on the channel side: + ```cpp + // Create OTel StatsPlugin. + grpc::OpenTelemetryPluginBuilder ot_plugin_builder; + // ...set options on builder... + auto ot_plugin = ot_plugin_builder.Build(); + assert(ot_plugin.ok()); + // Add the StatsPlugin to both child args and parent args. + grpc::ChannelArguments child_args; grpc::ChannelArguments parent_args; - // Pass the nested args up - parent_args.SetChildChannelArgs(child_channel_args); + ot_plugin->AddToChannelArguments(&child_args); + ot_plugin->AddToChannelArguments(&parent_args); + // Add child args to parent args. + parent_args.SetChildChannelArgs(child_args); + // Create channel with parent args. + auto channel = grpc::CreateCustomChannel( + "xds:///my-service", credentials, parent_args); + ``` + + An example of how this will work on the server side: - std::shared_ptr channel = - grpc::CreateCustomChannel("xds:///my-service", credentials, parent_args); + ```cpp + grpc::ServerBuilder server_builder; + // Create OTel StatsPlugin. + grpc::OpenTelemetryPluginBuilder ot_plugin_builder; + // ...set options on builder... + auto ot_plugin = ot_plugin_builder.Build(); + assert(ot_plugin.ok()); + // Add the StatsPlugin to both child args and the server builder. + grpc::ChannelArguments child_args; + ot_plugin->AddToChannelArguments(&child_args); + ot_plugin->AddToServerBuilder(&server_builder); + // Add the child args to the server builder. + server_builder.SetChildChannelArgs(child_args); + // Start the server. + auto server = server_builder.BuildAndStart(); ``` ## Rationale ### Why not Global Configuration? -We reject global configuration (static variables) because it prevents -multi-tenant applications from isolating configurations. For example, one client -may need to export metrics to Prometheus, while another in the same process -exports to Cloud Monitoring. Furthermore, libraries want to configure their -channels but cannot do so globally without affecting the host application. +The primary use-case we care about is setting a `StatsPlugin` for one particular +channel, in which case we want that same `StatsPlugin` to also be used for any +child of that channel. + +For example, let's say that we create two channels, one to target A and one to +target B, both of which create their own child channel to an `ext_authz` server +target Z. If we create the channel to target A with a specific `StatsPlugin`, +then we want that `StatsPlugin` to also be used for the child channel to target +Z created by the channel to target A. We do *not* want it to be used for the +child channel to target Z created by the channel to target B, because we did not +configure the `StatsPlugin` for the channel to target B. + +We cannot achieve this using the global registry, for a couple of reasons. +First, the global registry can only select channels based on parameters like the +target URI. To attach our `StatsPlugin` to the internal target Z, we would have to +select it based on target Z, which would erroneously attach it to the child +channels for both A and B. Second, it is hard for the application that registers +the global `StatsPlugin` to know what target URIs will be used for internal +child channels. From 03ee77753be515096cfcfe3f6ad90ba78842005d Mon Sep 17 00:00:00 2001 From: agravator Date: Thu, 19 Mar 2026 22:09:36 +0530 Subject: [PATCH 10/19] suggested changes --- A110-child-channel-plugins.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index f3117498a..b35dd265c 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -85,13 +85,16 @@ configuration `O_internal` (e.g., target URIs, or internal interceptors). * Conflict Resolution: Mandatory internal settings (`O_internal`) generally take precedence over user-provided child options (`O_child`) to ensure correctness. -Certain internal channels, specifically the **xDS Control Plane Client**, are -often pooled and shared across multiple parent channels or servers within a -process based on the target URI (see -[gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md)). +In some cases, child channels may be shared across multiple parent +channels/servers. For example, the xDS control plane channel is shared across +multiple channels or servers as described +in [gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md). +However, it is possible for each parent channel or server to be created with +different child options. In such cases, we will do the following: -If multiple Parent Channels/Servers (`P1`, `P2`) point to the same xDS target -but provide *different* Child Channel Options (`O_child1`, `O_child2`): +Consider an example where Parent Channels/Servers (`P1`, `P2`) point to the +same target but provide *different* Child Channel +Options (`O_child1`, `O_child2`): * Behavior: The shared client is created using the options from the first parent channel or server that triggers its creation (e.g., `O_child1`). @@ -250,7 +253,7 @@ value is a pointer to another `grpc_channel_args` structure. This "Nested Arguments" pattern allows the parent channel or server to carry a specific subset of arguments intended solely for its children. -* #### Configuration Mechanism +* ##### Configuration Mechanism We define a new channel argument key. The value associated with this key is a pointer to a `grpc_channel_args` struct, managed via a pointer vtable to @@ -262,7 +265,7 @@ subset of arguments intended solely for its children. #define GRPC_ARG_CHILD_CHANNEL_ARGS "grpc.child_channel.args" ``` -* #### API Changes +* ##### API Changes We add a helper method to the C++ `ChannelArguments` and `ServerBuilder` classes to simplify packing the nested arguments safely. @@ -272,7 +275,7 @@ subset of arguments intended solely for its children. void SetChildChannelArgs(const ChannelArguments& args); ``` -* #### Usage Example (User-Side Code) +* ##### Usage Example (User-Side Code) An example of how this will work on the channel side: From f9f021b3d2eff944b6c7233073baf2dfc4172260 Mon Sep 17 00:00:00 2001 From: agravator Date: Thu, 19 Mar 2026 22:16:45 +0530 Subject: [PATCH 11/19] suggested changes --- A110-child-channel-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index b35dd265c..c32d368ef 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -90,7 +90,7 @@ channels/servers. For example, the xDS control plane channel is shared across multiple channels or servers as described in [gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md). However, it is possible for each parent channel or server to be created with -different child options. In such cases, we will do the following: +different child options. Consider an example where Parent Channels/Servers (`P1`, `P2`) point to the same target but provide *different* Child Channel From 879a187c7a3a2d96fa4262cfb2984868813ba25c Mon Sep 17 00:00:00 2001 From: agravator Date: Wed, 15 Apr 2026 16:19:07 +0530 Subject: [PATCH 12/19] add future work --- A110-child-channel-plugins.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index c32d368ef..ed3aa4208 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -318,6 +318,12 @@ subset of arguments intended solely for its children. ## Rationale +The proposed mechanism of Child Channel Options provides a targeted way to +propagate configuration from a parent channel to its children. This approach is +chosen because it allows configuration to be scoped to specific parent-child +hierarchies, which is necessary for accurate telemetry and interceptor +application without affecting unrelated channels. + ### Why not Global Configuration? The primary use-case we care about is setting a `StatsPlugin` for one particular @@ -339,3 +345,15 @@ select it based on target Z, which would erroneously attach it to the child channels for both A and B. Second, it is hard for the application that registers the global `StatsPlugin` to know what target URIs will be used for internal child channels. + +## Non-Goals and Future Work + +### Differentiating Configuration Per Child Channel + +This proposal mandates that the child channel options provided by a parent are +uniform across all its child channels. We considered allowing different +configurations for different child channels (e.g., based on the purpose of the +channel like xDS vs RLS). However, this would require a mechanism to categorize +or identify the purpose of each child channel, which adds significant +complexity. Since no strong need for this was identified during initial +discussions, it is left for future work if and when it becomes necessary. From 20baf841c4f827eea26fbf030ccadaa2447f91fc Mon Sep 17 00:00:00 2001 From: agravator Date: Mon, 11 May 2026 21:38:05 +0530 Subject: [PATCH 13/19] LB policies and resolvers --- A110-child-channel-plugins.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index ed3aa4208..8ee613e03 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -102,6 +102,16 @@ Options (`O_child1`, `O_child2`): shared client. `O_child2` is effectively ignored for that specific shared resource. +**LB Policies and Resolvers** + +Some LB policies and resolvers may need to create child channels. We use `grpclb` as an example for how this plumbing will be handled in LB policies. Note that this proposal does not mandate any behavior changes for `grpclb` specifically. + +To support this, the child channel options must be plumbed down into resolvers and LB policies. Here are examples of how a component like `grpclb` would use this plumbing: + +* Java: The `Helper` will provide a function that accepts a `ChannelBuilder` and applies the child channel options to it. +* Go: A new field will be added to the `BuildOptions` struct (passed when creating a resolver or LB policy) to contain the child channel options. +* C-core: No special plumbing is needed because the child channel args are simply passed as channel arguments, which are already available to LB policies. + ### Language Implementations #### Java From 85a194b4ee7eb174e7567b47e2b376fcdf662500 Mon Sep 17 00:00:00 2001 From: AgraVator Date: Fri, 12 Jun 2026 14:43:45 +0530 Subject: [PATCH 14/19] fix: renamed configurer to configurator and added a section on nesting to make it clear --- A110-child-channel-plugins.md | 50 +++++++++++++++-------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index 8ee613e03..0f256d538 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -61,7 +61,7 @@ We introduce the concept of **Child Channel Options**. This is a configuration container attached to a parent channel/server that is strictly designated for use by its children. -The user API must allow "nesting" of channel options. A user creating a Parent +The user API must allow "nesting" of channel options (specifying child channel options within parent channel options). A user creating a Parent Channel/Server `P` can provide a set of options `O_child`. * `O_child` is opaque to `P`. `P` does not apply these options to itself. @@ -77,6 +77,8 @@ needs to create a Child Channel `C`: 2. It applies `O_child` to the configuration of `C`. 3. It should also configure channel `C` to use `O_child` for its children. +* **Multi-level Propagation**: The child options `O_child` apply recursively to all child channels, no matter how deeply nested. For example, if a parent channel `P` creates a child channel `C` (e.g., to an `ext_authz` server), and `C` itself is configured to use `xds:///` (which requires creating an xDS client control plane channel), `C` will pass `O_child` to its own child channels. + The Child Channel `C` typically requires some internal configuration `O_internal` (e.g., target URIs, or internal interceptors). @@ -116,48 +118,40 @@ To support this, the child channel options must be plumbed down into resolvers a #### Java -In Java, the configuration will be achieved by accepting functions. -The API allows users to pass a `ManagedChannelBuilder builder` or -`ServerBuilder builder` (or a similar functional interface). When an internal -library (e.g., xDS, gRPCLB) creates a child channel, it applies this -user-provided function to the builder before further configuring the channel. +In Java, the configuration will be achieved by accepting functional interfaces. +The API allows users to register a configurer on a `ManagedChannelBuilder` or +`ServerBuilder`. When an internal library (e.g., xDS, gRPCLB) creates a child +channel, it applies this user-provided configurer to the child's channel builder +before building the channel. * ##### Configuration Interface - Define a new public API interface, `ChannelConfigurer`, to encapsulate the + Define a new public API interface, `ChannelConfigurator`, to encapsulate the configuration logic for channels. ```java import io.grpc.ManagedChannelBuilder; - import io.grpc.ServerBuilder; // Captures the intent of the plugin. - // Consumes a builder to modify it before further configuring the channel or server - public interface ChannelConfigurer { + // Consumes a builder to modify it before further configuring the channel + public interface ChannelConfigurator { /** * Configures the given channel builder. * * @param builder the channel builder to configure */ - default void configureChannelBuilder(ManagedChannelBuilder builder) {} - - /** - * Configures the given server builder. - * - * @param builder the server builder to configure - */ - default void configureServerBuilder(ServerBuilder builder) {} + void configureChannelBuilder(ManagedChannelBuilder builder); } ``` * ##### API Changes * ManagedChannelBuilder: - Add `ManagedChannelBuilder#childChannelConfigurer(ChannelConfigurer channelConfigurer)` - to allow users to register this configurer. + Add `ManagedChannelBuilder#childChannelConfigurator(ChannelConfigurator channelConfigurator)` + to allow users to register this configurator. * XdsServerBuilder: - Add `XdsServerBuilder#childChannelConfigurer(ChannelConfigurer configurer)` + Add `XdsServerBuilder#childChannelConfigurator(ChannelConfigurator configurator)` to allow users to provide configuration for any internal channels created by the server (e.g., connections to external authorization or processing services). @@ -165,17 +159,17 @@ user-provided function to the builder before further configuring the channel. * ##### Usage Example ```java - // Define the configurer for internal child channels - ChannelConfigurer myInternalConfig = new ChannelConfigurer() { + // Define the configurator for internal child channels + ChannelConfigurator myInternalConfig = new ChannelConfigurator() { @Override public void configureChannelBuilder(ManagedChannelBuilder builder) { - builder.addMetricSink(sink); + builder.maxInboundMessageSize(4 * 1024 * 1024); } }; // Apply it to the parent channel ManagedChannel channel = ManagedChannelBuilder.forTarget("xds:///my-service") - .childChannelConfigurer(myInternalConfig) // <--- Configuration injected here + .childChannelConfigurator(myInternalConfig) // <--- Configuration injected here .build(); ``` @@ -201,7 +195,7 @@ into these internal channels from both entry points. } ``` -* Server-Side: `WithChildDialOptions` +* Server-Side: `ChildChannelOptions` For xDS-enabled servers, we introduce a `ServerOption` wrapper. Since `xds.NewGRPCServer` creates an internal xDS client to fetch listener @@ -209,10 +203,10 @@ into these internal channels from both entry points. Options** or **Stats Handlers**) to that internal connection. ```go - // WithChildDialOptions returns a ServerOption that specifies a list of + // ChildChannelOptions returns a ServerOption that specifies a list of // DialOptions to be applied to the server's internal child channels // (e.g., the xDS control plane connection). - func WithChildDialOptions(opts ...DialOption) ServerOption { + func ChildChannelOptions(opts ...DialOption) ServerOption { return newFuncServerOption(func(o *serverOptions) { o.childDialOptions = opts }) From d1e6333d1381c92a1c66cbf85636d365df410f2f Mon Sep 17 00:00:00 2001 From: AgraVator Date: Mon, 15 Jun 2026 21:39:08 +0530 Subject: [PATCH 15/19] fix: formatting --- A110-child-channel-plugins.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index 0f256d538..6748bc8d5 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -119,9 +119,9 @@ To support this, the child channel options must be plumbed down into resolvers a #### Java In Java, the configuration will be achieved by accepting functional interfaces. -The API allows users to register a configurer on a `ManagedChannelBuilder` or +The API allows users to register a configurator on a `ManagedChannelBuilder` or `ServerBuilder`. When an internal library (e.g., xDS, gRPCLB) creates a child -channel, it applies this user-provided configurer to the child's channel builder +channel, it applies this user-provided configurator to the child's channel builder before building the channel. * ##### Configuration Interface From e98c6366ea5a81a6f437aeee59079b138ff0cf92 Mon Sep 17 00:00:00 2001 From: AgraVator Date: Mon, 29 Jun 2026 16:46:29 +0530 Subject: [PATCH 16/19] Address review feedback on XdsClient lifetime options and C-core LB policy propagation --- A110-child-channel-plugins.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index 6748bc8d5..fe03cfa83 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -24,7 +24,7 @@ are not directly instantiated by the user application. The primary examples are: 1. xDS: When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control - plane. + plane. (Note: Child channel options are passed to the `XdsClient` when the `XdsClient` is created, and that `XdsClient` instance will use those same child channel options for any child channel it creates over its lifetime.) 2. External Authorization (ext_authz): As described in [gRFC A92](https://github.com/grpc/proposal/pull/481), the gRPC server or client may create an internal channel to contact an external authorization @@ -112,7 +112,7 @@ To support this, the child channel options must be plumbed down into resolvers a * Java: The `Helper` will provide a function that accepts a `ChannelBuilder` and applies the child channel options to it. * Go: A new field will be added to the `BuildOptions` struct (passed when creating a resolver or LB policy) to contain the child channel options. -* C-core: No special plumbing is needed because the child channel args are simply passed as channel arguments, which are already available to LB policies. +* C-core: No special plumbing is needed because the child channel args are simply passed as channel arguments, which are already available to LB policies. However, when an LB policy creates a child channel, it must propagate both the individual child channel args and the `GRPC_ARG_CHILD_CHANNEL_ARGS` argument containing the child channel args to the child channel. ### Language Implementations From e693475e1c55d76db3f9dd11602ffbf1dc233fd6 Mon Sep 17 00:00:00 2001 From: AgraVator Date: Mon, 29 Jun 2026 16:55:00 +0530 Subject: [PATCH 17/19] Format proposal prose and bullet points to 80-character line width limit --- A110-child-channel-plugins.md | 52 ++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index fe03cfa83..fb90108b1 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -24,7 +24,9 @@ are not directly instantiated by the user application. The primary examples are: 1. xDS: When a user creates a channel with an xDS target, the gRPC library internally creates a separate channel to communicate with the xDS control - plane. (Note: Child channel options are passed to the `XdsClient` when the `XdsClient` is created, and that `XdsClient` instance will use those same child channel options for any child channel it creates over its lifetime.) + plane. (Note: Child channel options are passed to the `XdsClient` when the + `XdsClient` is created, and that `XdsClient` instance will use those same + child channel options for any child channel it creates over its lifetime.) 2. External Authorization (ext_authz): As described in [gRFC A92](https://github.com/grpc/proposal/pull/481), the gRPC server or client may create an internal channel to contact an external authorization @@ -61,7 +63,8 @@ We introduce the concept of **Child Channel Options**. This is a configuration container attached to a parent channel/server that is strictly designated for use by its children. -The user API must allow "nesting" of channel options (specifying child channel options within parent channel options). A user creating a Parent +The user API must allow "nesting" of channel options (specifying child channel +options within parent channel options). A user creating a Parent Channel/Server `P` can provide a set of options `O_child`. * `O_child` is opaque to `P`. `P` does not apply these options to itself. @@ -77,7 +80,12 @@ needs to create a Child Channel `C`: 2. It applies `O_child` to the configuration of `C`. 3. It should also configure channel `C` to use `O_child` for its children. -* **Multi-level Propagation**: The child options `O_child` apply recursively to all child channels, no matter how deeply nested. For example, if a parent channel `P` creates a child channel `C` (e.g., to an `ext_authz` server), and `C` itself is configured to use `xds:///` (which requires creating an xDS client control plane channel), `C` will pass `O_child` to its own child channels. +* **Multi-level Propagation**: The child options `O_child` apply recursively to + all child channels, no matter how deeply nested. For example, if a parent + channel `P` creates a child channel `C` (e.g., to an `ext_authz` server), and + `C` itself is configured to use `xds:///` (which requires creating an xDS + client control plane channel), `C` will pass `O_child` to its own child + channels. The Child Channel `C` typically requires some internal configuration `O_internal` (e.g., target URIs, or internal interceptors). @@ -106,23 +114,35 @@ Options (`O_child1`, `O_child2`): **LB Policies and Resolvers** -Some LB policies and resolvers may need to create child channels. We use `grpclb` as an example for how this plumbing will be handled in LB policies. Note that this proposal does not mandate any behavior changes for `grpclb` specifically. - -To support this, the child channel options must be plumbed down into resolvers and LB policies. Here are examples of how a component like `grpclb` would use this plumbing: - -* Java: The `Helper` will provide a function that accepts a `ChannelBuilder` and applies the child channel options to it. -* Go: A new field will be added to the `BuildOptions` struct (passed when creating a resolver or LB policy) to contain the child channel options. -* C-core: No special plumbing is needed because the child channel args are simply passed as channel arguments, which are already available to LB policies. However, when an LB policy creates a child channel, it must propagate both the individual child channel args and the `GRPC_ARG_CHILD_CHANNEL_ARGS` argument containing the child channel args to the child channel. +Some LB policies and resolvers may need to create child channels. We use +`grpclb` as an example for how this plumbing will be handled in LB policies. +Note that this proposal does not mandate any behavior changes for `grpclb` +specifically. + +To support this, the child channel options must be plumbed down into resolvers +and LB policies. Here are examples of how a component like `grpclb` would use +this plumbing: + +* Java: The `Helper` will provide a function that accepts a `ChannelBuilder` and + applies the child channel options to it. +* Go: A new field will be added to the `BuildOptions` struct (passed when + creating a resolver or LB policy) to contain the child channel options. +* C-core: No special plumbing is needed because the child channel args are + simply passed as channel arguments, which are already available to LB + policies. However, when an LB policy creates a child channel, it must + propagate both the individual child channel args and the + `GRPC_ARG_CHILD_CHANNEL_ARGS` argument containing the child channel args to + the child channel. ### Language Implementations #### Java In Java, the configuration will be achieved by accepting functional interfaces. -The API allows users to register a configurator on a `ManagedChannelBuilder` or -`ServerBuilder`. When an internal library (e.g., xDS, gRPCLB) creates a child -channel, it applies this user-provided configurator to the child's channel builder -before building the channel. +The API allows users to register a configurator on a `ManagedChannelBuilder` +or `ServerBuilder`. When an internal library (e.g., xDS, gRPCLB) creates a +child channel, it applies this user-provided configurator to the child's channel +builder before building the channel. * ##### Configuration Interface @@ -344,8 +364,8 @@ configure the `StatsPlugin` for the channel to target B. We cannot achieve this using the global registry, for a couple of reasons. First, the global registry can only select channels based on parameters like the -target URI. To attach our `StatsPlugin` to the internal target Z, we would have to -select it based on target Z, which would erroneously attach it to the child +target URI. To attach our `StatsPlugin` to the internal target Z, we would have +to select it based on target Z, which would erroneously attach it to the child channels for both A and B. Second, it is hard for the application that registers the global `StatsPlugin` to know what target URIs will be used for internal child channels. From c1c7460cc9db24a256266b50314292f062fbaf3b Mon Sep 17 00:00:00 2001 From: AgraVator Date: Mon, 6 Jul 2026 14:36:35 +0530 Subject: [PATCH 18/19] Clarify recursive propagation of child channel options in Java and Go --- A110-child-channel-plugins.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index fb90108b1..db6afece5 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -124,9 +124,17 @@ and LB policies. Here are examples of how a component like `grpclb` would use this plumbing: * Java: The `Helper` will provide a function that accepts a `ChannelBuilder` and - applies the child channel options to it. -* Go: A new field will be added to the `BuildOptions` struct (passed when - creating a resolver or LB policy) to contain the child channel options. + applies the child channel options (`ChannelConfigurator`) to it. In addition, + when internal components (e.g., xDS transport factories, or `Helper` methods + creating OOB channels) create a child channel `C`, they must call both + `channelConfigurator.configureChannelBuilder(channelBuilder)` to apply the + options to `C` and `channelBuilder.childChannelConfigurator(channelConfigurator)` + to propagate `O_child` recursively to any further child channels created by `C`. +* Go: A new field (`ChildChannelOptions`) will be added to the `BuildOptions` + struct (passed when creating a resolver or LB policy) to contain the child + channel options. When a resolver or LB policy creates a child channel `C`, it + must apply `ChildChannelOptions` to `C` and propagate `ChildChannelOptions` so + that any further child channels created by `C` also inherit the options. * C-core: No special plumbing is needed because the child channel args are simply passed as channel arguments, which are already available to LB policies. However, when an LB policy creates a child channel, it must @@ -140,9 +148,11 @@ this plumbing: In Java, the configuration will be achieved by accepting functional interfaces. The API allows users to register a configurator on a `ManagedChannelBuilder` -or `ServerBuilder`. When an internal library (e.g., xDS, gRPCLB) creates a -child channel, it applies this user-provided configurator to the child's channel -builder before building the channel. +or `ServerBuilder`. When an internal library (e.g., xDS, gRPCLB, or out-of-band +channel helpers) creates a child channel `C`, it performs two steps on `C`'s builder: +1. Calls `channelConfigurator.configureChannelBuilder(builder)` to configure `C`. +2. Calls `builder.childChannelConfigurator(channelConfigurator)` to propagate the + configurator recursively to any further child channels created by `C`. * ##### Configuration Interface From 8339925c8017b28d50d395502a364c7c87a5ad24 Mon Sep 17 00:00:00 2001 From: AgraVator Date: Mon, 6 Jul 2026 15:35:50 +0530 Subject: [PATCH 19/19] Address review feedback on component responsibilities for child channel plugins --- A110-child-channel-plugins.md | 114 +++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 35 deletions(-) diff --git a/A110-child-channel-plugins.md b/A110-child-channel-plugins.md index db6afece5..6f2102bbc 100644 --- a/A110-child-channel-plugins.md +++ b/A110-child-channel-plugins.md @@ -119,40 +119,63 @@ Some LB policies and resolvers may need to create child channels. We use Note that this proposal does not mandate any behavior changes for `grpclb` specifically. -To support this, the child channel options must be plumbed down into resolvers -and LB policies. Here are examples of how a component like `grpclb` would use -this plumbing: - -* Java: The `Helper` will provide a function that accepts a `ChannelBuilder` and - applies the child channel options (`ChannelConfigurator`) to it. In addition, - when internal components (e.g., xDS transport factories, or `Helper` methods - creating OOB channels) create a child channel `C`, they must call both - `channelConfigurator.configureChannelBuilder(channelBuilder)` to apply the - options to `C` and `channelBuilder.childChannelConfigurator(channelConfigurator)` - to propagate `O_child` recursively to any further child channels created by `C`. -* Go: A new field (`ChildChannelOptions`) will be added to the `BuildOptions` - struct (passed when creating a resolver or LB policy) to contain the child - channel options. When a resolver or LB policy creates a child channel `C`, it - must apply `ChildChannelOptions` to `C` and propagate `ChildChannelOptions` so - that any further child channels created by `C` also inherit the options. -* C-core: No special plumbing is needed because the child channel args are - simply passed as channel arguments, which are already available to LB - policies. However, when an LB policy creates a child channel, it must - propagate both the individual child channel args and the - `GRPC_ARG_CHILD_CHANNEL_ARGS` argument containing the child channel args to - the child channel. +To support this, the child channel options must be plumbed down into +resolvers and LB policies. Each internal component that creates a child +channel `C` is explicitly responsible for applying and propagating those +options: + +* Java: When an LB policy creates an out-of-band (OOB) child channel via + `LoadBalancer.Helper` (e.g., `createResolvingOobChannelBuilder()` or + `createOobChannel()`), the `Helper` implementation (`ManagedChannelImpl`) + is responsible for automatically calling + `channelConfigurator.configureChannelBuilder(builder)` to apply the + options to `C` and `builder.childChannelConfigurator(channelConfigurator)` + to propagate `O_child` recursively to any further child channels. For + resolvers and other internal components (e.g., `XdsClient` via + `GrpcXdsTransportFactory`) that independently create a child channel `C`, + `channelConfigurator` is plumbed via + `NameResolver.Args.getChildChannelConfigurator()`. That internal + component is responsible for explicitly calling both + `channelConfigurator.configureChannelBuilder(channelBuilder)` and + `channelBuilder.childChannelConfigurator(channelConfigurator)` when + constructing `C`. +* Go: A new field (`ChildChannelOptions`) will be added to + `resolver.BuildOptions` and `balancer.BuildOptions` (passed when + creating a resolver or LB policy) to contain the child channel options + (`[]grpc.DialOption`). When a resolver (e.g., the xDS resolver creating + a control plane `XdsClient`) or an LB policy (e.g., `grpclb` creating an + out-of-band `ClientConn`) creates a child channel `C` (e.g., via + `grpc.NewClient`), that resolver or LB policy is responsible for applying + `ChildChannelOptions` to `C` and calling + `grpc.WithChildChannelOptions(ChildChannelOptions...)` so that any further + child channels created by `C` also inherit the options. +* C-core: No special plumbing is needed to pass child channel options to LB + policies and resolvers because they are already contained within the + channel arguments (`grpc_channel_args`). When an LB policy or resolver + (e.g., `grpclb` or an xDS resolver) creates a child channel `C`, that LB + policy or resolver is responsible for propagating both the individual + child channel args (`O_child` applied to `C`) and the + `GRPC_ARG_CHILD_CHANNEL_ARGS` argument containing the child channel args + (`O_child` propagated recursively) to `C`. ### Language Implementations #### Java -In Java, the configuration will be achieved by accepting functional interfaces. -The API allows users to register a configurator on a `ManagedChannelBuilder` -or `ServerBuilder`. When an internal library (e.g., xDS, gRPCLB, or out-of-band -channel helpers) creates a child channel `C`, it performs two steps on `C`'s builder: -1. Calls `channelConfigurator.configureChannelBuilder(builder)` to configure `C`. -2. Calls `builder.childChannelConfigurator(channelConfigurator)` to propagate the - configurator recursively to any further child channels created by `C`. +In Java, the configuration will be achieved by accepting functional +interfaces. The API allows users to register a configurator on a +`ManagedChannelBuilder` or `ServerBuilder`. When an internal library +or component creates a child channel `C`: +1. If created via `LoadBalancer.Helper` (`createResolvingOobChannelBuilder()` + or `createOobChannel()`), `ManagedChannelImpl` automatically calls + `channelConfigurator.configureChannelBuilder(builder)` and + `builder.childChannelConfigurator(channelConfigurator)` on `C`'s builder. +2. If created independently by a resolver or internal transport factory + (e.g., `GrpcXdsTransportFactory`), that component retrieves + `channelConfigurator` via + `NameResolver.Args.getChildChannelConfigurator()` and explicitly calls + `channelConfigurator.configureChannelBuilder(builder)` and + `builder.childChannelConfigurator(channelConfigurator)` on `C`'s builder. * ##### Configuration Interface @@ -178,13 +201,18 @@ channel helpers) creates a child channel `C`, it performs two steps on `C`'s bui * ##### API Changes * ManagedChannelBuilder: - Add `ManagedChannelBuilder#childChannelConfigurator(ChannelConfigurator channelConfigurator)` - to allow users to register this configurator. + Add `ManagedChannelBuilder#childChannelConfigurator(ChannelConfigurator` + `channelConfigurator)` to allow users to register this configurator. * XdsServerBuilder: - Add `XdsServerBuilder#childChannelConfigurator(ChannelConfigurator configurator)` - to allow users to provide configuration for any internal channels created - by the server (e.g., connections to external authorization or processing - services). + Add `XdsServerBuilder#childChannelConfigurator(ChannelConfigurator` + `configurator)` to allow users to provide configuration for any + internal channels created by the server (e.g., connections to external + authorization or processing services). + * NameResolver.Args: + Add `NameResolver.Args#getChildChannelConfigurator()` and + `NameResolver.Args.Builder#setChildChannelConfigurator(` + `ChannelConfigurator channelConfigurator)` to allow resolvers to access + the child channel configurator when creating internal child channels. * ##### Usage Example @@ -243,6 +271,22 @@ into these internal channels from both entry points. } ``` +* Resolvers and LB Policies: `BuildOptions` + + To allow resolvers and load balancers to access child channel options when + creating child channels, `ChildChannelOptions` will be added to both + `resolver.BuildOptions` and `balancer.BuildOptions`. + + ```go + type BuildOptions struct { + // ... existing fields ... + + // ChildChannelOptions contains the options to be applied to any + // internal child channels created by the resolver or load balancer. + ChildChannelOptions []DialOption + } + ``` + * ##### Usage Example (User-Side Code) This design provides users with the flexibility to define independent