Merge pull request #12236 from matt-kwong/kokoro_fil

Add test filtering to Kokoro Linux PR jobs
diff --git a/gRPC.podspec b/gRPC.podspec
index ba90149..79315e4 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -40,12 +40,9 @@
   s.header_dir = name
 
   src_dir = 'src/objective-c/GRPCClient'
-  s.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}"
-  s.private_header_files = "#{src_dir}/private/*.h"
-  s.header_mappings_dir = "#{src_dir}"
 
-  s.dependency 'gRPC-Core', version
   s.dependency 'gRPC-RxLibrary', version
+  s.default_subspec = 'Main'
 
   # Certificates, to be able to establish TLS connections:
   s.resource_bundles = { 'gRPCCertificates' => ['etc/roots.pem'] }
@@ -54,4 +51,22 @@
     # This is needed by all pods that depend on gRPC-RxLibrary:
     'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES',
   }
+
+  s.subspec 'Main' do |ss|
+    ss.header_mappings_dir = "#{src_dir}"
+
+    ss.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}"
+    ss.exclude_files = "#{src_dir}/GRPCCall+GID.{h,m}"
+    ss.private_header_files = "#{src_dir}/private/*.h"
+
+    ss.dependency 'gRPC-Core', version
+  end
+
+  s.subspec 'GID' do |ss|
+    ss.header_mappings_dir = "#{src_dir}"
+
+    ss.source_files = "#{src_dir}/GRPCCall+GID.{h,m}"
+
+    ss.dependency 'Google/SignIn'
+  end
 end
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.c b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.c
index bb9217d..087b407 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.c
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.c
@@ -1544,6 +1544,7 @@
     grpc_byte_buffer_reader bbr;
     grpc_byte_buffer_reader_init(&bbr, glb_policy->lb_response_payload);
     grpc_slice response_slice = grpc_byte_buffer_reader_readall(&bbr);
+    grpc_byte_buffer_reader_destroy(&bbr);
     grpc_byte_buffer_destroy(glb_policy->lb_response_payload);
 
     grpc_grpclb_initial_response *response = NULL;
diff --git a/src/core/lib/iomgr/ev_epoll1_linux.c b/src/core/lib/iomgr/ev_epoll1_linux.c
index 6b034ca..3ee646f 100644
--- a/src/core/lib/iomgr/ev_epoll1_linux.c
+++ b/src/core/lib/iomgr/ev_epoll1_linux.c
@@ -445,6 +445,7 @@
 }
 
 static grpc_error *pollset_kick_all(grpc_pollset *pollset) {
+  GPR_TIMER_BEGIN("pollset_kick_all", 0);
   grpc_error *error = GRPC_ERROR_NONE;
   if (pollset->root_worker != NULL) {
     grpc_pollset_worker *worker = pollset->root_worker;
@@ -470,7 +471,7 @@
   }
   // TODO: sreek.  Check if we need to set 'kicked_without_poller' to true here
   // in the else case
-
+  GPR_TIMER_END("pollset_kick_all", 0);
   return error;
 }
 
@@ -478,6 +479,7 @@
                                           grpc_pollset *pollset) {
   if (pollset->shutdown_closure != NULL && pollset->root_worker == NULL &&
       pollset->begin_refs == 0) {
+    GPR_TIMER_MARK("pollset_finish_shutdown", 0);
     GRPC_CLOSURE_SCHED(exec_ctx, pollset->shutdown_closure, GRPC_ERROR_NONE);
     pollset->shutdown_closure = NULL;
   }
@@ -485,12 +487,14 @@
 
 static void pollset_shutdown(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset,
                              grpc_closure *closure) {
+  GPR_TIMER_BEGIN("pollset_shutdown", 0);
   GPR_ASSERT(pollset->shutdown_closure == NULL);
   GPR_ASSERT(!pollset->shutting_down);
   pollset->shutdown_closure = closure;
   pollset->shutting_down = true;
   GRPC_LOG_IF_ERROR("pollset_shutdown", pollset_kick_all(pollset));
   pollset_maybe_finish_shutdown(exec_ctx, pollset);
+  GPR_TIMER_END("pollset_shutdown", 0);
 }
 
 #define MAX_EPOLL_EVENTS 100
@@ -518,6 +522,8 @@
   struct epoll_event events[MAX_EPOLL_EVENTS];
   static const char *err_desc = "pollset_poll";
 
+  GPR_TIMER_BEGIN("pollset_epoll", 0);
+
   int timeout = poll_deadline_to_millis_timeout(deadline, now);
 
   if (timeout != 0) {
@@ -525,13 +531,18 @@
   }
   int r;
   do {
+    GPR_TIMER_BEGIN("epoll_wait", 0);
     r = epoll_wait(g_epfd, events, MAX_EPOLL_EVENTS, timeout);
+    GPR_TIMER_END("epoll_wait", 0);
   } while (r < 0 && errno == EINTR);
   if (timeout != 0) {
     GRPC_SCHEDULING_END_BLOCKING_REGION;
   }
 
-  if (r < 0) return GRPC_OS_ERROR(errno, "epoll_wait");
+  if (r < 0) {
+    GPR_TIMER_END("pollset_epoll", 0);
+    return GRPC_OS_ERROR(errno, "epoll_wait");
+  }
 
   grpc_error *error = GRPC_ERROR_NONE;
   for (int i = 0; i < r; i++) {
@@ -552,13 +563,14 @@
       }
     }
   }
-
+  GPR_TIMER_END("pollset_epoll", 0);
   return error;
 }
 
 static bool begin_worker(grpc_pollset *pollset, grpc_pollset_worker *worker,
                          grpc_pollset_worker **worker_hdl, gpr_timespec *now,
                          gpr_timespec deadline) {
+  GPR_TIMER_BEGIN("begin_worker", 0);
   if (worker_hdl != NULL) *worker_hdl = worker;
   worker->initialized_cv = false;
   SET_KICK_STATE(worker, UNKICKED);
@@ -663,14 +675,17 @@
 
   if (pollset->kicked_without_poller) {
     pollset->kicked_without_poller = false;
+    GPR_TIMER_END("begin_worker", 0);
     return false;
   }
 
+  GPR_TIMER_END("begin_worker", 0);
   return worker->kick_state == DESIGNATED_POLLER && !pollset->shutting_down;
 }
 
 static bool check_neighbourhood_for_available_poller(
     pollset_neighbourhood *neighbourhood) {
+  GPR_TIMER_BEGIN("check_neighbourhood_for_available_poller", 0);
   bool found_worker = false;
   do {
     grpc_pollset *inspect = neighbourhood->active_root;
@@ -692,6 +707,7 @@
               }
               SET_KICK_STATE(inspect_worker, DESIGNATED_POLLER);
               if (inspect_worker->initialized_cv) {
+                GPR_TIMER_MARK("signal worker", 0);
                 gpr_cv_signal(&inspect_worker->cv);
               }
             } else {
@@ -727,12 +743,14 @@
     }
     gpr_mu_unlock(&inspect->mu);
   } while (!found_worker);
+  GPR_TIMER_END("check_neighbourhood_for_available_poller", 0);
   return found_worker;
 }
 
 static void end_worker(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset,
                        grpc_pollset_worker *worker,
                        grpc_pollset_worker **worker_hdl) {
+  GPR_TIMER_BEGIN("end_worker", 0);
   if (GRPC_TRACER_ON(grpc_polling_trace)) {
     gpr_log(GPR_DEBUG, "PS:%p END_WORKER:%p", pollset, worker);
   }
@@ -802,6 +820,7 @@
     pollset_maybe_finish_shutdown(exec_ctx, pollset);
   }
   GPR_ASSERT(gpr_atm_no_barrier_load(&g_active_poller) != (gpr_atm)worker);
+  GPR_TIMER_END("end_worker", 0);
 }
 
 /* pollset->po.mu lock must be held by the caller before calling this.
@@ -814,8 +833,10 @@
   grpc_pollset_worker worker;
   grpc_error *error = GRPC_ERROR_NONE;
   static const char *err_desc = "pollset_work";
+  GPR_TIMER_BEGIN("pollset_work", 0);
   if (pollset->kicked_without_poller) {
     pollset->kicked_without_poller = false;
+    GPR_TIMER_END("pollset_work", 0);
     return GRPC_ERROR_NONE;
   }
   if (begin_worker(pollset, &worker, worker_hdl, &now, deadline)) {
@@ -833,11 +854,14 @@
   }
   end_worker(exec_ctx, pollset, &worker, worker_hdl);
   gpr_tls_set(&g_current_thread_pollset, 0);
+  GPR_TIMER_END("pollset_work", 0);
   return error;
 }
 
 static grpc_error *pollset_kick(grpc_pollset *pollset,
                                 grpc_pollset_worker *specific_worker) {
+  GPR_TIMER_BEGIN("pollset_kick", 0);
+  grpc_error *ret_err = GRPC_ERROR_NONE;
   if (GRPC_TRACER_ON(grpc_polling_trace)) {
     gpr_strvec log;
     gpr_strvec_init(&log);
@@ -872,7 +896,7 @@
         if (GRPC_TRACER_ON(grpc_polling_trace)) {
           gpr_log(GPR_ERROR, " .. kicked_without_poller");
         }
-        return GRPC_ERROR_NONE;
+        goto done;
       }
       grpc_pollset_worker *next_worker = root_worker->next;
       if (root_worker->kick_state == KICKED) {
@@ -880,13 +904,13 @@
           gpr_log(GPR_ERROR, " .. already kicked %p", root_worker);
         }
         SET_KICK_STATE(root_worker, KICKED);
-        return GRPC_ERROR_NONE;
+        goto done;
       } else if (next_worker->kick_state == KICKED) {
         if (GRPC_TRACER_ON(grpc_polling_trace)) {
           gpr_log(GPR_ERROR, " .. already kicked %p", next_worker);
         }
         SET_KICK_STATE(next_worker, KICKED);
-        return GRPC_ERROR_NONE;
+        goto done;
       } else if (root_worker ==
                      next_worker &&  // only try and wake up a poller if
                                      // there is no next worker
@@ -896,7 +920,8 @@
           gpr_log(GPR_ERROR, " .. kicked %p", root_worker);
         }
         SET_KICK_STATE(root_worker, KICKED);
-        return grpc_wakeup_fd_wakeup(&global_wakeup_fd);
+        ret_err = grpc_wakeup_fd_wakeup(&global_wakeup_fd);
+        goto done;
       } else if (next_worker->kick_state == UNKICKED) {
         if (GRPC_TRACER_ON(grpc_polling_trace)) {
           gpr_log(GPR_ERROR, " .. kicked %p", next_worker);
@@ -904,7 +929,7 @@
         GPR_ASSERT(next_worker->initialized_cv);
         SET_KICK_STATE(next_worker, KICKED);
         gpr_cv_signal(&next_worker->cv);
-        return GRPC_ERROR_NONE;
+        goto done;
       } else if (next_worker->kick_state == DESIGNATED_POLLER) {
         if (root_worker->kick_state != DESIGNATED_POLLER) {
           if (GRPC_TRACER_ON(grpc_polling_trace)) {
@@ -917,59 +942,64 @@
           if (root_worker->initialized_cv) {
             gpr_cv_signal(&root_worker->cv);
           }
-          return GRPC_ERROR_NONE;
+          goto done;
         } else {
           if (GRPC_TRACER_ON(grpc_polling_trace)) {
             gpr_log(GPR_ERROR, " .. non-root poller %p (root=%p)", next_worker,
                     root_worker);
           }
           SET_KICK_STATE(next_worker, KICKED);
-          return grpc_wakeup_fd_wakeup(&global_wakeup_fd);
+          ret_err = grpc_wakeup_fd_wakeup(&global_wakeup_fd);
+          goto done;
         }
       } else {
         GPR_ASSERT(next_worker->kick_state == KICKED);
         SET_KICK_STATE(next_worker, KICKED);
-        return GRPC_ERROR_NONE;
+        goto done;
       }
     } else {
       if (GRPC_TRACER_ON(grpc_polling_trace)) {
         gpr_log(GPR_ERROR, " .. kicked while waking up");
       }
-      return GRPC_ERROR_NONE;
+      goto done;
     }
   } else if (specific_worker->kick_state == KICKED) {
     if (GRPC_TRACER_ON(grpc_polling_trace)) {
       gpr_log(GPR_ERROR, " .. specific worker already kicked");
     }
-    return GRPC_ERROR_NONE;
+    goto done;
   } else if (gpr_tls_get(&g_current_thread_worker) ==
              (intptr_t)specific_worker) {
     if (GRPC_TRACER_ON(grpc_polling_trace)) {
       gpr_log(GPR_ERROR, " .. mark %p kicked", specific_worker);
     }
     SET_KICK_STATE(specific_worker, KICKED);
-    return GRPC_ERROR_NONE;
+    goto done;
   } else if (specific_worker ==
              (grpc_pollset_worker *)gpr_atm_no_barrier_load(&g_active_poller)) {
     if (GRPC_TRACER_ON(grpc_polling_trace)) {
       gpr_log(GPR_ERROR, " .. kick active poller");
     }
     SET_KICK_STATE(specific_worker, KICKED);
-    return grpc_wakeup_fd_wakeup(&global_wakeup_fd);
+    ret_err = grpc_wakeup_fd_wakeup(&global_wakeup_fd);
+    goto done;
   } else if (specific_worker->initialized_cv) {
     if (GRPC_TRACER_ON(grpc_polling_trace)) {
       gpr_log(GPR_ERROR, " .. kick waiting worker");
     }
     SET_KICK_STATE(specific_worker, KICKED);
     gpr_cv_signal(&specific_worker->cv);
-    return GRPC_ERROR_NONE;
+    goto done;
   } else {
     if (GRPC_TRACER_ON(grpc_polling_trace)) {
       gpr_log(GPR_ERROR, " .. kick non-waiting worker");
     }
     SET_KICK_STATE(specific_worker, KICKED);
-    return GRPC_ERROR_NONE;
+    goto done;
   }
+done:
+  GPR_TIMER_END("pollset_kick", 0);
+  return ret_err;
 }
 
 static void pollset_add_fd(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset,
diff --git a/src/objective-c/GRPCClient/GRPCCall+GID.h b/src/objective-c/GRPCClient/GRPCCall+GID.h
new file mode 100644
index 0000000..3ee732e
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCCall+GID.h
@@ -0,0 +1,29 @@
+/*
+ *
+ * Copyright 2017 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import "GRPCCall.h"
+#import "GRPCCall+OAuth2.h"
+
+#import <Google/SignIn.h>
+
+/**
+ * Extend GIDSignIn class to comply GRPCAuthorizationProtocol
+ */
+@interface GIDSignIn (GRPC) <GRPCAuthorizationProtocol>
+- (void)getTokenWithHandler:(void (^)(NSString *token))hander;
+@end
diff --git a/src/objective-c/GRPCClient/GRPCCall+GID.m b/src/objective-c/GRPCClient/GRPCCall+GID.m
new file mode 100644
index 0000000..0307371
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCCall+GID.m
@@ -0,0 +1,28 @@
+/*
+ *
+ * Copyright 2017 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import "GRPCCall+GID.h"
+
+@implementation GIDSignIn (GRPC)
+
+- (void)getTokenWithHandler:(void (^)(NSString *token))handler {
+  NSString *token = self.currentUser.authentication.accessToken;
+  handler(token);
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h
index 65465e9..adb1042 100644
--- a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h
+++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h
@@ -18,6 +18,13 @@
 
 #import "GRPCCall.h"
 
+/**
+ * The protocol of an OAuth2 token object from which GRPCCall can acquire a token.
+ */
+@protocol GRPCAuthorizationProtocol
+- (void)getTokenWithHandler:(void (^)(NSString *token))hander;
+@end
+
 /** Helpers for setting and reading headers compatible with OAuth2. */
 @interface GRPCCall (OAuth2)
 
@@ -33,4 +40,12 @@
 /** Returns the value (if any) of the "www-authenticate" response header (the challenge header). */
 @property(atomic, readonly) NSString *oauth2ChallengeHeader;
 
+/**
+ * The authorization token object to be used when starting the call. If the value is set to nil, no
+ * oauth authentication will be used.
+ *
+ * If tokenProvider exists, it takes precedence over the token set by oauth2AccessToken.
+ */
+@property(atomic, strong) id<GRPCAuthorizationProtocol> tokenProvider;
+
 @end
diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.m b/src/objective-c/GRPCClient/GRPCCall+OAuth2.m
index eaa7465..8451ebe 100644
--- a/src/objective-c/GRPCClient/GRPCCall+OAuth2.m
+++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.m
@@ -16,6 +16,8 @@
  *
  */
 
+#import <objc/runtime.h>
+
 #import "GRPCCall+OAuth2.h"
 
 static NSString * const kAuthorizationHeader = @"authorization";
@@ -23,6 +25,7 @@
 static NSString * const kChallengeHeader = @"www-authenticate";
 
 @implementation GRPCCall (OAuth2)
+@dynamic tokenProvider;
 
 - (NSString *)oauth2AccessToken {
   NSString *headerValue = self.requestHeaders[kAuthorizationHeader];
@@ -45,4 +48,12 @@
   return self.responseHeaders[kChallengeHeader];
 }
 
+- (void)setTokenProvider:(id<GRPCAuthorizationProtocol>)tokenProvider {
+  objc_setAssociatedObject(self, @selector(tokenProvider), tokenProvider, OBJC_ASSOCIATION_RETAIN);
+}
+
+- (id<GRPCAuthorizationProtocol>)tokenProvider {
+  return objc_getAssociatedObject(self, @selector(tokenProvider));
+}
+
 @end
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m
index 8723624..436c19e 100644
--- a/src/objective-c/GRPCClient/GRPCCall.m
+++ b/src/objective-c/GRPCClient/GRPCCall.m
@@ -18,6 +18,8 @@
 
 #import "GRPCCall.h"
 
+#import "GRPCCall+OAuth2.h"
+
 #include <grpc/grpc.h>
 #include <grpc/support/time.h>
 #import <RxLibrary/GRXConcurrentWriteable.h>
@@ -40,10 +42,14 @@
 NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey";
 static NSMutableDictionary *callFlags;
 
+static NSString * const kAuthorizationHeader = @"authorization";
+static NSString * const kBearerPrefix = @"Bearer ";
+
 @interface GRPCCall () <GRXWriteable>
 // Make them read-write.
 @property(atomic, strong) NSDictionary *responseHeaders;
 @property(atomic, strong) NSDictionary *responseTrailers;
+@property(atomic) BOOL isWaitingForToken;
 @end
 
 // The following methods of a C gRPC call object aren't reentrant, and thus
@@ -181,9 +187,6 @@
 
 - (void)finishWithError:(NSError *)errorOrNil {
   @synchronized(self) {
-    if (_state == GRXWriterStateFinished) {
-      return;
-    }
     _state = GRXWriterStateFinished;
   }
 
@@ -211,7 +214,11 @@
   [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
                                             code:GRPCErrorCodeCancelled
                                         userInfo:@{NSLocalizedDescriptionKey: @"Canceled by app"}]];
-  [self cancelCall];
+  if (!self.isWaitingForToken) {
+    [self cancelCall];
+  } else {
+    self.isWaitingForToken = NO;
+  }
 }
 
 - (void)dealloc {
@@ -410,6 +417,36 @@
 
 #pragma mark GRXWriter implementation
 
+- (void)startCallWithWriteable:(id<GRXWriteable>)writeable {
+  _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable
+                                                           dispatchQueue:_responseQueue];
+
+  _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host
+                                            serverName:_serverName
+                                                  path:_path];
+  NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?");
+
+  [self sendHeaders:_requestHeaders];
+  [self invokeCall];
+
+  // TODO(jcanizales): Extract this logic somewhere common.
+  NSString *host = [NSURL URLWithString:[@"https://" stringByAppendingString:_host]].host;
+  if (!host) {
+    // TODO(jcanizales): Check this on init.
+    [NSException raise:NSInvalidArgumentException format:@"host of %@ is nil", _host];
+  }
+  _connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host];
+  __weak typeof(self) weakSelf = self;
+  void (^handler)() = ^{
+    typeof(self) strongSelf = weakSelf;
+    [strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
+                                                    code:GRPCErrorCodeUnavailable
+                                                userInfo:@{ NSLocalizedDescriptionKey : @"Connectivity lost." }]];
+  };
+  [_connectivityMonitor handleLossWithHandler:handler
+                      wifiStatusChangeHandler:nil];
+}
+
 - (void)startWithWriteable:(id<GRXWriteable>)writeable {
   @synchronized(self) {
     _state = GRXWriterStateStarted;
@@ -422,33 +459,23 @@
   // that the life of the instance is determined by this retain cycle.
   _retainSelf = self;
 
-  _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable
-                                                           dispatchQueue:_responseQueue];
-
-  _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host serverName:_serverName path:_path];
-  NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?");
-
-  [self sendHeaders:_requestHeaders];
-  [self invokeCall];
-
-  // TODO(jcanizales): Extract this logic somewhere common.
-  NSString *host = [NSURL URLWithString:[@"https://" stringByAppendingString:_host]].host;
-  if (!host) {
-    // TODO(jcanizales): Check this on init.
-    [NSException raise:NSInvalidArgumentException format:@"host of %@ is nil", _host];
+  if (self.tokenProvider != nil) {
+    self.isWaitingForToken = YES;
+    __weak typeof(self) weakSelf = self;
+    [self.tokenProvider getTokenWithHandler:^(NSString *token){
+      typeof(self) strongSelf = weakSelf;
+      if (strongSelf && strongSelf.isWaitingForToken) {
+        if (token) {
+          NSString *t = [kBearerPrefix stringByAppendingString:token];
+          strongSelf.requestHeaders[kAuthorizationHeader] = t;
+        }
+        [strongSelf startCallWithWriteable:writeable];
+        strongSelf.isWaitingForToken = NO;
+      }
+    }];
+  } else {
+    [self startCallWithWriteable:writeable];
   }
-  __weak typeof(self) weakSelf = self;
-  _connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host];
-  void (^handler)() = ^{
-    typeof(self) strongSelf = weakSelf;
-    if (strongSelf) {
-      [strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
-                                                      code:GRPCErrorCodeUnavailable
-                                                  userInfo:@{ NSLocalizedDescriptionKey : @"Connectivity lost." }]];
-    }
-  };
-  [_connectivityMonitor handleLossWithHandler:handler
-                      wifiStatusChangeHandler:nil];
 }
 
 - (void)setState:(GRXWriterState)newState {
diff --git a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m
index 7640a64..5de1d8f 100644
--- a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m
+++ b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m
@@ -103,7 +103,6 @@
 }
 
 - (void)setObject:(id)obj forKey:(NSString *)key {
-  [self checkCallIsNotStarted];
   CheckIsNonNilASCII(@"Header name", key);
   key = key.lowercaseString;
   CheckKeyValuePairIsValid(key, obj);
diff --git a/src/objective-c/RxLibrary/GRXConcurrentWriteable.m b/src/objective-c/RxLibrary/GRXConcurrentWriteable.m
index cb5d0a6..bbfe491 100644
--- a/src/objective-c/RxLibrary/GRXConcurrentWriteable.m
+++ b/src/objective-c/RxLibrary/GRXConcurrentWriteable.m
@@ -28,7 +28,7 @@
 @implementation GRXConcurrentWriteable {
   dispatch_queue_t _writeableQueue;
   // This ensures that writesFinishedWithError: is only sent once to the writeable.
-  dispatch_once_t _alreadyFinished;
+  BOOL _alreadyFinished;
 }
 
 - (instancetype)init {
@@ -65,19 +65,35 @@
 
 - (void)enqueueSuccessfulCompletion {
   dispatch_async(_writeableQueue, ^{
-    dispatch_once(&_alreadyFinished, ^{
+    BOOL finished = NO;
+    @synchronized (self) {
+      if (!_alreadyFinished) {
+        _alreadyFinished = YES;
+      } else {
+        finished = YES;
+      }
+    }
+    if (!finished) {
       // Cancellation is now impossible. None of the other three blocks can run concurrently with
       // this one.
       [self.writeable writesFinishedWithError:nil];
       // Skip any possible message to the wrapped writeable enqueued after this one.
       self.writeable = nil;
-    });
+    }
   });
 }
 
 - (void)cancelWithError:(NSError *)error {
   NSAssert(error, @"For a successful completion, use enqueueSuccessfulCompletion.");
-  dispatch_once(&_alreadyFinished, ^{
+  BOOL finished = NO;
+  @synchronized (self) {
+    if (!_alreadyFinished) {
+      _alreadyFinished = YES;
+    } else {
+      finished = YES;
+    }
+  }
+  if (!finished) {
     // Skip any of the still-enqueued messages to the wrapped writeable. We use the atomic setter to
     // nillify writeable because we might be running concurrently with the blocks in
     // _writeableQueue, and assignment with ARC isn't atomic.
@@ -87,15 +103,23 @@
     dispatch_async(_writeableQueue, ^{
       [writeable writesFinishedWithError:error];
     });
-  });
+  }
 }
 
 - (void)cancelSilently {
-  dispatch_once(&_alreadyFinished, ^{
+  BOOL finished = NO;
+  @synchronized (self) {
+    if (!_alreadyFinished) {
+      _alreadyFinished = YES;
+    } else {
+      finished = YES;
+    }
+  }
+  if (!finished) {
     // Skip any of the still-enqueued messages to the wrapped writeable. We use the atomic setter to
     // nillify writeable because we might be running concurrently with the blocks in
     // _writeableQueue, and assignment with ARC isn't atomic.
     self.writeable = nil;
-  });
+  }
 }
 @end
diff --git a/src/objective-c/tests/build_one_example.sh b/src/objective-c/tests/build_one_example.sh
index 298d8e7..985d55f 100755
--- a/src/objective-c/tests/build_one_example.sh
+++ b/src/objective-c/tests/build_one_example.sh
@@ -37,11 +37,11 @@
 pod install
 
 set -o pipefail
-XCODEBUILD_FILTER='(^===|^\*\*|\bfatal\b|\berror\b|\bwarning\b|\bfail|\bpassed\b)'
+XCODEBUILD_FILTER='(^CompileC |^Ld |^.*clang |^ *cd |^ *export |^Libtool |^.*libtool |^CpHeader |^ *builtin-copy )'
 xcodebuild \
     build \
     -workspace *.xcworkspace \
     -scheme $SCHEME \
     -destination name="iPhone 6" \
-    | egrep "$XCODEBUILD_FILTER" \
-    | egrep -v "(GPBDictionary|GPBArray)" -
+    | egrep -v "$XCODEBUILD_FILTER" \
+    | egrep -v "^$" -
diff --git a/src/objective-c/tests/run_tests.sh b/src/objective-c/tests/run_tests.sh
index 8843eda..5b7a2d1 100755
--- a/src/objective-c/tests/run_tests.sh
+++ b/src/objective-c/tests/run_tests.sh
@@ -38,7 +38,7 @@
 # element of the pipe fails.
 # TODO(jcanizales): Use xctool instead? Issue #2540.
 set -o pipefail
-XCODEBUILD_FILTER='(^===|^\*\*|\bfatal\b|\berror\b|\bwarning\b|\bfail|\bpassed\b)'
+XCODEBUILD_FILTER='(^CompileC |^Ld |^.*clang |^ *cd |^ *export |^Libtool |^.*libtool |^CpHeader |^ *builtin-copy )'
 echo "TIME:  $(date)"
 xcodebuild \
     -workspace Tests.xcworkspace \
@@ -48,8 +48,8 @@
     HOST_PORT_LOCAL=localhost:5050 \
     HOST_PORT_REMOTE=grpc-test.sandbox.googleapis.com \
     test \
-    | egrep "$XCODEBUILD_FILTER" \
-    | egrep -v "(GPBDictionary|GPBArray)" -
+    | egrep -v "$XCODEBUILD_FILTER" \
+    | egrep -v '^$' -
 
 echo "TIME:  $(date)"
 xcodebuild \
@@ -60,16 +60,12 @@
     | egrep "$XCODEBUILD_FILTER" \
     | egrep -v "(GPBDictionary|GPBArray)" -
 
-# Temporarily disabled for (possible) flakiness on Jenkins.
-# Fix or reenable after confirmation/disconfirmation that it is the source of
-# Jenkins problem.
-
-# echo "TIME:  $(date)"
-# xcodebuild \
-#     -workspace Tests.xcworkspace \
-#     -scheme CronetUnitTests \
-#     -destination name="iPhone 6" \
-#     test | xcpretty
+echo "TIME:  $(date)"
+xcodebuild \
+    -workspace Tests.xcworkspace \
+    -scheme CronetUnitTests \
+    -destination name="iPhone 6" \
+    test | xcpretty
 
 echo "TIME:  $(date)"
 xcodebuild \
diff --git a/templates/gRPC.podspec.template b/templates/gRPC.podspec.template
index 62a6d37..5c92f9f 100644
--- a/templates/gRPC.podspec.template
+++ b/templates/gRPC.podspec.template
@@ -42,12 +42,9 @@
     s.header_dir = name
 
     src_dir = 'src/objective-c/GRPCClient'
-    s.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}"
-    s.private_header_files = "#{src_dir}/private/*.h"
-    s.header_mappings_dir = "#{src_dir}"
 
-    s.dependency 'gRPC-Core', version
     s.dependency 'gRPC-RxLibrary', version
+    s.default_subspec = 'Main'
 
     # Certificates, to be able to establish TLS connections:
     s.resource_bundles = { 'gRPCCertificates' => ['etc/roots.pem'] }
@@ -56,4 +53,22 @@
       # This is needed by all pods that depend on gRPC-RxLibrary:
       'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES',
     }
+
+    s.subspec 'Main' do |ss|
+      ss.header_mappings_dir = "#{src_dir}"
+
+      ss.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}"
+      ss.exclude_files = "#{src_dir}/GRPCCall+GID.{h,m}"
+      ss.private_header_files = "#{src_dir}/private/*.h"
+
+      ss.dependency 'gRPC-Core', version
+    end
+
+    s.subspec 'GID' do |ss|
+      ss.header_mappings_dir = "#{src_dir}"
+
+      ss.source_files = "#{src_dir}/GRPCCall+GID.{h,m}"
+
+      ss.dependency 'Google/SignIn'
+    end
   end
diff --git a/tools/internal_ci/helper_scripts/prepare_build_macos_rc b/tools/internal_ci/helper_scripts/prepare_build_macos_rc
index b48040f..bd25095 100644
--- a/tools/internal_ci/helper_scripts/prepare_build_macos_rc
+++ b/tools/internal_ci/helper_scripts/prepare_build_macos_rc
@@ -55,6 +55,7 @@
 brew install coreutils  # we need grealpath
 sudo pip install virtualenv
 sudo pip install -U six tox setuptools
+export PYTHONPATH=/Library/Python/3.4/site-packages
 
 # python 3.4
 wget -q https://www.python.org/ftp/python/3.4.4/python-3.4.4-macosx10.6.pkg
diff --git a/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_dbg.cfg b/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_dbg.cfg
index dcc6265..8124f5c 100644
--- a/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_dbg.cfg
+++ b/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_dbg.cfg
@@ -26,5 +26,5 @@
 
 env_vars {
   key: "RUN_TESTS_FLAGS"
-  value: "-f basictests linux corelang dbg --inner_jobs 16 -j 1 --internal_ci"
+  value: "-f basictests linux corelang dbg --inner_jobs 16 -j 1 --internal_ci --max_time=3600"
 }
diff --git a/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_opt.cfg b/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_opt.cfg
index f60beaf..ecedc73 100644
--- a/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_opt.cfg
+++ b/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_opt.cfg
@@ -26,5 +26,5 @@
 
 env_vars {
   key: "RUN_TESTS_FLAGS"
-  value: "-f basictests linux corelang opt --inner_jobs 16 -j 1 --internal_ci"
+  value: "-f basictests linux corelang opt --inner_jobs 16 -j 1 --internal_ci --max_time=3600"
 }
diff --git a/tools/internal_ci/linux/pull_request/grpc_basictests_multilang.cfg b/tools/internal_ci/linux/pull_request/grpc_basictests_multilang.cfg
index 7c16cf6..f7e8d8a 100644
--- a/tools/internal_ci/linux/pull_request/grpc_basictests_multilang.cfg
+++ b/tools/internal_ci/linux/pull_request/grpc_basictests_multilang.cfg
@@ -26,5 +26,5 @@
 
 env_vars {
   key: "RUN_TESTS_FLAGS"
-  value: "-f basictests linux multilang --inner_jobs 16 -j 2 --internal_ci"
+  value: "-f basictests linux multilang --inner_jobs 16 -j 2 --internal_ci --max_time=3600"
 }
diff --git a/tools/internal_ci/macos/pull_request/grpc_basictests_dbg.cfg b/tools/internal_ci/macos/pull_request/grpc_basictests_dbg.cfg
index f058f0c..30c01d3 100644
--- a/tools/internal_ci/macos/pull_request/grpc_basictests_dbg.cfg
+++ b/tools/internal_ci/macos/pull_request/grpc_basictests_dbg.cfg
@@ -27,5 +27,5 @@
 
 env_vars {
   key: "RUN_TESTS_FLAGS"
-  value: "-f basictests macos dbg --internal_ci -j 1 --inner_jobs 4"
+  value: "-f basictests macos dbg --internal_ci -j 1 --inner_jobs 4 --max_time=3600"
 }
diff --git a/tools/internal_ci/macos/pull_request/grpc_basictests_opt.cfg b/tools/internal_ci/macos/pull_request/grpc_basictests_opt.cfg
index 5048baa..b63ee71 100644
--- a/tools/internal_ci/macos/pull_request/grpc_basictests_opt.cfg
+++ b/tools/internal_ci/macos/pull_request/grpc_basictests_opt.cfg
@@ -27,5 +27,5 @@
 
 env_vars {
   key: "RUN_TESTS_FLAGS"
-  value: "-f basictests macos opt --internal_ci -j 1 --inner_jobs 4"
+  value: "-f basictests macos opt --internal_ci -j 1 --inner_jobs 4 --max_time=3600"
 }
diff --git a/tools/internal_ci/windows/pull_request/grpc_basictests.cfg b/tools/internal_ci/windows/pull_request/grpc_basictests.cfg
index a116738..678ebeb 100644
--- a/tools/internal_ci/windows/pull_request/grpc_basictests.cfg
+++ b/tools/internal_ci/windows/pull_request/grpc_basictests.cfg
@@ -26,5 +26,5 @@
 
 env_vars {
   key: "RUN_TESTS_FLAGS"
-  value: "-f basictests windows -j 1 --inner_jobs 8 --internal_ci"
+  value: "-f basictests windows -j 1 --inner_jobs 8 --internal_ci --max_time=3600"
 }