Conversation
…ionFilter
Previously, `FilterStack` and `CompressionFilter` always executed
`sendMetadata`, `sendMessage`, and `receiveMessage` through `async` functions
and `Promise` chains—even when no compression was used and no asynchronous
filters were present in the stack. As a result, every uncompressed RPC had to
allocate multiple intermediate `Promise`s and closures and hop through the
microtask queue for metadata filtering, request framing, and response
deframing.
This change adds an optional synchronous fast path (`sendMetadataMaybeSync`,
`sendMessageMaybeSync`, and `receiveMessageMaybeSync`) to `Filter`,
`FilterStack`, `CompressionFilter`, and `ResolvingCall`.
- **Synchronous metadata and uncompressed message framing/deframing**:
`CompressionHandler.writeMessage` and `readMessage` now return a `Buffer`
synchronously when no compression or decompression is required (such as
`identity` encoding or when `WriteFlags.NoCompress` is set), only returning
a `Promise` when `zlib` compression or decompression is actually needed.
- **Zero-allocation pass-through for `BaseFilter` methods**: `FilterStack`
skips methods inherited unchanged from `BaseFilter` (such as pass-through
methods on `RouterFilter`) without allocating a `Promise`.
- **Shared `IDENTITY_HANDLER` singleton**: `IdentityHandler` has no instance
state, so `CompressionFilter` now reuses a module-level singleton instead of
allocating two new `IdentityHandler` instances per call.
- **Synchronous child call start on warm channels**: When channel configuration
is already resolved and metadata filters are synchronous, `ResolvingCall` now
starts the child `RetryingCall` and forwards the initial request message and
`halfClose` in the same turn, avoiding the intermediate `pendingMessage`
queuing in `ResolvingCall` and allowing `LoadBalancingCall` to flush the
initial message and half-close together when call credentials resolve.
- **Full backward compatibility for existing filters and callers**: The
existing `sendMetadata`, `sendMessage`, and `receiveMessage` methods on
`Filter`, `BaseFilter`, and `FilterStack` are preserved. When a legacy filter
is present in the stack, `FilterStack` seamlessly transitions to `Promise`
chaining for that filter and any subsequent filters in the pipeline.
- **Unchanged ordering and async guarantees**: When any filter in the stack
returns a `Promise` (for example, `gzip` or `deflate` compression, or a
custom async filter), `ResolvingCall` continues to set `writeFilterPending`
and `readFilterPending` to defer `halfClose` and `onReceiveStatus` until the
pending filter finishes. Status delivery in `outputStatus` continues to use
`process.nextTick`.
- **Minor housekeeping**:
- Clear `this.pendingMessage = null` in `ResolvingCall` and
`LoadBalancingCall` once the queued message has been forwarded to the child
call so the request buffer is not retained for the rest of the call.
- Check `if (this.ended) return;` when asynchronous metadata or message
filters resolve so cancelled calls do not start a child call or forward
in-flight messages after cancellation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Previously,
FilterStackandCompressionFilteralways executedsendMetadata,sendMessage, andreceiveMessagethroughasyncfunctions andPromisechains—even when no compression was used and no asynchronous filters were present in the stack. As a result, every uncompressed RPC had to allocate multiple intermediatePromises and closures and hop through the microtask queue for metadata filtering, request framing, and response deframing.This change adds an optional synchronous fast path (
sendMetadataMaybeSync,sendMessageMaybeSync, andreceiveMessageMaybeSync) toFilter,FilterStack,CompressionFilter, andResolvingCall.Synchronous metadata and uncompressed message framing/deframing:
CompressionHandler.writeMessageandreadMessagenow return aBuffersynchronously when no compression or decompression is required (such asidentityencoding or whenWriteFlags.NoCompressis set), only returning aPromisewhenzlibcompression or decompression is actually needed.Zero-allocation pass-through for
BaseFiltermethods:FilterStackskips methods inherited unchanged fromBaseFilter(such as pass-through methods onRouterFilter) without allocating aPromise.Shared
IDENTITY_HANDLERsingleton:IdentityHandlerhas no instance state, soCompressionFilternow reuses a module-level singleton instead of allocating two newIdentityHandlerinstances per call.Synchronous child call start on warm channels: When channel configuration is already resolved and metadata filters are synchronous,
ResolvingCallnow starts the childRetryingCalland forwards the initial request message andhalfClosein the same turn, avoiding the intermediatependingMessagequeuing inResolvingCalland allowingLoadBalancingCallto flush the initial message and half-close together when call credentials resolve.Full backward compatibility for existing filters and callers: The existing
sendMetadata,sendMessage, andreceiveMessagemethods onFilter,BaseFilter, andFilterStackare preserved. When a legacy filter is present in the stack,FilterStackseamlessly transitions toPromisechaining for that filter and any subsequent filters in the pipeline.Unchanged ordering and async guarantees: When any filter in the stack returns a
Promise(for example,gzipordeflatecompression, or a custom async filter),ResolvingCallcontinues to setwriteFilterPendingandreadFilterPendingto deferhalfCloseandonReceiveStatusuntil the pending filter finishes. Status delivery inoutputStatuscontinues to useprocess.nextTick.Minor housekeeping:
this.pendingMessage = nullinResolvingCallandLoadBalancingCallonce the queued message has been forwarded to the child call so the request buffer is not retained for the rest of the call.if (this.ended) return;when asynchronous metadata or message filters resolve so cancelled calls do not start a child call or forward in-flight messages after cancellation.