mirror of https://github.com/grpc/grpc.git
commit
753b45dcf8
277 changed files with 7650 additions and 2129 deletions
@ -0,0 +1,118 @@ |
||||
## **gRPC Compression** |
||||
|
||||
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", |
||||
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be |
||||
interpreted as described in [RFC 2119](http://www.ietf.org/rfc/rfc2119.txt). |
||||
|
||||
### Intent |
||||
|
||||
Compression is used to reduce the amount of bandwidth used between peers. The |
||||
compression supported by gRPC acts _at the individual message level_, taking |
||||
_message_ [as defined in the wire format |
||||
document](PROTOCOL-HTTP2.md). |
||||
|
||||
The implementation supports different compression algorithms. A _default |
||||
compression level_, to be used in the absence of message-specific settings, MAY |
||||
be specified for during channel creation. |
||||
|
||||
The ability to control compression settings per call and to enable/disable |
||||
compression on a per message basis MAY be used to prevent CRIME/BEAST attacks. |
||||
It also allows for asymmetric compression communication, whereby a response MAY |
||||
be compressed differently, if at all. |
||||
|
||||
### Specification |
||||
|
||||
Compression MAY be configured by the Client Application by calling the |
||||
appropriate API method. There are two scenarios where compression MAY be |
||||
configured: |
||||
|
||||
+ At channel creation time, which sets the channel default compression and |
||||
therefore the compression that SHALL be used in the absence of per-RPC |
||||
compression configuration. |
||||
+ At response time, via: |
||||
+ For unary RPCs, the {Client,Server}Context instance. |
||||
+ For streaming RPCs, the {Client,Server}Writer instance. In this case, |
||||
configuration is reduced to disabling compression altogether. |
||||
|
||||
### Compression Method Asymmetry Between Peers |
||||
|
||||
A gRPC peer MAY choose to respond using a different compression method to that |
||||
of the request, including not performing any compression, regardless of channel |
||||
and RPC settings (for example, if compression would result in small or negative |
||||
gains). |
||||
|
||||
When a message from a client compressed with an unsupported algorithm is |
||||
processed by a server, it WILL result in an `UNIMPLEMENTED` error status on the |
||||
server. The server will then include in its response a `grpc-accept-encoding` |
||||
header specifying the algorithms it does accept. If an `UNIMPLEMENTED` error |
||||
status is returned from the server despite having used one of the algorithms |
||||
from the `grpc-accept-encoding` header, the cause MUST NOT be related to |
||||
compression. Data sent from a server compressed with an algorithm not supported |
||||
by the client WILL result in an `INTERNAL` error status on the client side. |
||||
|
||||
Note that a peer MAY choose to not disclose all the encodings it supports. |
||||
However, if it receives a message compressed in an undisclosed but supported |
||||
encoding, it MUST include said encoding in the response's `grpc-accept-encoding |
||||
h`eader. |
||||
|
||||
For every message a server is requested to compress using an algorithm it knows |
||||
the client doesn't support (as indicated by the last `grpc-accept-encoding` |
||||
header received from the client), it SHALL send the message uncompressed. |
||||
|
||||
### Specific Disabling of Compression |
||||
|
||||
If the user (through the previously described mechanisms) requests to disable |
||||
compression the next message MUST be sent uncompressed. This is instrumental in |
||||
preventing BEAST/CRIME attacks. This applies to both the the unary and streaming |
||||
cases. |
||||
|
||||
### Compression Levels and Algorithms |
||||
|
||||
The set of supported algorithm is implementation dependent. In order to simplify |
||||
the public API and to operate seamlessly across implementations (both in terms |
||||
of languages but also different version of the same one), we introduce the idea |
||||
of _compression levels_ (such as "low", "medium", "high"). |
||||
|
||||
Levels map to concrete algorithms and/or their settings (such as "low" mapping |
||||
to "gzip -3" and "high" mapping to "gzip -9") automatically depending on what a |
||||
peer is known to support. A server is always aware of what its clients support, |
||||
as clients disclose it in their Message-Accept-Encoding header as part of their |
||||
initial call. A client doesn't a priori (presently) know which algorithms a |
||||
server supports. This issue can be addressed with an initial negotiation of |
||||
capabilities or an automatic retry mechanism. These features will be implemented |
||||
in the future. Currently however, compression levels are only supported at the |
||||
server side, which is aware of the client's capabilities through the incoming |
||||
Message-Accept-Encoding header. |
||||
|
||||
### Propagation to child RPCs |
||||
|
||||
The inheritance of the compression configuration by child RPCs is left up to the |
||||
implementation. Note that in the absence of changes to the parent channel, its |
||||
configuration will be used. |
||||
|
||||
### Test cases |
||||
|
||||
1. When a compression level is not specified for either the channel or the |
||||
message, the default channel level _none_ is considered: data MUST NOT be |
||||
compressed. |
||||
1. When per-RPC compression configuration isn't present for a message, the |
||||
channel compression configuration MUST be used. |
||||
1. When a compression method (including no compression) is specified for an |
||||
outgoing message, the message MUST be compressed accordingly. |
||||
1. A message compressed by a client in a way not supported by its server MUST |
||||
fail with status `UNIMPLEMENTED`, its associated description indicating the |
||||
unsupported condition as well as the supported ones. The returned |
||||
`grpc-accept-encoding` header MUST NOT contain the compression method |
||||
(encoding) used. |
||||
1. A message compressed by a server in a way not supported by its client MUST |
||||
fail with status `INTERNAL`, its associated description indicating the |
||||
unsupported condition as well as the supported ones. The returned |
||||
`grpc-accept-encoding` header MUST NOT contain the compression method |
||||
(encoding) used. |
||||
1. An ill-constructed message with its [Compressed-Flag |
||||
bit](PROTOCOL-HTTP2.md#compressed-flag) |
||||
set but lacking a |
||||
"[grpc-encoding](PROTOCOL-HTTP2.md#message-encoding)" |
||||
entry different from _identity_ in its metadata MUST fail with `INTERNAL` |
||||
status, its associated description indicating the invalid Compressed-Flag |
||||
condition. |
@ -0,0 +1,91 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<Scheme |
||||
LastUpgradeVersion = "0730" |
||||
version = "1.3"> |
||||
<BuildAction |
||||
parallelizeBuildables = "YES" |
||||
buildImplicitDependencies = "YES"> |
||||
<BuildActionEntries> |
||||
<BuildActionEntry |
||||
buildForTesting = "YES" |
||||
buildForRunning = "YES" |
||||
buildForProfiling = "YES" |
||||
buildForArchiving = "YES" |
||||
buildForAnalyzing = "YES"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "63E1E97B1B28CB2000EF0978" |
||||
BuildableName = "AuthSample.app" |
||||
BlueprintName = "AuthSample" |
||||
ReferencedContainer = "container:AuthSample.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildActionEntry> |
||||
</BuildActionEntries> |
||||
</BuildAction> |
||||
<TestAction |
||||
buildConfiguration = "Debug" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||
<Testables> |
||||
</Testables> |
||||
<MacroExpansion> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "63E1E97B1B28CB2000EF0978" |
||||
BuildableName = "AuthSample.app" |
||||
BlueprintName = "AuthSample" |
||||
ReferencedContainer = "container:AuthSample.xcodeproj"> |
||||
</BuildableReference> |
||||
</MacroExpansion> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</TestAction> |
||||
<LaunchAction |
||||
buildConfiguration = "Debug" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
launchStyle = "0" |
||||
useCustomWorkingDirectory = "NO" |
||||
ignoresPersistentStateOnLaunch = "NO" |
||||
debugDocumentVersioning = "YES" |
||||
debugServiceExtension = "internal" |
||||
allowLocationSimulation = "YES"> |
||||
<BuildableProductRunnable |
||||
runnableDebuggingMode = "0"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "63E1E97B1B28CB2000EF0978" |
||||
BuildableName = "AuthSample.app" |
||||
BlueprintName = "AuthSample" |
||||
ReferencedContainer = "container:AuthSample.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildableProductRunnable> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</LaunchAction> |
||||
<ProfileAction |
||||
buildConfiguration = "Release" |
||||
shouldUseLaunchSchemeArgsEnv = "YES" |
||||
savedToolIdentifier = "" |
||||
useCustomWorkingDirectory = "NO" |
||||
debugDocumentVersioning = "YES"> |
||||
<BuildableProductRunnable |
||||
runnableDebuggingMode = "0"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "63E1E97B1B28CB2000EF0978" |
||||
BuildableName = "AuthSample.app" |
||||
BlueprintName = "AuthSample" |
||||
ReferencedContainer = "container:AuthSample.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildableProductRunnable> |
||||
</ProfileAction> |
||||
<AnalyzeAction |
||||
buildConfiguration = "Debug"> |
||||
</AnalyzeAction> |
||||
<ArchiveAction |
||||
buildConfiguration = "Release" |
||||
revealArchiveInOrganizer = "YES"> |
||||
</ArchiveAction> |
||||
</Scheme> |
@ -0,0 +1,91 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<Scheme |
||||
LastUpgradeVersion = "0730" |
||||
version = "1.3"> |
||||
<BuildAction |
||||
parallelizeBuildables = "YES" |
||||
buildImplicitDependencies = "YES"> |
||||
<BuildActionEntries> |
||||
<BuildActionEntry |
||||
buildForTesting = "YES" |
||||
buildForRunning = "YES" |
||||
buildForProfiling = "YES" |
||||
buildForArchiving = "YES" |
||||
buildForAnalyzing = "YES"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5E36905F1B2A23800040F884" |
||||
BuildableName = "HelloWorld.app" |
||||
BlueprintName = "HelloWorld" |
||||
ReferencedContainer = "container:HelloWorld.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildActionEntry> |
||||
</BuildActionEntries> |
||||
</BuildAction> |
||||
<TestAction |
||||
buildConfiguration = "Debug" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||
<Testables> |
||||
</Testables> |
||||
<MacroExpansion> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5E36905F1B2A23800040F884" |
||||
BuildableName = "HelloWorld.app" |
||||
BlueprintName = "HelloWorld" |
||||
ReferencedContainer = "container:HelloWorld.xcodeproj"> |
||||
</BuildableReference> |
||||
</MacroExpansion> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</TestAction> |
||||
<LaunchAction |
||||
buildConfiguration = "Debug" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
launchStyle = "0" |
||||
useCustomWorkingDirectory = "NO" |
||||
ignoresPersistentStateOnLaunch = "NO" |
||||
debugDocumentVersioning = "YES" |
||||
debugServiceExtension = "internal" |
||||
allowLocationSimulation = "YES"> |
||||
<BuildableProductRunnable |
||||
runnableDebuggingMode = "0"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5E36905F1B2A23800040F884" |
||||
BuildableName = "HelloWorld.app" |
||||
BlueprintName = "HelloWorld" |
||||
ReferencedContainer = "container:HelloWorld.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildableProductRunnable> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</LaunchAction> |
||||
<ProfileAction |
||||
buildConfiguration = "Release" |
||||
shouldUseLaunchSchemeArgsEnv = "YES" |
||||
savedToolIdentifier = "" |
||||
useCustomWorkingDirectory = "NO" |
||||
debugDocumentVersioning = "YES"> |
||||
<BuildableProductRunnable |
||||
runnableDebuggingMode = "0"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5E36905F1B2A23800040F884" |
||||
BuildableName = "HelloWorld.app" |
||||
BlueprintName = "HelloWorld" |
||||
ReferencedContainer = "container:HelloWorld.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildableProductRunnable> |
||||
</ProfileAction> |
||||
<AnalyzeAction |
||||
buildConfiguration = "Debug"> |
||||
</AnalyzeAction> |
||||
<ArchiveAction |
||||
buildConfiguration = "Release" |
||||
revealArchiveInOrganizer = "YES"> |
||||
</ArchiveAction> |
||||
</Scheme> |
@ -0,0 +1,91 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<Scheme |
||||
LastUpgradeVersion = "0730" |
||||
version = "1.3"> |
||||
<BuildAction |
||||
parallelizeBuildables = "YES" |
||||
buildImplicitDependencies = "YES"> |
||||
<BuildActionEntries> |
||||
<BuildActionEntry |
||||
buildForTesting = "YES" |
||||
buildForRunning = "YES" |
||||
buildForProfiling = "YES" |
||||
buildForArchiving = "YES" |
||||
buildForAnalyzing = "YES"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "6325277C1B1D0395003073D9" |
||||
BuildableName = "RouteGuideClient.app" |
||||
BlueprintName = "RouteGuideClient" |
||||
ReferencedContainer = "container:RouteGuideClient.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildActionEntry> |
||||
</BuildActionEntries> |
||||
</BuildAction> |
||||
<TestAction |
||||
buildConfiguration = "Debug" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||
<Testables> |
||||
</Testables> |
||||
<MacroExpansion> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "6325277C1B1D0395003073D9" |
||||
BuildableName = "RouteGuideClient.app" |
||||
BlueprintName = "RouteGuideClient" |
||||
ReferencedContainer = "container:RouteGuideClient.xcodeproj"> |
||||
</BuildableReference> |
||||
</MacroExpansion> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</TestAction> |
||||
<LaunchAction |
||||
buildConfiguration = "Debug" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
launchStyle = "0" |
||||
useCustomWorkingDirectory = "NO" |
||||
ignoresPersistentStateOnLaunch = "NO" |
||||
debugDocumentVersioning = "YES" |
||||
debugServiceExtension = "internal" |
||||
allowLocationSimulation = "YES"> |
||||
<BuildableProductRunnable |
||||
runnableDebuggingMode = "0"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "6325277C1B1D0395003073D9" |
||||
BuildableName = "RouteGuideClient.app" |
||||
BlueprintName = "RouteGuideClient" |
||||
ReferencedContainer = "container:RouteGuideClient.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildableProductRunnable> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</LaunchAction> |
||||
<ProfileAction |
||||
buildConfiguration = "Release" |
||||
shouldUseLaunchSchemeArgsEnv = "YES" |
||||
savedToolIdentifier = "" |
||||
useCustomWorkingDirectory = "NO" |
||||
debugDocumentVersioning = "YES"> |
||||
<BuildableProductRunnable |
||||
runnableDebuggingMode = "0"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "6325277C1B1D0395003073D9" |
||||
BuildableName = "RouteGuideClient.app" |
||||
BlueprintName = "RouteGuideClient" |
||||
ReferencedContainer = "container:RouteGuideClient.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildableProductRunnable> |
||||
</ProfileAction> |
||||
<AnalyzeAction |
||||
buildConfiguration = "Debug"> |
||||
</AnalyzeAction> |
||||
<ArchiveAction |
||||
buildConfiguration = "Release" |
||||
revealArchiveInOrganizer = "YES"> |
||||
</ArchiveAction> |
||||
</Scheme> |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,121 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
#include "src/core/lib/iomgr/endpoint.h" |
||||
|
||||
typedef struct endpoint_ll_node { |
||||
grpc_endpoint *ep; |
||||
struct endpoint_ll_node *next; |
||||
} endpoint_ll_node; |
||||
|
||||
static endpoint_ll_node *head = NULL; |
||||
static gpr_mu g_endpoint_mutex; |
||||
static bool g_init_done = false; |
||||
|
||||
void grpc_initialize_network_status_monitor() { |
||||
g_init_done = true; |
||||
gpr_mu_init(&g_endpoint_mutex); |
||||
// TODO(makarandd): Install callback with OS to monitor network status.
|
||||
} |
||||
|
||||
void grpc_destroy_network_status_monitor() { |
||||
for (endpoint_ll_node *curr = head; curr != NULL;) { |
||||
endpoint_ll_node *next = curr->next; |
||||
gpr_free(curr); |
||||
curr = next; |
||||
} |
||||
gpr_mu_destroy(&g_endpoint_mutex); |
||||
} |
||||
|
||||
void grpc_network_status_register_endpoint(grpc_endpoint *ep) { |
||||
if (!g_init_done) { |
||||
grpc_initialize_network_status_monitor(); |
||||
} |
||||
gpr_mu_lock(&g_endpoint_mutex); |
||||
if (head == NULL) { |
||||
head = (endpoint_ll_node *)gpr_malloc(sizeof(endpoint_ll_node)); |
||||
head->ep = ep; |
||||
head->next = NULL; |
||||
} else { |
||||
endpoint_ll_node *prev_head = head; |
||||
head = (endpoint_ll_node *)gpr_malloc(sizeof(endpoint_ll_node)); |
||||
head->ep = ep; |
||||
head->next = prev_head; |
||||
} |
||||
gpr_mu_unlock(&g_endpoint_mutex); |
||||
} |
||||
|
||||
void grpc_network_status_unregister_endpoint(grpc_endpoint *ep) { |
||||
gpr_mu_lock(&g_endpoint_mutex); |
||||
GPR_ASSERT(head); |
||||
bool found = false; |
||||
endpoint_ll_node *prev = head; |
||||
// if we're unregistering the head, just move head to the next
|
||||
if (ep == head->ep) { |
||||
head = head->next; |
||||
gpr_free(prev); |
||||
found = true; |
||||
} else { |
||||
for (endpoint_ll_node *curr = head->next; curr != NULL; curr = curr->next) { |
||||
if (ep == curr->ep) { |
||||
prev->next = curr->next; |
||||
gpr_free(curr); |
||||
found = true; |
||||
break; |
||||
} |
||||
prev = curr; |
||||
} |
||||
} |
||||
gpr_mu_unlock(&g_endpoint_mutex); |
||||
GPR_ASSERT(found); |
||||
} |
||||
|
||||
// Walk the linked-list from head and execute shutdown. It is possible that
|
||||
// other threads might be in the process of shutdown as well, but that has
|
||||
// no side effect since endpoint shutdown is idempotent.
|
||||
void grpc_network_status_shutdown_all_endpoints() { |
||||
gpr_mu_lock(&g_endpoint_mutex); |
||||
if (head == NULL) { |
||||
gpr_mu_unlock(&g_endpoint_mutex); |
||||
return; |
||||
} |
||||
grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; |
||||
|
||||
for (endpoint_ll_node *curr = head; curr != NULL; curr = curr->next) { |
||||
curr->ep->vtable->shutdown(&exec_ctx, curr->ep); |
||||
} |
||||
gpr_mu_unlock(&g_endpoint_mutex); |
||||
grpc_exec_ctx_finish(&exec_ctx); |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue