|  | @@ -0,0 +1,356 @@
 | 
	
		
			
				|  |  | +/*
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * Copyright 2020 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.
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// Event engine based on Apple's CFRunLoop API family. If the CFRunLoop engine
 | 
	
		
			
				|  |  | +/// is enabled (see iomgr_posix_cfstream.cc), a global thread is started to
 | 
	
		
			
				|  |  | +/// handle and trigger all the CFStream events. The CFStream streams register
 | 
	
		
			
				|  |  | +/// themselves with the run loop with functions grpc_apple_register_read_stream
 | 
	
		
			
				|  |  | +/// and grpc_apple_register_read_stream. Pollsets are dummy and block on a
 | 
	
		
			
				|  |  | +/// condition variable in pollset_work().
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#include <grpc/support/port_platform.h>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#include "src/core/lib/iomgr/port.h"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#ifdef GRPC_APPLE_EV
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#include <CoreFoundation/CoreFoundation.h>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#include <list>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#include "src/core/lib/gprpp/thd.h"
 | 
	
		
			
				|  |  | +#include "src/core/lib/iomgr/ev_apple.h"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +grpc_core::DebugOnlyTraceFlag grpc_apple_polling_trace(false, "apple_polling");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#ifndef NDEBUG
 | 
	
		
			
				|  |  | +#define GRPC_POLLING_TRACE(format, ...)                    \
 | 
	
		
			
				|  |  | +  if (GRPC_TRACE_FLAG_ENABLED(grpc_apple_polling_trace)) { \
 | 
	
		
			
				|  |  | +    gpr_log(GPR_DEBUG, "(polling) " format, __VA_ARGS__);  \
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +#else
 | 
	
		
			
				|  |  | +#define GRPC_POLLING_TRACE(...)
 | 
	
		
			
				|  |  | +#endif  // NDEBUG
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#define GRPC_POLLSET_KICK_BROADCAST ((grpc_pollset_worker*)1)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +struct GlobalRunLoopContext {
 | 
	
		
			
				|  |  | +  grpc_core::CondVar init_cv;
 | 
	
		
			
				|  |  | +  grpc_core::CondVar input_source_cv;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  grpc_core::Mutex mu;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Whether an input source registration is pending. Protected by mu.
 | 
	
		
			
				|  |  | +  bool input_source_registered = false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // The reference to the global run loop object. Protected by mu.
 | 
	
		
			
				|  |  | +  CFRunLoopRef run_loop;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Whether the pollset has been globally shut down. Protected by mu.
 | 
	
		
			
				|  |  | +  bool is_shutdown = false;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +struct GrpcAppleWorker {
 | 
	
		
			
				|  |  | +  // The condition varible to kick the worker. Works with the pollset's lock
 | 
	
		
			
				|  |  | +  // (GrpcApplePollset.mu).
 | 
	
		
			
				|  |  | +  grpc_core::CondVar cv;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Whether the worker is kicked. Protected by the pollset's lock
 | 
	
		
			
				|  |  | +  // (GrpcApplePollset.mu).
 | 
	
		
			
				|  |  | +  bool kicked = false;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +struct GrpcApplePollset {
 | 
	
		
			
				|  |  | +  grpc_core::Mutex mu;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Tracks the current workers in the pollset. Protected by mu.
 | 
	
		
			
				|  |  | +  std::list<GrpcAppleWorker*> workers;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Whether the pollset is shut down. Protected by mu.
 | 
	
		
			
				|  |  | +  bool is_shutdown = false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Closure to call when shutdown is done. Protected by mu.
 | 
	
		
			
				|  |  | +  grpc_closure* shutdown_closure;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Whether there's an outstanding kick that was not processed. Protected by
 | 
	
		
			
				|  |  | +  // mu.
 | 
	
		
			
				|  |  | +  bool kicked_without_poller = false;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static GlobalRunLoopContext* gGlobalRunLoopContext = nullptr;
 | 
	
		
			
				|  |  | +static grpc_core::Thread* gGlobalRunLoopThread = nullptr;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// Register the stream with the dispatch queue. Callbacks of the stream will be
 | 
	
		
			
				|  |  | +/// issued to the dispatch queue when a network event happens and will be
 | 
	
		
			
				|  |  | +/// managed by Grand Central Dispatch.
 | 
	
		
			
				|  |  | +static void grpc_apple_register_read_stream_queue(
 | 
	
		
			
				|  |  | +    CFReadStreamRef read_stream, dispatch_queue_t dispatch_queue) {
 | 
	
		
			
				|  |  | +  CFReadStreamSetDispatchQueue(read_stream, dispatch_queue);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// Register the stream with the dispatch queue. Callbacks of the stream will be
 | 
	
		
			
				|  |  | +/// issued to the dispatch queue when a network event happens and will be
 | 
	
		
			
				|  |  | +/// managed by Grand Central Dispatch.
 | 
	
		
			
				|  |  | +static void grpc_apple_register_write_stream_queue(
 | 
	
		
			
				|  |  | +    CFWriteStreamRef write_stream, dispatch_queue_t dispatch_queue) {
 | 
	
		
			
				|  |  | +  CFWriteStreamSetDispatchQueue(write_stream, dispatch_queue);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// Register the stream with the global run loop. Callbacks of the stream will
 | 
	
		
			
				|  |  | +/// be issued to the run loop when a network event happens and will be driven by
 | 
	
		
			
				|  |  | +/// the global run loop thread gGlobalRunLoopThread.
 | 
	
		
			
				|  |  | +static void grpc_apple_register_read_stream_run_loop(
 | 
	
		
			
				|  |  | +    CFReadStreamRef read_stream, dispatch_queue_t dispatch_queue) {
 | 
	
		
			
				|  |  | +  GRPC_POLLING_TRACE("Register read stream: %p", read_stream);
 | 
	
		
			
				|  |  | +  grpc_core::MutexLock lock(&gGlobalRunLoopContext->mu);
 | 
	
		
			
				|  |  | +  CFReadStreamScheduleWithRunLoop(read_stream, gGlobalRunLoopContext->run_loop,
 | 
	
		
			
				|  |  | +                                  kCFRunLoopDefaultMode);
 | 
	
		
			
				|  |  | +  gGlobalRunLoopContext->input_source_registered = true;
 | 
	
		
			
				|  |  | +  gGlobalRunLoopContext->input_source_cv.Signal();
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// Register the stream with the global run loop. Callbacks of the stream will
 | 
	
		
			
				|  |  | +/// be issued to the run loop when a network event happens, and will be driven
 | 
	
		
			
				|  |  | +/// by the global run loop thread gGlobalRunLoopThread.
 | 
	
		
			
				|  |  | +static void grpc_apple_register_write_stream_run_loop(
 | 
	
		
			
				|  |  | +    CFWriteStreamRef write_stream, dispatch_queue_t dispatch_queue) {
 | 
	
		
			
				|  |  | +  GRPC_POLLING_TRACE("Register write stream: %p", write_stream);
 | 
	
		
			
				|  |  | +  grpc_core::MutexLock lock(&gGlobalRunLoopContext->mu);
 | 
	
		
			
				|  |  | +  CFWriteStreamScheduleWithRunLoop(
 | 
	
		
			
				|  |  | +      write_stream, gGlobalRunLoopContext->run_loop, kCFRunLoopDefaultMode);
 | 
	
		
			
				|  |  | +  gGlobalRunLoopContext->input_source_registered = true;
 | 
	
		
			
				|  |  | +  gGlobalRunLoopContext->input_source_cv.Signal();
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// The default implementation of stream registration is to register the stream
 | 
	
		
			
				|  |  | +/// to a dispatch queue. However, if the CFRunLoop based pollset is enabled (by
 | 
	
		
			
				|  |  | +/// macro and environment variable, see docs in iomgr_posix_cfstream.cc), the
 | 
	
		
			
				|  |  | +/// CFStream streams are registered with the global run loop instead (see
 | 
	
		
			
				|  |  | +/// pollset_global_init below).
 | 
	
		
			
				|  |  | +static void (*grpc_apple_register_read_stream_impl)(
 | 
	
		
			
				|  |  | +    CFReadStreamRef, dispatch_queue_t) = grpc_apple_register_read_stream_queue;
 | 
	
		
			
				|  |  | +static void (*grpc_apple_register_write_stream_impl)(CFWriteStreamRef,
 | 
	
		
			
				|  |  | +                                                     dispatch_queue_t) =
 | 
	
		
			
				|  |  | +    grpc_apple_register_write_stream_queue;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void grpc_apple_register_read_stream(CFReadStreamRef read_stream,
 | 
	
		
			
				|  |  | +                                     dispatch_queue_t dispatch_queue) {
 | 
	
		
			
				|  |  | +  grpc_apple_register_read_stream_impl(read_stream, dispatch_queue);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void grpc_apple_register_write_stream(CFWriteStreamRef write_stream,
 | 
	
		
			
				|  |  | +                                      dispatch_queue_t dispatch_queue) {
 | 
	
		
			
				|  |  | +  grpc_apple_register_write_stream_impl(write_stream, dispatch_queue);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// Drive the run loop in a global singleton thread until the global run loop is
 | 
	
		
			
				|  |  | +/// shutdown.
 | 
	
		
			
				|  |  | +static void GlobalRunLoopFunc(void* arg) {
 | 
	
		
			
				|  |  | +  grpc_core::ReleasableMutexLock lock(&gGlobalRunLoopContext->mu);
 | 
	
		
			
				|  |  | +  gGlobalRunLoopContext->run_loop = CFRunLoopGetCurrent();
 | 
	
		
			
				|  |  | +  gGlobalRunLoopContext->init_cv.Signal();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  while (!gGlobalRunLoopContext->is_shutdown) {
 | 
	
		
			
				|  |  | +    // CFRunLoopRun() will return immediately if no stream is registered on it.
 | 
	
		
			
				|  |  | +    // So we wait on a conditional variable until a stream is registered;
 | 
	
		
			
				|  |  | +    // otherwise we'll be running a spinning loop.
 | 
	
		
			
				|  |  | +    while (!gGlobalRunLoopContext->input_source_registered) {
 | 
	
		
			
				|  |  | +      gGlobalRunLoopContext->input_source_cv.Wait(&gGlobalRunLoopContext->mu);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    gGlobalRunLoopContext->input_source_registered = false;
 | 
	
		
			
				|  |  | +    lock.Unlock();
 | 
	
		
			
				|  |  | +    CFRunLoopRun();
 | 
	
		
			
				|  |  | +    lock.Lock();
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  lock.Unlock();
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// pollset implementation
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static void pollset_global_init(void) {
 | 
	
		
			
				|  |  | +  gGlobalRunLoopContext = new GlobalRunLoopContext;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  grpc_apple_register_read_stream_impl =
 | 
	
		
			
				|  |  | +      grpc_apple_register_read_stream_run_loop;
 | 
	
		
			
				|  |  | +  grpc_apple_register_write_stream_impl =
 | 
	
		
			
				|  |  | +      grpc_apple_register_write_stream_run_loop;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  grpc_core::MutexLock lock(&gGlobalRunLoopContext->mu);
 | 
	
		
			
				|  |  | +  gGlobalRunLoopThread =
 | 
	
		
			
				|  |  | +      new grpc_core::Thread("apple_ev", GlobalRunLoopFunc, nullptr);
 | 
	
		
			
				|  |  | +  gGlobalRunLoopThread->Start();
 | 
	
		
			
				|  |  | +  while (gGlobalRunLoopContext->run_loop == NULL)
 | 
	
		
			
				|  |  | +    gGlobalRunLoopContext->init_cv.Wait(&gGlobalRunLoopContext->mu);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static void pollset_global_shutdown(void) {
 | 
	
		
			
				|  |  | +  {
 | 
	
		
			
				|  |  | +    grpc_core::MutexLock lock(&gGlobalRunLoopContext->mu);
 | 
	
		
			
				|  |  | +    gGlobalRunLoopContext->is_shutdown = true;
 | 
	
		
			
				|  |  | +    CFRunLoopStop(gGlobalRunLoopContext->run_loop);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  gGlobalRunLoopThread->Join();
 | 
	
		
			
				|  |  | +  delete gGlobalRunLoopThread;
 | 
	
		
			
				|  |  | +  delete gGlobalRunLoopContext;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// The caller must acquire the lock GrpcApplePollset.mu before calling this
 | 
	
		
			
				|  |  | +/// function. The lock may be temporarily released when waiting on the condition
 | 
	
		
			
				|  |  | +/// variable but will be re-acquired before the function returns.
 | 
	
		
			
				|  |  | +///
 | 
	
		
			
				|  |  | +/// The Apple pollset simply waits on a condition variable until it is kicked.
 | 
	
		
			
				|  |  | +/// The network events are handled in the global run loop thread. Processing of
 | 
	
		
			
				|  |  | +/// these events will eventually trigger the kick.
 | 
	
		
			
				|  |  | +static grpc_error* pollset_work(grpc_pollset* pollset,
 | 
	
		
			
				|  |  | +                                grpc_pollset_worker** worker,
 | 
	
		
			
				|  |  | +                                grpc_millis deadline) {
 | 
	
		
			
				|  |  | +  GRPC_POLLING_TRACE("pollset work: %p, worker: %p, deadline: %" PRIu64,
 | 
	
		
			
				|  |  | +                     pollset, worker, deadline);
 | 
	
		
			
				|  |  | +  GrpcApplePollset* apple_pollset =
 | 
	
		
			
				|  |  | +      reinterpret_cast<GrpcApplePollset*>(pollset);
 | 
	
		
			
				|  |  | +  GrpcAppleWorker actual_worker;
 | 
	
		
			
				|  |  | +  if (worker) {
 | 
	
		
			
				|  |  | +    *worker = reinterpret_cast<grpc_pollset_worker*>(&actual_worker);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (apple_pollset->kicked_without_poller) {
 | 
	
		
			
				|  |  | +    // Process the outstanding kick and reset the flag. Do not block.
 | 
	
		
			
				|  |  | +    apple_pollset->kicked_without_poller = false;
 | 
	
		
			
				|  |  | +  } else {
 | 
	
		
			
				|  |  | +    // Block until kicked, timed out, or the pollset shuts down.
 | 
	
		
			
				|  |  | +    apple_pollset->workers.push_front(&actual_worker);
 | 
	
		
			
				|  |  | +    auto it = apple_pollset->workers.begin();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    while (!actual_worker.kicked && !apple_pollset->is_shutdown) {
 | 
	
		
			
				|  |  | +      if (actual_worker.cv.Wait(
 | 
	
		
			
				|  |  | +              &apple_pollset->mu,
 | 
	
		
			
				|  |  | +              grpc_millis_to_timespec(deadline, GPR_CLOCK_REALTIME))) {
 | 
	
		
			
				|  |  | +        // timed out
 | 
	
		
			
				|  |  | +        break;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    apple_pollset->workers.erase(it);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // If the pollset is shut down asynchronously and this is the last pending
 | 
	
		
			
				|  |  | +    // worker, the shutdown process is complete at this moment and the shutdown
 | 
	
		
			
				|  |  | +    // callback will be called.
 | 
	
		
			
				|  |  | +    if (apple_pollset->is_shutdown && apple_pollset->workers.empty()) {
 | 
	
		
			
				|  |  | +      grpc_core::ExecCtx::Run(DEBUG_LOCATION, apple_pollset->shutdown_closure,
 | 
	
		
			
				|  |  | +                              GRPC_ERROR_NONE);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  return GRPC_ERROR_NONE;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// Kick a specific worker. The caller must acquire the lock GrpcApplePollset.mu
 | 
	
		
			
				|  |  | +/// before calling this function.
 | 
	
		
			
				|  |  | +static void kick_worker(GrpcAppleWorker* worker) {
 | 
	
		
			
				|  |  | +  worker->kicked = true;
 | 
	
		
			
				|  |  | +  worker->cv.Signal();
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// The caller must acquire the lock GrpcApplePollset.mu before calling this
 | 
	
		
			
				|  |  | +/// function. The kick action simply signals the condition variable of the
 | 
	
		
			
				|  |  | +/// worker.
 | 
	
		
			
				|  |  | +static grpc_error* pollset_kick(grpc_pollset* pollset,
 | 
	
		
			
				|  |  | +                                grpc_pollset_worker* specific_worker) {
 | 
	
		
			
				|  |  | +  GrpcApplePollset* apple_pollset =
 | 
	
		
			
				|  |  | +      reinterpret_cast<GrpcApplePollset*>(pollset);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  GRPC_POLLING_TRACE("pollset kick: %p, worker:%p", pollset, specific_worker);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (specific_worker == nullptr) {
 | 
	
		
			
				|  |  | +    if (apple_pollset->workers.empty()) {
 | 
	
		
			
				|  |  | +      apple_pollset->kicked_without_poller = true;
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +      GrpcAppleWorker* actual_worker = apple_pollset->workers.front();
 | 
	
		
			
				|  |  | +      kick_worker(actual_worker);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  } else if (specific_worker == GRPC_POLLSET_KICK_BROADCAST) {
 | 
	
		
			
				|  |  | +    for (auto& actual_worker : apple_pollset->workers) {
 | 
	
		
			
				|  |  | +      kick_worker(actual_worker);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  } else {
 | 
	
		
			
				|  |  | +    GrpcAppleWorker* actual_worker =
 | 
	
		
			
				|  |  | +        reinterpret_cast<GrpcAppleWorker*>(specific_worker);
 | 
	
		
			
				|  |  | +    kick_worker(actual_worker);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  return GRPC_ERROR_NONE;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static void pollset_init(grpc_pollset* pollset, gpr_mu** mu) {
 | 
	
		
			
				|  |  | +  GRPC_POLLING_TRACE("pollset init: %p", pollset);
 | 
	
		
			
				|  |  | +  GrpcApplePollset* apple_pollset = new (pollset) GrpcApplePollset();
 | 
	
		
			
				|  |  | +  *mu = apple_pollset->mu.get();
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// The caller must acquire the lock GrpcApplePollset.mu before calling this
 | 
	
		
			
				|  |  | +/// function.
 | 
	
		
			
				|  |  | +static void pollset_shutdown(grpc_pollset* pollset, grpc_closure* closure) {
 | 
	
		
			
				|  |  | +  GRPC_POLLING_TRACE("pollset shutdown: %p", pollset);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  GrpcApplePollset* apple_pollset =
 | 
	
		
			
				|  |  | +      reinterpret_cast<GrpcApplePollset*>(pollset);
 | 
	
		
			
				|  |  | +  apple_pollset->is_shutdown = true;
 | 
	
		
			
				|  |  | +  pollset_kick(pollset, GRPC_POLLSET_KICK_BROADCAST);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // If there is any worker blocked, shutdown will be done asynchronously.
 | 
	
		
			
				|  |  | +  if (apple_pollset->workers.empty()) {
 | 
	
		
			
				|  |  | +    grpc_core::ExecCtx::Run(DEBUG_LOCATION, closure, GRPC_ERROR_NONE);
 | 
	
		
			
				|  |  | +  } else {
 | 
	
		
			
				|  |  | +    apple_pollset->shutdown_closure = closure;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static void pollset_destroy(grpc_pollset* pollset) {
 | 
	
		
			
				|  |  | +  GRPC_POLLING_TRACE("pollset destroy: %p", pollset);
 | 
	
		
			
				|  |  | +  GrpcApplePollset* apple_pollset =
 | 
	
		
			
				|  |  | +      reinterpret_cast<GrpcApplePollset*>(pollset);
 | 
	
		
			
				|  |  | +  apple_pollset->~GrpcApplePollset();
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +size_t pollset_size(void) { return sizeof(GrpcApplePollset); }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +grpc_pollset_vtable grpc_apple_pollset_vtable = {
 | 
	
		
			
				|  |  | +    pollset_global_init, pollset_global_shutdown,
 | 
	
		
			
				|  |  | +    pollset_init,        pollset_shutdown,
 | 
	
		
			
				|  |  | +    pollset_destroy,     pollset_work,
 | 
	
		
			
				|  |  | +    pollset_kick,        pollset_size};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// pollset_set implementation
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +grpc_pollset_set* pollset_set_create(void) { return nullptr; }
 | 
	
		
			
				|  |  | +void pollset_set_destroy(grpc_pollset_set* pollset_set) {}
 | 
	
		
			
				|  |  | +void pollset_set_add_pollset(grpc_pollset_set* pollset_set,
 | 
	
		
			
				|  |  | +                             grpc_pollset* pollset) {}
 | 
	
		
			
				|  |  | +void pollset_set_del_pollset(grpc_pollset_set* pollset_set,
 | 
	
		
			
				|  |  | +                             grpc_pollset* pollset) {}
 | 
	
		
			
				|  |  | +void pollset_set_add_pollset_set(grpc_pollset_set* bag,
 | 
	
		
			
				|  |  | +                                 grpc_pollset_set* item) {}
 | 
	
		
			
				|  |  | +void pollset_set_del_pollset_set(grpc_pollset_set* bag,
 | 
	
		
			
				|  |  | +                                 grpc_pollset_set* item) {}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +grpc_pollset_set_vtable grpc_apple_pollset_set_vtable = {
 | 
	
		
			
				|  |  | +    pollset_set_create,          pollset_set_destroy,
 | 
	
		
			
				|  |  | +    pollset_set_add_pollset,     pollset_set_del_pollset,
 | 
	
		
			
				|  |  | +    pollset_set_add_pollset_set, pollset_set_del_pollset_set};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#endif
 |