Merge branch 'master' into gpr_review
diff --git a/BUILD b/BUILD
index a28fc86..561993a 100644
--- a/BUILD
+++ b/BUILD
@@ -78,10 +78,6 @@
     "include/grpc/support/sync_windows.h",
     "include/grpc/support/thd.h",
     "include/grpc/support/time.h",
-    "include/grpc/support/tls.h",
-    "include/grpc/support/tls_gcc.h",
-    "include/grpc/support/tls_msvc.h",
-    "include/grpc/support/tls_pthread.h",
 ]
 
 GRPC_PUBLIC_HDRS = [
@@ -500,6 +496,10 @@
         "src/core/lib/gpr/string_windows.h",
         "src/core/lib/gpr/thd_internal.h",
         "src/core/lib/gpr/time_precise.h",
+        "src/core/lib/gpr/tls.h",
+        "src/core/lib/gpr/tls_gcc.h",
+        "src/core/lib/gpr/tls_msvc.h",
+        "src/core/lib/gpr/tls_pthread.h",
         "src/core/lib/gpr/tmpfile.h",
         "src/core/lib/gpr/useful.h",
         "src/core/lib/profiling/timers.h",
@@ -945,7 +945,6 @@
         "src/core/ext/filters/client_channel/proxy_mapper.cc",
         "src/core/ext/filters/client_channel/proxy_mapper_registry.cc",
         "src/core/ext/filters/client_channel/resolver.cc",
-        "src/core/ext/filters/client_channel/resolver_factory.cc",
         "src/core/ext/filters/client_channel/resolver_registry.cc",
         "src/core/ext/filters/client_channel/retry_throttle.cc",
         "src/core/ext/filters/client_channel/subchannel.cc",
@@ -975,8 +974,11 @@
     ],
     language = "c++",
     deps = [
+        "gpr_base",
         "grpc_base",
         "grpc_deadline_filter",
+        "inlined_vector",
+        "orphanable",
         "ref_counted",
         "ref_counted_ptr",
     ],
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8831c18..382d7ce 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -712,10 +712,6 @@
   include/grpc/support/sync_windows.h
   include/grpc/support/thd.h
   include/grpc/support/time.h
-  include/grpc/support/tls.h
-  include/grpc/support/tls_gcc.h
-  include/grpc/support/tls_msvc.h
-  include/grpc/support/tls_pthread.h
   include/grpc/impl/codegen/atm.h
   include/grpc/impl/codegen/atm_gcc_atomic.h
   include/grpc/impl/codegen/atm_gcc_sync.h
@@ -995,7 +991,6 @@
   src/core/ext/filters/client_channel/proxy_mapper.cc
   src/core/ext/filters/client_channel/proxy_mapper_registry.cc
   src/core/ext/filters/client_channel/resolver.cc
-  src/core/ext/filters/client_channel/resolver_factory.cc
   src/core/ext/filters/client_channel/resolver_registry.cc
   src/core/ext/filters/client_channel/retry_throttle.cc
   src/core/ext/filters/client_channel/subchannel.cc
@@ -1307,7 +1302,6 @@
   src/core/ext/filters/client_channel/proxy_mapper.cc
   src/core/ext/filters/client_channel/proxy_mapper_registry.cc
   src/core/ext/filters/client_channel/resolver.cc
-  src/core/ext/filters/client_channel/resolver_factory.cc
   src/core/ext/filters/client_channel/resolver_registry.cc
   src/core/ext/filters/client_channel/retry_throttle.cc
   src/core/ext/filters/client_channel/subchannel.cc
@@ -1603,7 +1597,6 @@
   src/core/ext/filters/client_channel/proxy_mapper.cc
   src/core/ext/filters/client_channel/proxy_mapper_registry.cc
   src/core/ext/filters/client_channel/resolver.cc
-  src/core/ext/filters/client_channel/resolver_factory.cc
   src/core/ext/filters/client_channel/resolver_registry.cc
   src/core/ext/filters/client_channel/retry_throttle.cc
   src/core/ext/filters/client_channel/subchannel.cc
@@ -1687,10 +1680,6 @@
   include/grpc/support/sync_windows.h
   include/grpc/support/thd.h
   include/grpc/support/time.h
-  include/grpc/support/tls.h
-  include/grpc/support/tls_gcc.h
-  include/grpc/support/tls_msvc.h
-  include/grpc/support/tls_pthread.h
   include/grpc/impl/codegen/atm.h
   include/grpc/impl/codegen/atm_gcc_atomic.h
   include/grpc/impl/codegen/atm_gcc_sync.h
@@ -1895,7 +1884,6 @@
   src/core/ext/filters/client_channel/proxy_mapper.cc
   src/core/ext/filters/client_channel/proxy_mapper_registry.cc
   src/core/ext/filters/client_channel/resolver.cc
-  src/core/ext/filters/client_channel/resolver_factory.cc
   src/core/ext/filters/client_channel/resolver_registry.cc
   src/core/ext/filters/client_channel/retry_throttle.cc
   src/core/ext/filters/client_channel/subchannel.cc
@@ -1979,10 +1967,6 @@
   include/grpc/support/sync_windows.h
   include/grpc/support/thd.h
   include/grpc/support/time.h
-  include/grpc/support/tls.h
-  include/grpc/support/tls_gcc.h
-  include/grpc/support/tls_msvc.h
-  include/grpc/support/tls_pthread.h
   include/grpc/impl/codegen/atm.h
   include/grpc/impl/codegen/atm_gcc_atomic.h
   include/grpc/impl/codegen/atm_gcc_sync.h
@@ -2200,7 +2184,6 @@
   src/core/ext/filters/client_channel/proxy_mapper.cc
   src/core/ext/filters/client_channel/proxy_mapper_registry.cc
   src/core/ext/filters/client_channel/resolver.cc
-  src/core/ext/filters/client_channel/resolver_factory.cc
   src/core/ext/filters/client_channel/resolver_registry.cc
   src/core/ext/filters/client_channel/retry_throttle.cc
   src/core/ext/filters/client_channel/subchannel.cc
@@ -2543,10 +2526,6 @@
   include/grpc/support/sync_windows.h
   include/grpc/support/thd.h
   include/grpc/support/time.h
-  include/grpc/support/tls.h
-  include/grpc/support/tls_gcc.h
-  include/grpc/support/tls_msvc.h
-  include/grpc/support/tls_pthread.h
   include/grpc/impl/codegen/atm.h
   include/grpc/impl/codegen/atm_gcc_atomic.h
   include/grpc/impl/codegen/atm_gcc_sync.h
@@ -2899,7 +2878,6 @@
   src/core/ext/filters/client_channel/proxy_mapper.cc
   src/core/ext/filters/client_channel/proxy_mapper_registry.cc
   src/core/ext/filters/client_channel/resolver.cc
-  src/core/ext/filters/client_channel/resolver_factory.cc
   src/core/ext/filters/client_channel/resolver_registry.cc
   src/core/ext/filters/client_channel/retry_throttle.cc
   src/core/ext/filters/client_channel/subchannel.cc
@@ -3012,10 +2990,6 @@
   include/grpc/support/sync_windows.h
   include/grpc/support/thd.h
   include/grpc/support/time.h
-  include/grpc/support/tls.h
-  include/grpc/support/tls_gcc.h
-  include/grpc/support/tls_msvc.h
-  include/grpc/support/tls_pthread.h
   include/grpc/impl/codegen/atm.h
   include/grpc/impl/codegen/atm_gcc_atomic.h
   include/grpc/impl/codegen/atm_gcc_sync.h
@@ -3737,10 +3711,6 @@
   include/grpc/support/sync_windows.h
   include/grpc/support/thd.h
   include/grpc/support/time.h
-  include/grpc/support/tls.h
-  include/grpc/support/tls_gcc.h
-  include/grpc/support/tls_msvc.h
-  include/grpc/support/tls_pthread.h
   include/grpc/impl/codegen/atm.h
   include/grpc/impl/codegen/atm_gcc_atomic.h
   include/grpc/impl/codegen/atm_gcc_sync.h
diff --git a/Makefile b/Makefile
index 1697e89..da6057e 100644
--- a/Makefile
+++ b/Makefile
@@ -2932,10 +2932,6 @@
     include/grpc/support/sync_windows.h \
     include/grpc/support/thd.h \
     include/grpc/support/time.h \
-    include/grpc/support/tls.h \
-    include/grpc/support/tls_gcc.h \
-    include/grpc/support/tls_msvc.h \
-    include/grpc/support/tls_pthread.h \
     include/grpc/impl/codegen/atm.h \
     include/grpc/impl/codegen/atm_gcc_atomic.h \
     include/grpc/impl/codegen/atm_gcc_sync.h \
@@ -3224,7 +3220,6 @@
     src/core/ext/filters/client_channel/proxy_mapper.cc \
     src/core/ext/filters/client_channel/proxy_mapper_registry.cc \
     src/core/ext/filters/client_channel/resolver.cc \
-    src/core/ext/filters/client_channel/resolver_factory.cc \
     src/core/ext/filters/client_channel/resolver_registry.cc \
     src/core/ext/filters/client_channel/retry_throttle.cc \
     src/core/ext/filters/client_channel/subchannel.cc \
@@ -3538,7 +3533,6 @@
     src/core/ext/filters/client_channel/proxy_mapper.cc \
     src/core/ext/filters/client_channel/proxy_mapper_registry.cc \
     src/core/ext/filters/client_channel/resolver.cc \
-    src/core/ext/filters/client_channel/resolver_factory.cc \
     src/core/ext/filters/client_channel/resolver_registry.cc \
     src/core/ext/filters/client_channel/retry_throttle.cc \
     src/core/ext/filters/client_channel/subchannel.cc \
@@ -3835,7 +3829,6 @@
     src/core/ext/filters/client_channel/proxy_mapper.cc \
     src/core/ext/filters/client_channel/proxy_mapper_registry.cc \
     src/core/ext/filters/client_channel/resolver.cc \
-    src/core/ext/filters/client_channel/resolver_factory.cc \
     src/core/ext/filters/client_channel/resolver_registry.cc \
     src/core/ext/filters/client_channel/retry_throttle.cc \
     src/core/ext/filters/client_channel/subchannel.cc \
@@ -3888,10 +3881,6 @@
     include/grpc/support/sync_windows.h \
     include/grpc/support/thd.h \
     include/grpc/support/time.h \
-    include/grpc/support/tls.h \
-    include/grpc/support/tls_gcc.h \
-    include/grpc/support/tls_msvc.h \
-    include/grpc/support/tls_pthread.h \
     include/grpc/impl/codegen/atm.h \
     include/grpc/impl/codegen/atm_gcc_atomic.h \
     include/grpc/impl/codegen/atm_gcc_sync.h \
@@ -4120,7 +4109,6 @@
     src/core/ext/filters/client_channel/proxy_mapper.cc \
     src/core/ext/filters/client_channel/proxy_mapper_registry.cc \
     src/core/ext/filters/client_channel/resolver.cc \
-    src/core/ext/filters/client_channel/resolver_factory.cc \
     src/core/ext/filters/client_channel/resolver_registry.cc \
     src/core/ext/filters/client_channel/retry_throttle.cc \
     src/core/ext/filters/client_channel/subchannel.cc \
@@ -4173,10 +4161,6 @@
     include/grpc/support/sync_windows.h \
     include/grpc/support/thd.h \
     include/grpc/support/time.h \
-    include/grpc/support/tls.h \
-    include/grpc/support/tls_gcc.h \
-    include/grpc/support/tls_msvc.h \
-    include/grpc/support/tls_pthread.h \
     include/grpc/impl/codegen/atm.h \
     include/grpc/impl/codegen/atm_gcc_atomic.h \
     include/grpc/impl/codegen/atm_gcc_sync.h \
@@ -4405,7 +4389,6 @@
     src/core/ext/filters/client_channel/proxy_mapper.cc \
     src/core/ext/filters/client_channel/proxy_mapper_registry.cc \
     src/core/ext/filters/client_channel/resolver.cc \
-    src/core/ext/filters/client_channel/resolver_factory.cc \
     src/core/ext/filters/client_channel/resolver_registry.cc \
     src/core/ext/filters/client_channel/retry_throttle.cc \
     src/core/ext/filters/client_channel/subchannel.cc \
@@ -4701,10 +4684,6 @@
     include/grpc/support/sync_windows.h \
     include/grpc/support/thd.h \
     include/grpc/support/time.h \
-    include/grpc/support/tls.h \
-    include/grpc/support/tls_gcc.h \
-    include/grpc/support/tls_msvc.h \
-    include/grpc/support/tls_pthread.h \
     include/grpc/impl/codegen/atm.h \
     include/grpc/impl/codegen/atm_gcc_atomic.h \
     include/grpc/impl/codegen/atm_gcc_sync.h \
@@ -5105,7 +5084,6 @@
     src/core/ext/filters/client_channel/proxy_mapper.cc \
     src/core/ext/filters/client_channel/proxy_mapper_registry.cc \
     src/core/ext/filters/client_channel/resolver.cc \
-    src/core/ext/filters/client_channel/resolver_factory.cc \
     src/core/ext/filters/client_channel/resolver_registry.cc \
     src/core/ext/filters/client_channel/retry_throttle.cc \
     src/core/ext/filters/client_channel/subchannel.cc \
@@ -5183,10 +5161,6 @@
     include/grpc/support/sync_windows.h \
     include/grpc/support/thd.h \
     include/grpc/support/time.h \
-    include/grpc/support/tls.h \
-    include/grpc/support/tls_gcc.h \
-    include/grpc/support/tls_msvc.h \
-    include/grpc/support/tls_pthread.h \
     include/grpc/impl/codegen/atm.h \
     include/grpc/impl/codegen/atm_gcc_atomic.h \
     include/grpc/impl/codegen/atm_gcc_sync.h \
@@ -5897,10 +5871,6 @@
     include/grpc/support/sync_windows.h \
     include/grpc/support/thd.h \
     include/grpc/support/time.h \
-    include/grpc/support/tls.h \
-    include/grpc/support/tls_gcc.h \
-    include/grpc/support/tls_msvc.h \
-    include/grpc/support/tls_pthread.h \
     include/grpc/impl/codegen/atm.h \
     include/grpc/impl/codegen/atm_gcc_atomic.h \
     include/grpc/impl/codegen/atm_gcc_sync.h \
diff --git a/build.yaml b/build.yaml
index 59915dc..0cfe04b 100644
--- a/build.yaml
+++ b/build.yaml
@@ -94,10 +94,6 @@
   - include/grpc/support/sync_windows.h
   - include/grpc/support/thd.h
   - include/grpc/support/time.h
-  - include/grpc/support/tls.h
-  - include/grpc/support/tls_gcc.h
-  - include/grpc/support/tls_msvc.h
-  - include/grpc/support/tls_pthread.h
   headers:
   - src/core/lib/gpr/arena.h
   - src/core/lib/gpr/env.h
@@ -110,6 +106,10 @@
   - src/core/lib/gpr/string_windows.h
   - src/core/lib/gpr/thd_internal.h
   - src/core/lib/gpr/time_precise.h
+  - src/core/lib/gpr/tls.h
+  - src/core/lib/gpr/tls_gcc.h
+  - src/core/lib/gpr/tls_msvc.h
+  - src/core/lib/gpr/tls_pthread.h
   - src/core/lib/gpr/tmpfile.h
   - src/core/lib/gpr/useful.h
   - src/core/lib/gprpp/abstract.h
@@ -473,7 +473,6 @@
   - src/core/ext/filters/client_channel/proxy_mapper.cc
   - src/core/ext/filters/client_channel/proxy_mapper_registry.cc
   - src/core/ext/filters/client_channel/resolver.cc
-  - src/core/ext/filters/client_channel/resolver_factory.cc
   - src/core/ext/filters/client_channel/resolver_registry.cc
   - src/core/ext/filters/client_channel/retry_throttle.cc
   - src/core/ext/filters/client_channel/subchannel.cc
diff --git a/config.m4 b/config.m4
index 7951f33..5bc2e58 100644
--- a/config.m4
+++ b/config.m4
@@ -290,7 +290,6 @@
     src/core/ext/filters/client_channel/proxy_mapper.cc \
     src/core/ext/filters/client_channel/proxy_mapper_registry.cc \
     src/core/ext/filters/client_channel/resolver.cc \
-    src/core/ext/filters/client_channel/resolver_factory.cc \
     src/core/ext/filters/client_channel/resolver_registry.cc \
     src/core/ext/filters/client_channel/retry_throttle.cc \
     src/core/ext/filters/client_channel/subchannel.cc \
diff --git a/config.w32 b/config.w32
index f054773..2ef122b 100644
--- a/config.w32
+++ b/config.w32
@@ -267,7 +267,6 @@
     "src\\core\\ext\\filters\\client_channel\\proxy_mapper.cc " +
     "src\\core\\ext\\filters\\client_channel\\proxy_mapper_registry.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver.cc " +
-    "src\\core\\ext\\filters\\client_channel\\resolver_factory.cc " +
     "src\\core\\ext\\filters\\client_channel\\resolver_registry.cc " +
     "src\\core\\ext\\filters\\client_channel\\retry_throttle.cc " +
     "src\\core\\ext\\filters\\client_channel\\subchannel.cc " +
diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec
index ef2587b..5bd3b0b 100644
--- a/gRPC-C++.podspec
+++ b/gRPC-C++.podspec
@@ -214,6 +214,10 @@
                       'src/core/lib/gpr/string_windows.h',
                       'src/core/lib/gpr/thd_internal.h',
                       'src/core/lib/gpr/time_precise.h',
+                      'src/core/lib/gpr/tls.h',
+                      'src/core/lib/gpr/tls_gcc.h',
+                      'src/core/lib/gpr/tls_msvc.h',
+                      'src/core/lib/gpr/tls_pthread.h',
                       'src/core/lib/gpr/tmpfile.h',
                       'src/core/lib/gpr/useful.h',
                       'src/core/lib/gprpp/abstract.h',
@@ -462,6 +466,10 @@
                               'src/core/lib/gpr/string_windows.h',
                               'src/core/lib/gpr/thd_internal.h',
                               'src/core/lib/gpr/time_precise.h',
+                              'src/core/lib/gpr/tls.h',
+                              'src/core/lib/gpr/tls_gcc.h',
+                              'src/core/lib/gpr/tls_msvc.h',
+                              'src/core/lib/gpr/tls_pthread.h',
                               'src/core/lib/gpr/tmpfile.h',
                               'src/core/lib/gpr/useful.h',
                               'src/core/lib/gprpp/abstract.h',
@@ -648,6 +656,10 @@
                       'src/core/lib/gpr/string_windows.h',
                       'src/core/lib/gpr/thd_internal.h',
                       'src/core/lib/gpr/time_precise.h',
+                      'src/core/lib/gpr/tls.h',
+                      'src/core/lib/gpr/tls_gcc.h',
+                      'src/core/lib/gpr/tls_msvc.h',
+                      'src/core/lib/gpr/tls_pthread.h',
                       'src/core/lib/gpr/tmpfile.h',
                       'src/core/lib/gpr/useful.h',
                       'src/core/lib/gprpp/abstract.h',
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index 95f5012..1798a0c 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -126,10 +126,6 @@
                       'include/grpc/support/sync_windows.h',
                       'include/grpc/support/thd.h',
                       'include/grpc/support/time.h',
-                      'include/grpc/support/tls.h',
-                      'include/grpc/support/tls_gcc.h',
-                      'include/grpc/support/tls_msvc.h',
-                      'include/grpc/support/tls_pthread.h',
                       'include/grpc/impl/codegen/atm.h',
                       'include/grpc/impl/codegen/atm_gcc_atomic.h',
                       'include/grpc/impl/codegen/atm_gcc_sync.h',
@@ -198,6 +194,10 @@
                       'src/core/lib/gpr/string_windows.h',
                       'src/core/lib/gpr/thd_internal.h',
                       'src/core/lib/gpr/time_precise.h',
+                      'src/core/lib/gpr/tls.h',
+                      'src/core/lib/gpr/tls_gcc.h',
+                      'src/core/lib/gpr/tls_msvc.h',
+                      'src/core/lib/gpr/tls_pthread.h',
                       'src/core/lib/gpr/tmpfile.h',
                       'src/core/lib/gpr/useful.h',
                       'src/core/lib/gprpp/abstract.h',
@@ -674,7 +674,6 @@
                       'src/core/ext/filters/client_channel/proxy_mapper.cc',
                       'src/core/ext/filters/client_channel/proxy_mapper_registry.cc',
                       'src/core/ext/filters/client_channel/resolver.cc',
-                      'src/core/ext/filters/client_channel/resolver_factory.cc',
                       'src/core/ext/filters/client_channel/resolver_registry.cc',
                       'src/core/ext/filters/client_channel/retry_throttle.cc',
                       'src/core/ext/filters/client_channel/subchannel.cc',
@@ -724,6 +723,10 @@
                               'src/core/lib/gpr/string_windows.h',
                               'src/core/lib/gpr/thd_internal.h',
                               'src/core/lib/gpr/time_precise.h',
+                              'src/core/lib/gpr/tls.h',
+                              'src/core/lib/gpr/tls_gcc.h',
+                              'src/core/lib/gpr/tls_msvc.h',
+                              'src/core/lib/gpr/tls_pthread.h',
                               'src/core/lib/gpr/tmpfile.h',
                               'src/core/lib/gpr/useful.h',
                               'src/core/lib/gprpp/abstract.h',
diff --git a/grpc.gemspec b/grpc.gemspec
index 705ef8d..6e51703 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -61,10 +61,6 @@
   s.files += %w( include/grpc/support/sync_windows.h )
   s.files += %w( include/grpc/support/thd.h )
   s.files += %w( include/grpc/support/time.h )
-  s.files += %w( include/grpc/support/tls.h )
-  s.files += %w( include/grpc/support/tls_gcc.h )
-  s.files += %w( include/grpc/support/tls_msvc.h )
-  s.files += %w( include/grpc/support/tls_pthread.h )
   s.files += %w( include/grpc/impl/codegen/atm.h )
   s.files += %w( include/grpc/impl/codegen/atm_gcc_atomic.h )
   s.files += %w( include/grpc/impl/codegen/atm_gcc_sync.h )
@@ -89,6 +85,10 @@
   s.files += %w( src/core/lib/gpr/string_windows.h )
   s.files += %w( src/core/lib/gpr/thd_internal.h )
   s.files += %w( src/core/lib/gpr/time_precise.h )
+  s.files += %w( src/core/lib/gpr/tls.h )
+  s.files += %w( src/core/lib/gpr/tls_gcc.h )
+  s.files += %w( src/core/lib/gpr/tls_msvc.h )
+  s.files += %w( src/core/lib/gpr/tls_pthread.h )
   s.files += %w( src/core/lib/gpr/tmpfile.h )
   s.files += %w( src/core/lib/gpr/useful.h )
   s.files += %w( src/core/lib/gprpp/abstract.h )
@@ -604,7 +604,6 @@
   s.files += %w( src/core/ext/filters/client_channel/proxy_mapper.cc )
   s.files += %w( src/core/ext/filters/client_channel/proxy_mapper_registry.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver.cc )
-  s.files += %w( src/core/ext/filters/client_channel/resolver_factory.cc )
   s.files += %w( src/core/ext/filters/client_channel/resolver_registry.cc )
   s.files += %w( src/core/ext/filters/client_channel/retry_throttle.cc )
   s.files += %w( src/core/ext/filters/client_channel/subchannel.cc )
diff --git a/grpc.gyp b/grpc.gyp
index ea0e6fe..44bc7e8 100644
--- a/grpc.gyp
+++ b/grpc.gyp
@@ -431,7 +431,6 @@
         'src/core/ext/filters/client_channel/proxy_mapper.cc',
         'src/core/ext/filters/client_channel/proxy_mapper_registry.cc',
         'src/core/ext/filters/client_channel/resolver.cc',
-        'src/core/ext/filters/client_channel/resolver_factory.cc',
         'src/core/ext/filters/client_channel/resolver_registry.cc',
         'src/core/ext/filters/client_channel/retry_throttle.cc',
         'src/core/ext/filters/client_channel/subchannel.cc',
@@ -669,7 +668,6 @@
         'src/core/ext/filters/client_channel/proxy_mapper.cc',
         'src/core/ext/filters/client_channel/proxy_mapper_registry.cc',
         'src/core/ext/filters/client_channel/resolver.cc',
-        'src/core/ext/filters/client_channel/resolver_factory.cc',
         'src/core/ext/filters/client_channel/resolver_registry.cc',
         'src/core/ext/filters/client_channel/retry_throttle.cc',
         'src/core/ext/filters/client_channel/subchannel.cc',
@@ -885,7 +883,6 @@
         'src/core/ext/filters/client_channel/proxy_mapper.cc',
         'src/core/ext/filters/client_channel/proxy_mapper_registry.cc',
         'src/core/ext/filters/client_channel/resolver.cc',
-        'src/core/ext/filters/client_channel/resolver_factory.cc',
         'src/core/ext/filters/client_channel/resolver_registry.cc',
         'src/core/ext/filters/client_channel/retry_throttle.cc',
         'src/core/ext/filters/client_channel/subchannel.cc',
@@ -1113,7 +1110,6 @@
         'src/core/ext/filters/client_channel/proxy_mapper.cc',
         'src/core/ext/filters/client_channel/proxy_mapper_registry.cc',
         'src/core/ext/filters/client_channel/resolver.cc',
-        'src/core/ext/filters/client_channel/resolver_factory.cc',
         'src/core/ext/filters/client_channel/resolver_registry.cc',
         'src/core/ext/filters/client_channel/retry_throttle.cc',
         'src/core/ext/filters/client_channel/subchannel.cc',
diff --git a/include/grpc/module.modulemap b/include/grpc/module.modulemap
index 798e066..e5dac82 100644
--- a/include/grpc/module.modulemap
+++ b/include/grpc/module.modulemap
@@ -13,7 +13,6 @@
   header "support/sync_generic.h"
   header "support/thd.h"
   header "support/time.h"
-  header "support/tls.h"
   header "impl/codegen/atm.h"
   header "impl/codegen/fork.h"
   header "impl/codegen/gpr_slice.h"
@@ -57,9 +56,6 @@
   textual header "support/sync_custom.h"
   textual header "support/sync_posix.h"
   textual header "support/sync_windows.h"
-  textual header "support/tls_gcc.h"
-  textual header "support/tls_msvc.h"
-  textual header "support/tls_pthread.h"
   textual header "impl/codegen/atm_gcc_atomic.h"
   textual header "impl/codegen/atm_gcc_sync.h"
   textual header "impl/codegen/atm_windows.h"
diff --git a/package.xml b/package.xml
index 0cc9243..5b598c1 100644
--- a/package.xml
+++ b/package.xml
@@ -68,10 +68,6 @@
     <file baseinstalldir="/" name="include/grpc/support/sync_windows.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/support/thd.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/support/time.h" role="src" />
-    <file baseinstalldir="/" name="include/grpc/support/tls.h" role="src" />
-    <file baseinstalldir="/" name="include/grpc/support/tls_gcc.h" role="src" />
-    <file baseinstalldir="/" name="include/grpc/support/tls_msvc.h" role="src" />
-    <file baseinstalldir="/" name="include/grpc/support/tls_pthread.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/atm.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/atm_gcc_atomic.h" role="src" />
     <file baseinstalldir="/" name="include/grpc/impl/codegen/atm_gcc_sync.h" role="src" />
@@ -96,6 +92,10 @@
     <file baseinstalldir="/" name="src/core/lib/gpr/string_windows.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/thd_internal.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/time_precise.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gpr/tls.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gpr/tls_gcc.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gpr/tls_msvc.h" role="src" />
+    <file baseinstalldir="/" name="src/core/lib/gpr/tls_pthread.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/tmpfile.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gpr/useful.h" role="src" />
     <file baseinstalldir="/" name="src/core/lib/gprpp/abstract.h" role="src" />
@@ -611,7 +611,6 @@
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/proxy_mapper.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/proxy_mapper_registry.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver.cc" role="src" />
-    <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver_factory.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/resolver_registry.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/retry_throttle.cc" role="src" />
     <file baseinstalldir="/" name="src/core/ext/filters/client_channel/subchannel.cc" role="src" />
diff --git a/src/core/ext/filters/client_channel/client_channel.cc b/src/core/ext/filters/client_channel/client_channel.cc
index e16f9e4..cbee74c 100644
--- a/src/core/ext/filters/client_channel/client_channel.cc
+++ b/src/core/ext/filters/client_channel/client_channel.cc
@@ -164,7 +164,7 @@
 
 typedef struct client_channel_channel_data {
   /** resolver for this channel */
-  grpc_resolver* resolver;
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver;
   /** have we started resolving this channel */
   bool started_resolving;
   /** is deadline checking enabled? */
@@ -299,8 +299,8 @@
   GPR_ASSERT(!chand->started_resolving);
   chand->started_resolving = true;
   GRPC_CHANNEL_STACK_REF(chand->owning_stack, "resolver");
-  grpc_resolver_next_locked(chand->resolver, &chand->resolver_result,
-                            &chand->on_resolver_result_changed);
+  chand->resolver->NextLocked(&chand->resolver_result,
+                              &chand->on_resolver_result_changed);
 }
 
 typedef struct {
@@ -377,7 +377,7 @@
   if (grpc_client_channel_trace.enabled()) {
     gpr_log(GPR_DEBUG, "chand=%p: started name re-resolving", chand);
   }
-  grpc_resolver_channel_saw_error_locked(chand->resolver);
+  chand->resolver->RequestReresolutionLocked();
   // Give back the closure to the LB policy.
   grpc_lb_policy_set_reresolve_closure_locked(chand->lb_policy, &args->closure);
 }
@@ -567,9 +567,7 @@
       if (grpc_client_channel_trace.enabled()) {
         gpr_log(GPR_DEBUG, "chand=%p: shutting down resolver", chand);
       }
-      grpc_resolver_shutdown_locked(chand->resolver);
-      GRPC_RESOLVER_UNREF(chand->resolver, "channel");
-      chand->resolver = nullptr;
+      chand->resolver.reset();
     }
     set_channel_connectivity_state_locked(
         chand, GRPC_CHANNEL_SHUTDOWN,
@@ -605,8 +603,8 @@
       set_channel_connectivity_state_locked(
           chand, state, GRPC_ERROR_REF(state_error), "new_lb+resolver");
     }
-    grpc_resolver_next_locked(chand->resolver, &chand->resolver_result,
-                              &chand->on_resolver_result_changed);
+    chand->resolver->NextLocked(&chand->resolver_result,
+                                &chand->on_resolver_result_changed);
     GRPC_ERROR_UNREF(state_error);
   }
 }
@@ -647,9 +645,7 @@
       set_channel_connectivity_state_locked(
           chand, GRPC_CHANNEL_SHUTDOWN,
           GRPC_ERROR_REF(op->disconnect_with_error), "disconnect");
-      grpc_resolver_shutdown_locked(chand->resolver);
-      GRPC_RESOLVER_UNREF(chand->resolver, "channel");
-      chand->resolver = nullptr;
+      chand->resolver.reset();
       if (!chand->started_resolving) {
         grpc_closure_list_fail_all(&chand->waiting_for_resolver_result_closures,
                                    GRPC_ERROR_REF(op->disconnect_with_error));
@@ -758,7 +754,7 @@
   grpc_proxy_mappers_map_name(arg->value.string, args->channel_args,
                               &proxy_name, &new_args);
   // Instantiate resolver.
-  chand->resolver = grpc_resolver_create(
+  chand->resolver = grpc_core::ResolverRegistry::CreateResolver(
       proxy_name != nullptr ? proxy_name : arg->value.string,
       new_args != nullptr ? new_args : args->channel_args,
       chand->interested_parties, chand->combiner);
@@ -773,9 +769,8 @@
 }
 
 static void shutdown_resolver_locked(void* arg, grpc_error* error) {
-  grpc_resolver* resolver = (grpc_resolver*)arg;
-  grpc_resolver_shutdown_locked(resolver);
-  GRPC_RESOLVER_UNREF(resolver, "channel");
+  grpc_core::Resolver* resolver = static_cast<grpc_core::Resolver*>(arg);
+  resolver->Orphan();
 }
 
 /* Destructor for channel_data */
@@ -783,7 +778,7 @@
   channel_data* chand = (channel_data*)elem->channel_data;
   if (chand->resolver != nullptr) {
     GRPC_CLOSURE_SCHED(
-        GRPC_CLOSURE_CREATE(shutdown_resolver_locked, chand->resolver,
+        GRPC_CLOSURE_CREATE(shutdown_resolver_locked, chand->resolver.release(),
                             grpc_combiner_scheduler(chand->combiner)),
         GRPC_ERROR_NONE);
   }
diff --git a/src/core/ext/filters/client_channel/client_channel_plugin.cc b/src/core/ext/filters/client_channel/client_channel_plugin.cc
index ea630d2..d756d9c 100644
--- a/src/core/ext/filters/client_channel/client_channel_plugin.cc
+++ b/src/core/ext/filters/client_channel/client_channel_plugin.cc
@@ -49,14 +49,14 @@
       return true;
     }
   }
-  char* default_authority = grpc_get_default_authority(
-      grpc_channel_stack_builder_get_target(builder));
-  if (default_authority != nullptr) {
+  grpc_core::UniquePtr<char> default_authority =
+      grpc_core::ResolverRegistry::GetDefaultAuthority(
+          grpc_channel_stack_builder_get_target(builder));
+  if (default_authority.get() != nullptr) {
     grpc_arg arg = grpc_channel_arg_string_create(
-        (char*)GRPC_ARG_DEFAULT_AUTHORITY, default_authority);
+        (char*)GRPC_ARG_DEFAULT_AUTHORITY, default_authority.get());
     grpc_channel_args* new_args = grpc_channel_args_copy_and_add(args, &arg, 1);
     grpc_channel_stack_builder_set_channel_arguments(builder, new_args);
-    gpr_free(default_authority);
     grpc_channel_args_destroy(new_args);
   }
   return true;
@@ -64,7 +64,7 @@
 
 void grpc_client_channel_init(void) {
   grpc_lb_policy_registry_init();
-  grpc_resolver_registry_init();
+  grpc_core::ResolverRegistry::Builder::InitRegistry();
   grpc_retry_throttle_map_init();
   grpc_proxy_mapper_registry_init();
   grpc_register_http_proxy_mapper();
@@ -82,6 +82,6 @@
   grpc_channel_init_shutdown();
   grpc_proxy_mapper_registry_shutdown();
   grpc_retry_throttle_map_shutdown();
-  grpc_resolver_registry_shutdown();
+  grpc_core::ResolverRegistry::Builder::ShutdownRegistry();
   grpc_lb_policy_registry_shutdown();
 }
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc
index 5e24bdd..ac47990 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc
@@ -108,6 +108,7 @@
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/iomgr/sockaddr.h"
 #include "src/core/lib/iomgr/sockaddr_utils.h"
@@ -242,7 +243,8 @@
   glb_lb_call_data* lb_calld;
 
   /** response generator to inject address updates into \a lb_channel */
-  grpc_fake_resolver_response_generator* response_generator;
+  grpc_core::RefCountedPtr<grpc_core::FakeResolverResponseGenerator>
+      response_generator;
 
   /** the RR policy to use of the backend servers returned by the LB server */
   grpc_lb_policy* rr_policy;
@@ -872,7 +874,7 @@
  *   - \a args: other args inherited from the grpclb policy. */
 static grpc_channel_args* build_lb_channel_args(
     const grpc_lb_addresses* addresses,
-    grpc_fake_resolver_response_generator* response_generator,
+    grpc_core::FakeResolverResponseGenerator* response_generator,
     const grpc_channel_args* args) {
   size_t num_grpclb_addrs = 0;
   for (size_t i = 0; i < addresses->num_addresses; ++i) {
@@ -941,7 +943,8 @@
   if (glb_policy->fallback_backend_addresses != nullptr) {
     grpc_lb_addresses_destroy(glb_policy->fallback_backend_addresses);
   }
-  grpc_fake_resolver_response_generator_unref(glb_policy->response_generator);
+  // TODO(roth): Remove this once the LB policy becomes a C++ object.
+  glb_policy->response_generator.reset();
   grpc_subchannel_index_unref();
   gpr_free(glb_policy);
 }
@@ -1701,9 +1704,8 @@
   // Propagate updates to the LB channel (pick_first) through the fake
   // resolver.
   grpc_channel_args* lb_channel_args = build_lb_channel_args(
-      addresses, glb_policy->response_generator, args->args);
-  grpc_fake_resolver_response_generator_set_response(
-      glb_policy->response_generator, lb_channel_args);
+      addresses, glb_policy->response_generator.get(), args->args);
+  glb_policy->response_generator->SetResponse(lb_channel_args);
   grpc_channel_args_destroy(lb_channel_args);
   // Start watching the LB channel connectivity for connection, if not
   // already doing so.
@@ -1858,17 +1860,16 @@
 
   /* Create a client channel over them to communicate with a LB service */
   glb_policy->response_generator =
-      grpc_fake_resolver_response_generator_create();
+      grpc_core::MakeRefCounted<grpc_core::FakeResolverResponseGenerator>();
   grpc_channel_args* lb_channel_args = build_lb_channel_args(
-      addresses, glb_policy->response_generator, args->args);
+      addresses, glb_policy->response_generator.get(), args->args);
   char* uri_str;
   gpr_asprintf(&uri_str, "fake:///%s", glb_policy->server_name);
   glb_policy->lb_channel = grpc_lb_policy_grpclb_create_lb_channel(
       uri_str, args->client_channel_factory, lb_channel_args);
 
   /* Propagate initial resolution */
-  grpc_fake_resolver_response_generator_set_response(
-      glb_policy->response_generator, lb_channel_args);
+  glb_policy->response_generator->SetResponse(lb_channel_args);
   grpc_channel_args_destroy(lb_channel_args);
   gpr_free(uri_str);
   if (glb_policy->lb_channel == nullptr) {
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.cc
index 1e7f34b..013fb12 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.cc
@@ -37,10 +37,11 @@
 
 grpc_channel_args* grpc_lb_policy_grpclb_build_lb_channel_args(
     grpc_slice_hash_table* targets_info,
-    grpc_fake_resolver_response_generator* response_generator,
+    grpc_core::FakeResolverResponseGenerator* response_generator,
     const grpc_channel_args* args) {
   const grpc_arg to_add[] = {
-      grpc_fake_resolver_response_generator_arg(response_generator)};
+      grpc_core::FakeResolverResponseGenerator::MakeChannelArg(
+          response_generator)};
   /* We remove:
    *
    * - The channel arg for the LB policy name, since we want to use the default
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h
index 56104b2..2e34e3c 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h
@@ -37,7 +37,7 @@
 
 grpc_channel_args* grpc_lb_policy_grpclb_build_lb_channel_args(
     grpc_slice_hash_table* targets_info,
-    grpc_fake_resolver_response_generator* response_generator,
+    grpc_core::FakeResolverResponseGenerator* response_generator,
     const grpc_channel_args* args);
 
 #endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_LB_POLICY_GRPCLB_GRPCLB_CHANNEL_H \
diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel_secure.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel_secure.cc
index 15233d3..5e615ad 100644
--- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel_secure.cc
+++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel_secure.cc
@@ -63,11 +63,12 @@
 
 grpc_channel_args* grpc_lb_policy_grpclb_build_lb_channel_args(
     grpc_slice_hash_table* targets_info,
-    grpc_fake_resolver_response_generator* response_generator,
+    grpc_core::FakeResolverResponseGenerator* response_generator,
     const grpc_channel_args* args) {
   const grpc_arg to_add[] = {
       grpc_lb_targets_info_create_channel_arg(targets_info),
-      grpc_fake_resolver_response_generator_arg(response_generator)};
+      grpc_core::FakeResolverResponseGenerator::MakeChannelArg(
+          response_generator)};
   /* We remove:
    *
    * - The channel arg for the LB policy name, since we want to use the default
diff --git a/src/core/ext/filters/client_channel/resolver.cc b/src/core/ext/filters/client_channel/resolver.cc
index ff54e71..860c2ee 100644
--- a/src/core/ext/filters/client_channel/resolver.cc
+++ b/src/core/ext/filters/client_channel/resolver.cc
@@ -22,58 +22,12 @@
 grpc_core::DebugOnlyTraceFlag grpc_trace_resolver_refcount(false,
                                                            "resolver_refcount");
 
-void grpc_resolver_init(grpc_resolver* resolver,
-                        const grpc_resolver_vtable* vtable,
-                        grpc_combiner* combiner) {
-  resolver->vtable = vtable;
-  resolver->combiner = GRPC_COMBINER_REF(combiner, "resolver");
-  gpr_ref_init(&resolver->refs, 1);
-}
+namespace grpc_core {
 
-#ifndef NDEBUG
-void grpc_resolver_ref(grpc_resolver* resolver, const char* file, int line,
-                       const char* reason) {
-  if (grpc_trace_resolver_refcount.enabled()) {
-    gpr_atm old_refs = gpr_atm_no_barrier_load(&resolver->refs.count);
-    gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
-            "RESOLVER:%p   ref %" PRIdPTR " -> %" PRIdPTR " %s", resolver,
-            old_refs, old_refs + 1, reason);
-  }
-#else
-void grpc_resolver_ref(grpc_resolver* resolver) {
-#endif
-  gpr_ref(&resolver->refs);
-}
+Resolver::Resolver(grpc_combiner* combiner)
+    : InternallyRefCountedWithTracing(&grpc_trace_resolver_refcount),
+      combiner_(GRPC_COMBINER_REF(combiner, "resolver")) {}
 
-#ifndef NDEBUG
-void grpc_resolver_unref(grpc_resolver* resolver, const char* file, int line,
-                         const char* reason) {
-  if (grpc_trace_resolver_refcount.enabled()) {
-    gpr_atm old_refs = gpr_atm_no_barrier_load(&resolver->refs.count);
-    gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
-            "RESOLVER:%p unref %" PRIdPTR " -> %" PRIdPTR " %s", resolver,
-            old_refs, old_refs - 1, reason);
-  }
-#else
-void grpc_resolver_unref(grpc_resolver* resolver) {
-#endif
-  if (gpr_unref(&resolver->refs)) {
-    grpc_combiner* combiner = resolver->combiner;
-    resolver->vtable->destroy(resolver);
-    GRPC_COMBINER_UNREF(combiner, "resolver");
-  }
-}
+Resolver::~Resolver() { GRPC_COMBINER_UNREF(combiner_, "resolver"); }
 
-void grpc_resolver_shutdown_locked(grpc_resolver* resolver) {
-  resolver->vtable->shutdown_locked(resolver);
-}
-
-void grpc_resolver_channel_saw_error_locked(grpc_resolver* resolver) {
-  resolver->vtable->channel_saw_error_locked(resolver);
-}
-
-void grpc_resolver_next_locked(grpc_resolver* resolver,
-                               grpc_channel_args** result,
-                               grpc_closure* on_complete) {
-  resolver->vtable->next_locked(resolver, result, on_complete);
-}
+}  // namespace grpc_core
diff --git a/src/core/ext/filters/client_channel/resolver.h b/src/core/ext/filters/client_channel/resolver.h
index f6a4af0..62fcb49 100644
--- a/src/core/ext/filters/client_channel/resolver.h
+++ b/src/core/ext/filters/client_channel/resolver.h
@@ -19,67 +19,110 @@
 #ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_H
 #define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_H
 
-#include "src/core/ext/filters/client_channel/subchannel.h"
-#include "src/core/lib/iomgr/iomgr.h"
+#include <grpc/impl/codegen/grpc_types.h>
 
-typedef struct grpc_resolver grpc_resolver;
-typedef struct grpc_resolver_vtable grpc_resolver_vtable;
+#include "src/core/lib/gprpp/abstract.h"
+#include "src/core/lib/gprpp/orphanable.h"
+#include "src/core/lib/iomgr/combiner.h"
+#include "src/core/lib/iomgr/iomgr.h"
 
 extern grpc_core::DebugOnlyTraceFlag grpc_trace_resolver_refcount;
 
-/** \a grpc_resolver provides \a grpc_channel_args objects to its caller */
-struct grpc_resolver {
-  const grpc_resolver_vtable* vtable;
-  gpr_refcount refs;
-  grpc_combiner* combiner;
+namespace grpc_core {
+
+/// Interface for name resolution.
+///
+/// This interface is designed to support both push-based and pull-based
+/// mechanisms.  A push-based mechanism is one where the resolver will
+/// subscribe to updates for a given name, and the name service will
+/// proactively send new data to the resolver whenever the data associated
+/// with the name changes.  A pull-based mechanism is one where the resolver
+/// needs to query the name service again to get updated information (e.g.,
+/// DNS).
+///
+/// Note: All methods with a "Locked" suffix must be called from the
+/// combiner passed to the constructor.
+class Resolver : public InternallyRefCountedWithTracing<Resolver> {
+ public:
+  // Not copyable nor movable.
+  Resolver(const Resolver&) = delete;
+  Resolver& operator=(const Resolver&) = delete;
+
+  /// Requests a callback when a new result becomes available.
+  /// When the new result is available, sets \a *result to the new result
+  /// and schedules \a on_complete for execution.
+  /// If resolution is fatally broken, sets \a *result to nullptr and
+  /// schedules \a on_complete with an error.
+  ///
+  /// Note that the client channel will almost always have a request
+  /// to \a NextLocked() pending.  When it gets the callback, it will
+  /// process the new result and then immediately make another call to
+  /// \a NextLocked().  This allows push-based resolvers to provide new
+  /// data as soon as it becomes available.
+  virtual void NextLocked(grpc_channel_args** result,
+                          grpc_closure* on_complete) GRPC_ABSTRACT;
+
+  /// Asks the resolver to obtain an updated resolver result, if
+  /// applicable.
+  ///
+  /// This is useful for pull-based implementations to decide when to
+  /// re-resolve.  However, the implementation is not required to
+  /// re-resolve immediately upon receiving this call; it may instead
+  /// elect to delay based on some configured minimum time between
+  /// queries, to avoid hammering the name service with queries.
+  ///
+  /// For push-based implementations, this may be a no-op.
+  ///
+  /// If this causes new data to become available, then the currently
+  /// pending call to \a NextLocked() will return the new result.
+  ///
+  /// Note: Currently, all resolvers are required to return a new result
+  /// shortly after this method is called.  For pull-based mechanisms, if
+  /// the implementation decides to delay querying the name service, it
+  /// should immediately return a new copy of the previously returned
+  /// result (and it can then return the updated data later, when it
+  /// actually does query the name service).  For push-based mechanisms,
+  /// the implementation should immediately return a new copy of the
+  /// last-seen result.
+  /// TODO(roth): Remove this requirement once we fix pick_first to not
+  /// throw away unselected subchannels.
+  virtual void RequestReresolutionLocked() GRPC_ABSTRACT;
+
+  void Orphan() override {
+    // Invoke ShutdownAndUnrefLocked() inside of the combiner.
+    GRPC_CLOSURE_SCHED(
+        GRPC_CLOSURE_CREATE(&Resolver::ShutdownAndUnrefLocked, this,
+                            grpc_combiner_scheduler(combiner_)),
+        GRPC_ERROR_NONE);
+  }
+
+  GRPC_ABSTRACT_BASE_CLASS
+
+ protected:
+  /// Does NOT take ownership of the reference to \a combiner.
+  // TODO(roth): Once we have a C++-like interface for combiners, this
+  // API should change to take a RefCountedPtr<>, so that we always take
+  // ownership of a new ref.
+  explicit Resolver(grpc_combiner* combiner);
+
+  virtual ~Resolver();
+
+  /// Shuts down the resolver.  If there is a pending call to
+  /// NextLocked(), the callback will be scheduled with an error.
+  virtual void ShutdownLocked() GRPC_ABSTRACT;
+
+  grpc_combiner* combiner() const { return combiner_; }
+
+ private:
+  static void ShutdownAndUnrefLocked(void* arg, grpc_error* ignored) {
+    Resolver* resolver = static_cast<Resolver*>(arg);
+    resolver->ShutdownLocked();
+    resolver->Unref();
+  }
+
+  grpc_combiner* combiner_;
 };
 
-struct grpc_resolver_vtable {
-  void (*destroy)(grpc_resolver* resolver);
-  void (*shutdown_locked)(grpc_resolver* resolver);
-  void (*channel_saw_error_locked)(grpc_resolver* resolver);
-  void (*next_locked)(grpc_resolver* resolver, grpc_channel_args** result,
-                      grpc_closure* on_complete);
-};
-
-#ifndef NDEBUG
-#define GRPC_RESOLVER_REF(p, r) grpc_resolver_ref((p), __FILE__, __LINE__, (r))
-#define GRPC_RESOLVER_UNREF(p, r) \
-  grpc_resolver_unref((p), __FILE__, __LINE__, (r))
-void grpc_resolver_ref(grpc_resolver* policy, const char* file, int line,
-                       const char* reason);
-void grpc_resolver_unref(grpc_resolver* policy, const char* file, int line,
-                         const char* reason);
-#else
-#define GRPC_RESOLVER_REF(p, r) grpc_resolver_ref((p))
-#define GRPC_RESOLVER_UNREF(p, r) grpc_resolver_unref((p))
-void grpc_resolver_ref(grpc_resolver* policy);
-void grpc_resolver_unref(grpc_resolver* policy);
-#endif
-
-void grpc_resolver_init(grpc_resolver* resolver,
-                        const grpc_resolver_vtable* vtable,
-                        grpc_combiner* combiner);
-
-void grpc_resolver_shutdown_locked(grpc_resolver* resolver);
-
-/** Notification that the channel has seen an error on some address.
-    Can be used as a hint that re-resolution is desirable soon.
-
-    Must be called from the combiner passed as a resolver_arg at construction
-    time.*/
-void grpc_resolver_channel_saw_error_locked(grpc_resolver* resolver);
-
-/** Get the next result from the resolver.  Expected to set \a *result with
-    new channel args and then schedule \a on_complete for execution.
-
-    If resolution is fatally broken, set \a *result to NULL and
-    schedule \a on_complete.
-
-    Must be called from the combiner passed as a resolver_arg at construction
-    time.*/
-void grpc_resolver_next_locked(grpc_resolver* resolver,
-                               grpc_channel_args** result,
-                               grpc_closure* on_complete);
+}  // namespace grpc_core
 
 #endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_H */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc b/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
index f2f25bc..6e03ae4 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
@@ -49,109 +49,168 @@
 #define GRPC_DNS_RECONNECT_MAX_BACKOFF_SECONDS 120
 #define GRPC_DNS_RECONNECT_JITTER 0.2
 
-typedef struct {
-  /** base class: must be first */
-  grpc_resolver base;
-  /** DNS server to use (if not system default) */
-  char* dns_server;
-  /** name to resolve (usually the same as target_name) */
-  char* name_to_resolve;
-  /** default port to use */
-  char* default_port;
-  /** channel args. */
-  grpc_channel_args* channel_args;
-  /** whether to request the service config */
-  bool request_service_config;
-  /** pollset_set to drive the name resolution process */
-  grpc_pollset_set* interested_parties;
+namespace grpc_core {
 
-  /** Closures used by the combiner */
-  grpc_closure dns_ares_on_next_resolution_timer_closure;
-  grpc_closure dns_ares_on_resolved_closure;
+namespace {
 
-  /** Combiner guarding the rest of the state */
-  grpc_combiner* combiner;
-  /** are we currently resolving? */
-  bool resolving;
-  /** the pending resolving request */
-  grpc_ares_request* pending_request;
-  /** which version of the result have we published? */
-  int published_version;
-  /** which version of the result is current? */
-  int resolved_version;
-  /** pending next completion, or NULL */
-  grpc_closure* next_completion;
-  /** target result address for next completion */
-  grpc_channel_args** target_result;
-  /** current (fully resolved) result */
-  grpc_channel_args* resolved_result;
-  /** next resolution timer */
-  bool have_next_resolution_timer;
-  grpc_timer next_resolution_timer;
-  /** retry backoff state */
-  grpc_core::ManualConstructor<grpc_core::BackOff> backoff;
-  /** min resolution period. Max one resolution will happen per period */
-  grpc_millis min_time_between_resolutions;
-  /** when was the last resolution? -1 if no resolution has happened yet */
-  grpc_millis last_resolution_timestamp;
-  /** currently resolving addresses */
-  grpc_lb_addresses* lb_addresses;
-  /** currently resolving service config */
-  char* service_config_json;
-} ares_dns_resolver;
+const char kDefaultPort[] = "https";
 
-static void dns_ares_destroy(grpc_resolver* r);
+class AresDnsResolver : public Resolver {
+ public:
+  explicit AresDnsResolver(const ResolverArgs& args);
 
-static void dns_ares_start_resolving_locked(ares_dns_resolver* r);
-static void dns_ares_maybe_start_resolving_locked(ares_dns_resolver* r);
-static void dns_ares_maybe_finish_next_locked(ares_dns_resolver* r);
+  void NextLocked(grpc_channel_args** result,
+                  grpc_closure* on_complete) override;
 
-static void dns_ares_shutdown_locked(grpc_resolver* r);
-static void dns_ares_channel_saw_error_locked(grpc_resolver* r);
-static void dns_ares_next_locked(grpc_resolver* r,
-                                 grpc_channel_args** target_result,
-                                 grpc_closure* on_complete);
+  void RequestReresolutionLocked() override;
 
-static const grpc_resolver_vtable dns_ares_resolver_vtable = {
-    dns_ares_destroy, dns_ares_shutdown_locked,
-    dns_ares_channel_saw_error_locked, dns_ares_next_locked};
+  void ShutdownLocked() override;
 
-static void dns_ares_shutdown_locked(grpc_resolver* resolver) {
-  ares_dns_resolver* r = (ares_dns_resolver*)resolver;
-  if (r->have_next_resolution_timer) {
-    grpc_timer_cancel(&r->next_resolution_timer);
+ private:
+  virtual ~AresDnsResolver();
+
+  void MaybeStartResolvingLocked();
+  void StartResolvingLocked();
+  void MaybeFinishNextLocked();
+
+  static void OnNextResolutionLocked(void* arg, grpc_error* error);
+  static void OnResolvedLocked(void* arg, grpc_error* error);
+
+  /// DNS server to use (if not system default)
+  char* dns_server_;
+  /// name to resolve (usually the same as target_name)
+  char* name_to_resolve_;
+  /// channel args
+  grpc_channel_args* channel_args_;
+  /// whether to request the service config
+  bool request_service_config_;
+  /// pollset_set to drive the name resolution process
+  grpc_pollset_set* interested_parties_;
+  /// closures used by the combiner
+  grpc_closure on_next_resolution_;
+  grpc_closure on_resolved_;
+  /// are we currently resolving?
+  bool resolving_ = false;
+  /// the pending resolving request
+  grpc_ares_request* pending_request_ = nullptr;
+  /// which version of the result have we published?
+  int published_version_ = 0;
+  /// which version of the result is current?
+  int resolved_version_ = 0;
+  /// pending next completion, or NULL
+  grpc_closure* next_completion_ = nullptr;
+  /// target result address for next completion
+  grpc_channel_args** target_result_ = nullptr;
+  /// current (fully resolved) result
+  grpc_channel_args* resolved_result_ = nullptr;
+  /// next resolution timer
+  bool have_next_resolution_timer_ = false;
+  grpc_timer next_resolution_timer_;
+  /// min interval between DNS requests
+  grpc_millis min_time_between_resolutions_;
+  /// timestamp of last DNS request
+  grpc_millis last_resolution_timestamp_ = -1;
+  /// retry backoff state
+  BackOff backoff_;
+  /// currently resolving addresses
+  grpc_lb_addresses* lb_addresses_ = nullptr;
+  /// currently resolving service config
+  char* service_config_json_ = nullptr;
+};
+
+AresDnsResolver::AresDnsResolver(const ResolverArgs& args)
+    : Resolver(args.combiner),
+      backoff_(
+          BackOff::Options()
+              .set_initial_backoff(GRPC_DNS_INITIAL_CONNECT_BACKOFF_SECONDS *
+                                   1000)
+              .set_multiplier(GRPC_DNS_RECONNECT_BACKOFF_MULTIPLIER)
+              .set_jitter(GRPC_DNS_RECONNECT_JITTER)
+              .set_max_backoff(GRPC_DNS_RECONNECT_MAX_BACKOFF_SECONDS * 1000)) {
+  // Get name to resolve from URI path.
+  const char* path = args.uri->path;
+  if (path[0] == '/') ++path;
+  name_to_resolve_ = gpr_strdup(path);
+  // Get DNS server from URI authority.
+  if (0 != strcmp(args.uri->authority, "")) {
+    dns_server_ = gpr_strdup(args.uri->authority);
   }
-  if (r->pending_request != nullptr) {
-    grpc_cancel_ares_request(r->pending_request);
+  channel_args_ = grpc_channel_args_copy(args.args);
+  const grpc_arg* arg = grpc_channel_args_find(
+      channel_args_, GRPC_ARG_SERVICE_CONFIG_DISABLE_RESOLUTION);
+  request_service_config_ = !grpc_channel_arg_get_integer(
+      arg, (grpc_integer_options){false, false, true});
+  arg = grpc_channel_args_find(channel_args_,
+                               GRPC_ARG_DNS_MIN_TIME_BETWEEN_RESOLUTIONS_MS);
+  min_time_between_resolutions_ =
+      grpc_channel_arg_get_integer(arg, {1000, 0, INT_MAX});
+  interested_parties_ = grpc_pollset_set_create();
+  if (args.pollset_set != nullptr) {
+    grpc_pollset_set_add_pollset_set(interested_parties_, args.pollset_set);
   }
-  if (r->next_completion != nullptr) {
-    *r->target_result = nullptr;
-    GRPC_CLOSURE_SCHED(r->next_completion, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
-                                               "Resolver Shutdown"));
-    r->next_completion = nullptr;
+  GRPC_CLOSURE_INIT(&on_next_resolution_, OnNextResolutionLocked, this,
+                    grpc_combiner_scheduler(combiner()));
+  GRPC_CLOSURE_INIT(&on_resolved_, OnResolvedLocked, this,
+                    grpc_combiner_scheduler(combiner()));
+}
+
+AresDnsResolver::~AresDnsResolver() {
+  gpr_log(GPR_DEBUG, "destroying AresDnsResolver");
+  if (resolved_result_ != nullptr) {
+    grpc_channel_args_destroy(resolved_result_);
+  }
+  grpc_pollset_set_destroy(interested_parties_);
+  gpr_free(dns_server_);
+  gpr_free(name_to_resolve_);
+  grpc_channel_args_destroy(channel_args_);
+}
+
+void AresDnsResolver::NextLocked(grpc_channel_args** target_result,
+                                 grpc_closure* on_complete) {
+  gpr_log(GPR_DEBUG, "AresDnsResolver::NextLocked() is called.");
+  GPR_ASSERT(next_completion_ == nullptr);
+  next_completion_ = on_complete;
+  target_result_ = target_result;
+  if (resolved_version_ == 0 && !resolving_) {
+    MaybeStartResolvingLocked();
+  } else {
+    MaybeFinishNextLocked();
   }
 }
 
-static void dns_ares_channel_saw_error_locked(grpc_resolver* resolver) {
-  ares_dns_resolver* r = (ares_dns_resolver*)resolver;
-  if (!r->resolving) {
-    dns_ares_maybe_start_resolving_locked(r);
+void AresDnsResolver::RequestReresolutionLocked() {
+  if (!resolving_) {
+    MaybeStartResolvingLocked();
   }
 }
 
-static void dns_ares_on_next_resolution_timer_locked(void* arg,
-                                                     grpc_error* error) {
-  ares_dns_resolver* r = (ares_dns_resolver*)arg;
-  r->have_next_resolution_timer = false;
+void AresDnsResolver::ShutdownLocked() {
+  if (have_next_resolution_timer_) {
+    grpc_timer_cancel(&next_resolution_timer_);
+  }
+  if (pending_request_ != nullptr) {
+    grpc_cancel_ares_request(pending_request_);
+  }
+  if (next_completion_ != nullptr) {
+    *target_result_ = nullptr;
+    GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                                             "Resolver Shutdown"));
+    next_completion_ = nullptr;
+  }
+}
+
+void AresDnsResolver::OnNextResolutionLocked(void* arg, grpc_error* error) {
+  AresDnsResolver* r = static_cast<AresDnsResolver*>(arg);
+  r->have_next_resolution_timer_ = false;
   if (error == GRPC_ERROR_NONE) {
-    if (!r->resolving) {
-      dns_ares_start_resolving_locked(r);
+    if (!r->resolving_) {
+      r->StartResolvingLocked();
     }
   }
-  GRPC_RESOLVER_UNREF(&r->base, "next_resolution_timer");
+  r->Unref(DEBUG_LOCATION, "next_resolution_timer");
 }
 
-static bool value_in_json_array(grpc_json* array, const char* value) {
+bool ValueInJsonArray(grpc_json* array, const char* value) {
   for (grpc_json* entry = array->child; entry != nullptr; entry = entry->next) {
     if (entry->type == GRPC_JSON_STRING && strcmp(entry->value, value) == 0) {
       return true;
@@ -160,7 +219,7 @@
   return false;
 }
 
-static char* choose_service_config(char* service_config_choice_json) {
+char* ChooseServiceConfig(char* service_config_choice_json) {
   grpc_json* choices_json = grpc_json_parse_string(service_config_choice_json);
   if (choices_json == nullptr || choices_json->type != GRPC_JSON_ARRAY) {
     gpr_log(GPR_ERROR, "cannot parse service config JSON string");
@@ -178,8 +237,7 @@
          field = field->next) {
       // Check client language, if specified.
       if (strcmp(field->key, "clientLanguage") == 0) {
-        if (field->type != GRPC_JSON_ARRAY ||
-            !value_in_json_array(field, "c++")) {
+        if (field->type != GRPC_JSON_ARRAY || !ValueInJsonArray(field, "c++")) {
           service_config_json = nullptr;
           break;
         }
@@ -188,7 +246,7 @@
       if (strcmp(field->key, "clientHostname") == 0) {
         char* hostname = grpc_gethostname();
         if (hostname == nullptr || field->type != GRPC_JSON_ARRAY ||
-            !value_in_json_array(field, hostname)) {
+            !ValueInJsonArray(field, hostname)) {
           service_config_json = nullptr;
           break;
         }
@@ -223,24 +281,24 @@
   return service_config;
 }
 
-static void dns_ares_on_resolved_locked(void* arg, grpc_error* error) {
-  ares_dns_resolver* r = (ares_dns_resolver*)arg;
+void AresDnsResolver::OnResolvedLocked(void* arg, grpc_error* error) {
+  AresDnsResolver* r = static_cast<AresDnsResolver*>(arg);
   grpc_channel_args* result = nullptr;
-  GPR_ASSERT(r->resolving);
-  r->resolving = false;
-  r->pending_request = nullptr;
-  if (r->lb_addresses != nullptr) {
+  GPR_ASSERT(r->resolving_);
+  r->resolving_ = false;
+  r->pending_request_ = nullptr;
+  if (r->lb_addresses_ != nullptr) {
     static const char* args_to_remove[2];
     size_t num_args_to_remove = 0;
     grpc_arg new_args[3];
     size_t num_args_to_add = 0;
     new_args[num_args_to_add++] =
-        grpc_lb_addresses_create_channel_arg(r->lb_addresses);
+        grpc_lb_addresses_create_channel_arg(r->lb_addresses_);
     grpc_service_config* service_config = nullptr;
     char* service_config_string = nullptr;
-    if (r->service_config_json != nullptr) {
-      service_config_string = choose_service_config(r->service_config_json);
-      gpr_free(r->service_config_json);
+    if (r->service_config_json_ != nullptr) {
+      service_config_string = ChooseServiceConfig(r->service_config_json_);
+      gpr_free(r->service_config_json_);
       if (service_config_string != nullptr) {
         gpr_log(GPR_INFO, "selected service config choice: %s",
                 service_config_string);
@@ -260,221 +318,150 @@
       }
     }
     result = grpc_channel_args_copy_and_add_and_remove(
-        r->channel_args, args_to_remove, num_args_to_remove, new_args,
+        r->channel_args_, args_to_remove, num_args_to_remove, new_args,
         num_args_to_add);
     if (service_config != nullptr) grpc_service_config_destroy(service_config);
     gpr_free(service_config_string);
-    grpc_lb_addresses_destroy(r->lb_addresses);
+    grpc_lb_addresses_destroy(r->lb_addresses_);
     // Reset backoff state so that we start from the beginning when the
     // next request gets triggered.
-    r->backoff->Reset();
+    r->backoff_.Reset();
   } else {
     const char* msg = grpc_error_string(error);
     gpr_log(GPR_DEBUG, "dns resolution failed: %s", msg);
-    grpc_millis next_try = r->backoff->NextAttemptTime();
-    grpc_millis timeout = next_try - grpc_core::ExecCtx::Get()->Now();
+    grpc_millis next_try = r->backoff_.NextAttemptTime();
+    grpc_millis timeout = next_try - ExecCtx::Get()->Now();
     gpr_log(GPR_INFO, "dns resolution failed (will retry): %s",
             grpc_error_string(error));
-    GPR_ASSERT(!r->have_next_resolution_timer);
-    r->have_next_resolution_timer = true;
-    GRPC_RESOLVER_REF(&r->base, "next_resolution_timer");
+    GPR_ASSERT(!r->have_next_resolution_timer_);
+    r->have_next_resolution_timer_ = true;
+    // TODO(roth): We currently deal with this ref manually.  Once the
+    // new closure API is done, find a way to track this ref with the timer
+    // callback as part of the type system.
+    RefCountedPtr<Resolver> self = r->Ref(DEBUG_LOCATION, "retry-timer");
+    self.release();
     if (timeout > 0) {
       gpr_log(GPR_DEBUG, "retrying in %" PRIdPTR " milliseconds", timeout);
     } else {
       gpr_log(GPR_DEBUG, "retrying immediately");
     }
-    grpc_timer_init(&r->next_resolution_timer, next_try,
-                    &r->dns_ares_on_next_resolution_timer_closure);
+    grpc_timer_init(&r->next_resolution_timer_, next_try,
+                    &r->on_next_resolution_);
   }
-  if (r->resolved_result != nullptr) {
-    grpc_channel_args_destroy(r->resolved_result);
+  if (r->resolved_result_ != nullptr) {
+    grpc_channel_args_destroy(r->resolved_result_);
   }
-  r->resolved_result = result;
-  r->last_resolution_timestamp = grpc_core::ExecCtx::Get()->Now();
-  r->resolved_version++;
-  dns_ares_maybe_finish_next_locked(r);
-  GRPC_RESOLVER_UNREF(&r->base, "dns-resolving");
+  r->resolved_result_ = result;
+  ++r->resolved_version_;
+  r->MaybeFinishNextLocked();
+  r->Unref(DEBUG_LOCATION, "dns-resolving");
 }
 
-static void dns_ares_next_locked(grpc_resolver* resolver,
-                                 grpc_channel_args** target_result,
-                                 grpc_closure* on_complete) {
-  gpr_log(GPR_DEBUG, "dns_ares_next is called.");
-  ares_dns_resolver* r = (ares_dns_resolver*)resolver;
-  GPR_ASSERT(!r->next_completion);
-  r->next_completion = on_complete;
-  r->target_result = target_result;
-  if (r->resolved_version == 0 && !r->resolving) {
-    dns_ares_maybe_start_resolving_locked(r);
-  } else {
-    dns_ares_maybe_finish_next_locked(r);
-  }
-}
-
-static void dns_ares_start_resolving_locked(ares_dns_resolver* r) {
-  GRPC_RESOLVER_REF(&r->base, "dns-resolving");
-  GPR_ASSERT(!r->resolving);
-  r->resolving = true;
-  r->lb_addresses = nullptr;
-  r->service_config_json = nullptr;
-  r->pending_request = grpc_dns_lookup_ares(
-      r->dns_server, r->name_to_resolve, r->default_port, r->interested_parties,
-      &r->dns_ares_on_resolved_closure, &r->lb_addresses,
-      true /* check_grpclb */,
-      r->request_service_config ? &r->service_config_json : nullptr);
-}
-
-static void dns_ares_maybe_finish_next_locked(ares_dns_resolver* r) {
-  if (r->next_completion != nullptr &&
-      r->resolved_version != r->published_version) {
-    *r->target_result = r->resolved_result == nullptr
-                            ? nullptr
-                            : grpc_channel_args_copy(r->resolved_result);
-    gpr_log(GPR_DEBUG, "dns_ares_maybe_finish_next_locked");
-    GRPC_CLOSURE_SCHED(r->next_completion, GRPC_ERROR_NONE);
-    r->next_completion = nullptr;
-    r->published_version = r->resolved_version;
-  }
-}
-
-static void dns_ares_maybe_start_resolving_locked(ares_dns_resolver* r) {
-  if (r->last_resolution_timestamp >= 0) {
+void AresDnsResolver::MaybeStartResolvingLocked() {
+  if (last_resolution_timestamp_ >= 0) {
     const grpc_millis earliest_next_resolution =
-        r->last_resolution_timestamp + r->min_time_between_resolutions;
+        last_resolution_timestamp_ + min_time_between_resolutions_;
     const grpc_millis ms_until_next_resolution =
         earliest_next_resolution - grpc_core::ExecCtx::Get()->Now();
     if (ms_until_next_resolution > 0) {
       const grpc_millis last_resolution_ago =
-          grpc_core::ExecCtx::Get()->Now() - r->last_resolution_timestamp;
+          grpc_core::ExecCtx::Get()->Now() - last_resolution_timestamp_;
       gpr_log(GPR_DEBUG,
               "In cooldown from last resolution (from %" PRIdPTR
               " ms ago). Will resolve again in %" PRIdPTR " ms",
               last_resolution_ago, ms_until_next_resolution);
-      if (!r->have_next_resolution_timer) {
-        r->have_next_resolution_timer = true;
-        GRPC_RESOLVER_REF(&r->base, "next_resolution_timer_cooldown");
-        grpc_timer_init(&r->next_resolution_timer, ms_until_next_resolution,
-                        &r->dns_ares_on_next_resolution_timer_closure);
+      if (!have_next_resolution_timer_) {
+        have_next_resolution_timer_ = true;
+        // TODO(roth): We currently deal with this ref manually.  Once the
+        // new closure API is done, find a way to track this ref with the timer
+        // callback as part of the type system.
+        RefCountedPtr<Resolver> self =
+            Ref(DEBUG_LOCATION, "next_resolution_timer_cooldown");
+        self.release();
+        grpc_timer_init(&next_resolution_timer_, ms_until_next_resolution,
+                        &on_next_resolution_);
       }
       // TODO(dgq): remove the following two lines once Pick First stops
       // discarding subchannels after selecting.
-      ++r->resolved_version;
-      dns_ares_maybe_finish_next_locked(r);
+      ++resolved_version_;
+      MaybeFinishNextLocked();
       return;
     }
   }
-  dns_ares_start_resolving_locked(r);
+  StartResolvingLocked();
 }
 
-static void dns_ares_destroy(grpc_resolver* gr) {
-  gpr_log(GPR_DEBUG, "dns_ares_destroy");
-  ares_dns_resolver* r = (ares_dns_resolver*)gr;
-  if (r->resolved_result != nullptr) {
-    grpc_channel_args_destroy(r->resolved_result);
+void AresDnsResolver::StartResolvingLocked() {
+  // TODO(roth): We currently deal with this ref manually.  Once the
+  // new closure API is done, find a way to track this ref with the timer
+  // callback as part of the type system.
+  RefCountedPtr<Resolver> self = Ref(DEBUG_LOCATION, "dns-resolving");
+  self.release();
+  GPR_ASSERT(!resolving_);
+  resolving_ = true;
+  lb_addresses_ = nullptr;
+  service_config_json_ = nullptr;
+  pending_request_ = grpc_dns_lookup_ares(
+      dns_server_, name_to_resolve_, kDefaultPort, interested_parties_,
+      &on_resolved_, &lb_addresses_, true /* check_grpclb */,
+      request_service_config_ ? &service_config_json_ : nullptr);
+  last_resolution_timestamp_ = grpc_core::ExecCtx::Get()->Now();
+}
+
+void AresDnsResolver::MaybeFinishNextLocked() {
+  if (next_completion_ != nullptr && resolved_version_ != published_version_) {
+    *target_result_ = resolved_result_ == nullptr
+                          ? nullptr
+                          : grpc_channel_args_copy(resolved_result_);
+    gpr_log(GPR_DEBUG, "AresDnsResolver::MaybeFinishNextLocked()");
+    GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_NONE);
+    next_completion_ = nullptr;
+    published_version_ = resolved_version_;
   }
-  grpc_pollset_set_destroy(r->interested_parties);
-  gpr_free(r->dns_server);
-  gpr_free(r->name_to_resolve);
-  gpr_free(r->default_port);
-  grpc_channel_args_destroy(r->channel_args);
-  gpr_free(r);
 }
 
-static grpc_resolver* dns_ares_create(grpc_resolver_args* args,
-                                      const char* default_port) {
-  /* Get name from args. */
-  const char* path = args->uri->path;
-  if (path[0] == '/') ++path;
-  /* Create resolver. */
-  ares_dns_resolver* r =
-      (ares_dns_resolver*)gpr_zalloc(sizeof(ares_dns_resolver));
-  grpc_resolver_init(&r->base, &dns_ares_resolver_vtable, args->combiner);
-  if (0 != strcmp(args->uri->authority, "")) {
-    r->dns_server = gpr_strdup(args->uri->authority);
+//
+// Factory
+//
+
+class AresDnsResolverFactory : public ResolverFactory {
+ public:
+  OrphanablePtr<Resolver> CreateResolver(
+      const ResolverArgs& args) const override {
+    return OrphanablePtr<Resolver>(New<AresDnsResolver>(args));
   }
-  r->name_to_resolve = gpr_strdup(path);
-  r->default_port = gpr_strdup(default_port);
-  r->channel_args = grpc_channel_args_copy(args->args);
-  const grpc_arg* arg = grpc_channel_args_find(
-      r->channel_args, GRPC_ARG_SERVICE_CONFIG_DISABLE_RESOLUTION);
-  r->request_service_config = !grpc_channel_arg_get_integer(
-      arg, (grpc_integer_options){false, false, true});
-  r->interested_parties = grpc_pollset_set_create();
-  if (args->pollset_set != nullptr) {
-    grpc_pollset_set_add_pollset_set(r->interested_parties, args->pollset_set);
-  }
-  grpc_core::BackOff::Options backoff_options;
-  backoff_options
-      .set_initial_backoff(GRPC_DNS_INITIAL_CONNECT_BACKOFF_SECONDS * 1000)
-      .set_multiplier(GRPC_DNS_RECONNECT_BACKOFF_MULTIPLIER)
-      .set_jitter(GRPC_DNS_RECONNECT_JITTER)
-      .set_max_backoff(GRPC_DNS_RECONNECT_MAX_BACKOFF_SECONDS * 1000);
-  r->backoff.Init(grpc_core::BackOff(backoff_options));
-  GRPC_CLOSURE_INIT(&r->dns_ares_on_next_resolution_timer_closure,
-                    dns_ares_on_next_resolution_timer_locked, r,
-                    grpc_combiner_scheduler(r->base.combiner));
-  GRPC_CLOSURE_INIT(&r->dns_ares_on_resolved_closure,
-                    dns_ares_on_resolved_locked, r,
-                    grpc_combiner_scheduler(r->base.combiner));
-  const grpc_arg* period_arg = grpc_channel_args_find(
-      args->args, GRPC_ARG_DNS_MIN_TIME_BETWEEN_RESOLUTIONS_MS);
-  r->min_time_between_resolutions =
-      grpc_channel_arg_get_integer(period_arg, {1000, 0, INT_MAX});
-  r->last_resolution_timestamp = -1;
-  return &r->base;
-}
 
-/*
- * FACTORY
- */
+  const char* scheme() const override { return "dns"; }
+};
 
-static void dns_ares_factory_ref(grpc_resolver_factory* factory) {}
+}  // namespace
 
-static void dns_ares_factory_unref(grpc_resolver_factory* factory) {}
+}  // namespace grpc_core
 
-static grpc_resolver* dns_factory_create_resolver(
-    grpc_resolver_factory* factory, grpc_resolver_args* args) {
-  return dns_ares_create(args, "https");
-}
-
-static char* dns_ares_factory_get_default_host_name(
-    grpc_resolver_factory* factory, grpc_uri* uri) {
-  const char* path = uri->path;
-  if (path[0] == '/') ++path;
-  return gpr_strdup(path);
-}
-
-static const grpc_resolver_factory_vtable dns_ares_factory_vtable = {
-    dns_ares_factory_ref, dns_ares_factory_unref, dns_factory_create_resolver,
-    dns_ares_factory_get_default_host_name, "dns"};
-static grpc_resolver_factory dns_resolver_factory = {&dns_ares_factory_vtable};
-
-static grpc_resolver_factory* dns_ares_resolver_factory_create() {
-  return &dns_resolver_factory;
-}
-
-void grpc_resolver_dns_ares_init(void) {
-  char* resolver = gpr_getenv("GRPC_DNS_RESOLVER");
+void grpc_resolver_dns_ares_init() {
+  char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
   /* TODO(zyc): Turn on c-ares based resolver by default after the address
      sorter and the CNAME support are added. */
-  if (resolver != nullptr && gpr_stricmp(resolver, "ares") == 0) {
+  if (resolver_env != nullptr && gpr_stricmp(resolver_env, "ares") == 0) {
     grpc_error* error = grpc_ares_init();
     if (error != GRPC_ERROR_NONE) {
       GRPC_LOG_IF_ERROR("ares_library_init() failed", error);
       return;
     }
     grpc_resolve_address = grpc_resolve_address_ares;
-    grpc_register_resolver_type(dns_ares_resolver_factory_create());
+    grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
+        grpc_core::UniquePtr<grpc_core::ResolverFactory>(
+            grpc_core::New<grpc_core::AresDnsResolverFactory>()));
   }
-  gpr_free(resolver);
+  gpr_free(resolver_env);
 }
 
-void grpc_resolver_dns_ares_shutdown(void) {
-  char* resolver = gpr_getenv("GRPC_DNS_RESOLVER");
-  if (resolver != nullptr && gpr_stricmp(resolver, "ares") == 0) {
+void grpc_resolver_dns_ares_shutdown() {
+  char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
+  if (resolver_env != nullptr && gpr_stricmp(resolver_env, "ares") == 0) {
     grpc_ares_cleanup();
   }
-  gpr_free(resolver);
+  gpr_free(resolver_env);
 }
 
 #else /* GRPC_ARES == 1 && !defined(GRPC_UV) */
diff --git a/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc b/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
index 478810d..fbab136 100644
--- a/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
+++ b/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc
@@ -43,301 +43,298 @@
 #define GRPC_DNS_RECONNECT_MAX_BACKOFF_SECONDS 120
 #define GRPC_DNS_RECONNECT_JITTER 0.2
 
-typedef struct {
-  /** base class: must be first */
-  grpc_resolver base;
-  /** name to resolve */
-  char* name_to_resolve;
-  /** default port to use */
-  char* default_port;
-  /** channel args. */
-  grpc_channel_args* channel_args;
-  /** pollset_set to drive the name resolution process */
-  grpc_pollset_set* interested_parties;
+namespace grpc_core {
 
-  /** are we currently resolving? */
-  bool resolving;
-  /** which version of the result have we published? */
-  int published_version;
-  /** which version of the result is current? */
-  int resolved_version;
-  /** pending next completion, or NULL */
-  grpc_closure* next_completion;
-  /** target result address for next completion */
-  grpc_channel_args** target_result;
-  /** current (fully resolved) result */
-  grpc_channel_args* resolved_result;
-  /** next resolution timer */
-  bool have_next_resolution_timer;
-  grpc_timer next_resolution_timer;
-  grpc_closure next_resolution_closure;
-  /** retry backoff state */
-  grpc_core::ManualConstructor<grpc_core::BackOff> backoff;
-  /** min resolution period. Max one resolution will happen per period */
-  grpc_millis min_time_between_resolutions;
-  /** when was the last resolution? -1 if no resolution has happened yet */
-  grpc_millis last_resolution_timestamp;
-  /** currently resolving addresses */
-  grpc_resolved_addresses* addresses;
-} dns_resolver;
+namespace {
 
-static void dns_destroy(grpc_resolver* r);
+const char kDefaultPort[] = "https";
 
-static void dns_start_resolving_locked(dns_resolver* r);
-static void maybe_start_resolving_locked(dns_resolver* r);
-static void dns_maybe_finish_next_locked(dns_resolver* r);
+class NativeDnsResolver : public Resolver {
+ public:
+  explicit NativeDnsResolver(const ResolverArgs& args);
 
-static void dns_shutdown_locked(grpc_resolver* r);
-static void dns_channel_saw_error_locked(grpc_resolver* r);
-static void dns_next_locked(grpc_resolver* r, grpc_channel_args** target_result,
-                            grpc_closure* on_complete);
+  void NextLocked(grpc_channel_args** result,
+                  grpc_closure* on_complete) override;
 
-static const grpc_resolver_vtable dns_resolver_vtable = {
-    dns_destroy, dns_shutdown_locked, dns_channel_saw_error_locked,
-    dns_next_locked};
+  void RequestReresolutionLocked() override;
 
-static void dns_shutdown_locked(grpc_resolver* resolver) {
-  dns_resolver* r = (dns_resolver*)resolver;
-  if (r->have_next_resolution_timer) {
-    grpc_timer_cancel(&r->next_resolution_timer);
+  void ShutdownLocked() override;
+
+ private:
+  virtual ~NativeDnsResolver();
+
+  void MaybeStartResolvingLocked();
+  void StartResolvingLocked();
+  void MaybeFinishNextLocked();
+
+  static void OnNextResolutionLocked(void* arg, grpc_error* error);
+  static void OnResolvedLocked(void* arg, grpc_error* error);
+
+  /// name to resolve
+  char* name_to_resolve_ = nullptr;
+  /// channel args
+  grpc_channel_args* channel_args_ = nullptr;
+  /// pollset_set to drive the name resolution process
+  grpc_pollset_set* interested_parties_ = nullptr;
+  /// are we currently resolving?
+  bool resolving_ = false;
+  grpc_closure on_resolved_;
+  /// which version of the result have we published?
+  int published_version_ = 0;
+  /// which version of the result is current?
+  int resolved_version_ = 0;
+  /// pending next completion, or nullptr
+  grpc_closure* next_completion_ = nullptr;
+  /// target result address for next completion
+  grpc_channel_args** target_result_ = nullptr;
+  /// current (fully resolved) result
+  grpc_channel_args* resolved_result_ = nullptr;
+  /// next resolution timer
+  bool have_next_resolution_timer_ = false;
+  grpc_timer next_resolution_timer_;
+  grpc_closure on_next_resolution_;
+  /// min time between DNS requests
+  grpc_millis min_time_between_resolutions_;
+  /// timestamp of last DNS request
+  grpc_millis last_resolution_timestamp_ = -1;
+  /// retry backoff state
+  BackOff backoff_;
+  /// currently resolving addresses
+  grpc_resolved_addresses* addresses_ = nullptr;
+};
+
+NativeDnsResolver::NativeDnsResolver(const ResolverArgs& args)
+    : Resolver(args.combiner),
+      backoff_(
+          BackOff::Options()
+              .set_initial_backoff(GRPC_DNS_INITIAL_CONNECT_BACKOFF_SECONDS *
+                                   1000)
+              .set_multiplier(GRPC_DNS_RECONNECT_BACKOFF_MULTIPLIER)
+              .set_jitter(GRPC_DNS_RECONNECT_JITTER)
+              .set_max_backoff(GRPC_DNS_RECONNECT_MAX_BACKOFF_SECONDS * 1000)) {
+  char* path = args.uri->path;
+  if (path[0] == '/') ++path;
+  name_to_resolve_ = gpr_strdup(path);
+  channel_args_ = grpc_channel_args_copy(args.args);
+  const grpc_arg* arg = grpc_channel_args_find(
+      args.args, GRPC_ARG_DNS_MIN_TIME_BETWEEN_RESOLUTIONS_MS);
+  min_time_between_resolutions_ =
+      grpc_channel_arg_get_integer(arg, {1000, 0, INT_MAX});
+  interested_parties_ = grpc_pollset_set_create();
+  if (args.pollset_set != nullptr) {
+    grpc_pollset_set_add_pollset_set(interested_parties_, args.pollset_set);
   }
-  if (r->next_completion != nullptr) {
-    *r->target_result = nullptr;
-    GRPC_CLOSURE_SCHED(r->next_completion, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
-                                               "Resolver Shutdown"));
-    r->next_completion = nullptr;
-  }
+  GRPC_CLOSURE_INIT(&on_next_resolution_,
+                    NativeDnsResolver::OnNextResolutionLocked, this,
+                    grpc_combiner_scheduler(args.combiner));
+  GRPC_CLOSURE_INIT(&on_resolved_, NativeDnsResolver::OnResolvedLocked, this,
+                    grpc_combiner_scheduler(args.combiner));
 }
 
-static void dns_channel_saw_error_locked(grpc_resolver* resolver) {
-  dns_resolver* r = (dns_resolver*)resolver;
-  if (!r->resolving) {
-    maybe_start_resolving_locked(r);
+NativeDnsResolver::~NativeDnsResolver() {
+  if (resolved_result_ != nullptr) {
+    grpc_channel_args_destroy(resolved_result_);
   }
+  grpc_pollset_set_destroy(interested_parties_);
+  gpr_free(name_to_resolve_);
+  grpc_channel_args_destroy(channel_args_);
 }
 
-static void dns_next_locked(grpc_resolver* resolver,
-                            grpc_channel_args** target_result,
-                            grpc_closure* on_complete) {
-  dns_resolver* r = (dns_resolver*)resolver;
-  GPR_ASSERT(!r->next_completion);
-  r->next_completion = on_complete;
-  r->target_result = target_result;
-  if (r->resolved_version == 0 && !r->resolving) {
-    maybe_start_resolving_locked(r);
+void NativeDnsResolver::NextLocked(grpc_channel_args** result,
+                                   grpc_closure* on_complete) {
+  GPR_ASSERT(next_completion_ == nullptr);
+  next_completion_ = on_complete;
+  target_result_ = result;
+  if (resolved_version_ == 0 && !resolving_) {
+    MaybeStartResolvingLocked();
   } else {
-    dns_maybe_finish_next_locked(r);
+    MaybeFinishNextLocked();
   }
 }
 
-static void dns_on_next_resolution_timer_locked(void* arg, grpc_error* error) {
-  dns_resolver* r = (dns_resolver*)arg;
-  r->have_next_resolution_timer = false;
-  if (error == GRPC_ERROR_NONE && !r->resolving) {
-    dns_start_resolving_locked(r);
+void NativeDnsResolver::RequestReresolutionLocked() {
+  if (!resolving_) {
+    MaybeStartResolvingLocked();
   }
-  GRPC_RESOLVER_UNREF(&r->base, "next_resolution_timer");
 }
 
-static void dns_on_resolved_locked(void* arg, grpc_error* error) {
-  dns_resolver* r = (dns_resolver*)arg;
+void NativeDnsResolver::ShutdownLocked() {
+  if (have_next_resolution_timer_) {
+    grpc_timer_cancel(&next_resolution_timer_);
+  }
+  if (next_completion_ != nullptr) {
+    *target_result_ = nullptr;
+    GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                                             "Resolver Shutdown"));
+    next_completion_ = nullptr;
+  }
+}
+
+void NativeDnsResolver::OnNextResolutionLocked(void* arg, grpc_error* error) {
+  NativeDnsResolver* r = static_cast<NativeDnsResolver*>(arg);
+  r->have_next_resolution_timer_ = false;
+  if (error == GRPC_ERROR_NONE && !r->resolving_) {
+    r->StartResolvingLocked();
+  }
+  r->Unref(DEBUG_LOCATION, "retry-timer");
+}
+
+void NativeDnsResolver::OnResolvedLocked(void* arg, grpc_error* error) {
+  NativeDnsResolver* r = static_cast<NativeDnsResolver*>(arg);
   grpc_channel_args* result = nullptr;
-  GPR_ASSERT(r->resolving);
-  r->resolving = false;
+  GPR_ASSERT(r->resolving_);
+  r->resolving_ = false;
   GRPC_ERROR_REF(error);
-  error = grpc_error_set_str(error, GRPC_ERROR_STR_TARGET_ADDRESS,
-                             grpc_slice_from_copied_string(r->name_to_resolve));
-  if (r->addresses != nullptr) {
+  error =
+      grpc_error_set_str(error, GRPC_ERROR_STR_TARGET_ADDRESS,
+                         grpc_slice_from_copied_string(r->name_to_resolve_));
+  if (r->addresses_ != nullptr) {
     grpc_lb_addresses* addresses = grpc_lb_addresses_create(
-        r->addresses->naddrs, nullptr /* user_data_vtable */);
-    for (size_t i = 0; i < r->addresses->naddrs; ++i) {
+        r->addresses_->naddrs, nullptr /* user_data_vtable */);
+    for (size_t i = 0; i < r->addresses_->naddrs; ++i) {
       grpc_lb_addresses_set_address(
-          addresses, i, &r->addresses->addrs[i].addr,
-          r->addresses->addrs[i].len, false /* is_balancer */,
+          addresses, i, &r->addresses_->addrs[i].addr,
+          r->addresses_->addrs[i].len, false /* is_balancer */,
           nullptr /* balancer_name */, nullptr /* user_data */);
     }
     grpc_arg new_arg = grpc_lb_addresses_create_channel_arg(addresses);
-    result = grpc_channel_args_copy_and_add(r->channel_args, &new_arg, 1);
-    grpc_resolved_addresses_destroy(r->addresses);
+    result = grpc_channel_args_copy_and_add(r->channel_args_, &new_arg, 1);
+    grpc_resolved_addresses_destroy(r->addresses_);
     grpc_lb_addresses_destroy(addresses);
     // Reset backoff state so that we start from the beginning when the
     // next request gets triggered.
-    r->backoff->Reset();
+    r->backoff_.Reset();
   } else {
-    grpc_millis next_try = r->backoff->NextAttemptTime();
-    grpc_millis timeout = next_try - grpc_core::ExecCtx::Get()->Now();
+    grpc_millis next_try = r->backoff_.NextAttemptTime();
+    grpc_millis timeout = next_try - ExecCtx::Get()->Now();
     gpr_log(GPR_INFO, "dns resolution failed (will retry): %s",
             grpc_error_string(error));
-    GPR_ASSERT(!r->have_next_resolution_timer);
-    r->have_next_resolution_timer = true;
-    GRPC_RESOLVER_REF(&r->base, "next_resolution_timer");
+    GPR_ASSERT(!r->have_next_resolution_timer_);
+    r->have_next_resolution_timer_ = true;
+    // TODO(roth): We currently deal with this ref manually.  Once the
+    // new closure API is done, find a way to track this ref with the timer
+    // callback as part of the type system.
+    RefCountedPtr<Resolver> self =
+        r->Ref(DEBUG_LOCATION, "next_resolution_timer");
+    self.release();
     if (timeout > 0) {
       gpr_log(GPR_DEBUG, "retrying in %" PRIdPTR " milliseconds", timeout);
     } else {
       gpr_log(GPR_DEBUG, "retrying immediately");
     }
-    grpc_timer_init(&r->next_resolution_timer, next_try,
-                    &r->next_resolution_closure);
+    grpc_timer_init(&r->next_resolution_timer_, next_try,
+                    &r->on_next_resolution_);
   }
-  if (r->resolved_result != nullptr) {
-    grpc_channel_args_destroy(r->resolved_result);
+  if (r->resolved_result_ != nullptr) {
+    grpc_channel_args_destroy(r->resolved_result_);
   }
-  r->resolved_result = result;
-  r->resolved_version++;
-  dns_maybe_finish_next_locked(r);
+  r->resolved_result_ = result;
+  ++r->resolved_version_;
+  r->MaybeFinishNextLocked();
   GRPC_ERROR_UNREF(error);
-
-  GRPC_RESOLVER_UNREF(&r->base, "dns-resolving");
+  r->Unref(DEBUG_LOCATION, "dns-resolving");
 }
 
-static void maybe_start_resolving_locked(dns_resolver* r) {
-  if (r->last_resolution_timestamp >= 0) {
+void NativeDnsResolver::MaybeStartResolvingLocked() {
+  if (last_resolution_timestamp_ >= 0) {
     const grpc_millis earliest_next_resolution =
-        r->last_resolution_timestamp + r->min_time_between_resolutions;
+        last_resolution_timestamp_ + min_time_between_resolutions_;
     const grpc_millis ms_until_next_resolution =
         earliest_next_resolution - grpc_core::ExecCtx::Get()->Now();
     if (ms_until_next_resolution > 0) {
       const grpc_millis last_resolution_ago =
-          grpc_core::ExecCtx::Get()->Now() - r->last_resolution_timestamp;
+          grpc_core::ExecCtx::Get()->Now() - last_resolution_timestamp_;
       gpr_log(GPR_DEBUG,
               "In cooldown from last resolution (from %" PRIdPTR
               " ms ago). Will resolve again in %" PRIdPTR " ms",
               last_resolution_ago, ms_until_next_resolution);
-      if (!r->have_next_resolution_timer) {
-        r->have_next_resolution_timer = true;
-        GRPC_RESOLVER_REF(&r->base, "next_resolution_timer_cooldown");
-        grpc_timer_init(&r->next_resolution_timer, ms_until_next_resolution,
-                        &r->next_resolution_closure);
+      if (!have_next_resolution_timer_) {
+        have_next_resolution_timer_ = true;
+        // TODO(roth): We currently deal with this ref manually.  Once the
+        // new closure API is done, find a way to track this ref with the timer
+        // callback as part of the type system.
+        RefCountedPtr<Resolver> self =
+            Ref(DEBUG_LOCATION, "next_resolution_timer_cooldown");
+        self.release();
+        grpc_timer_init(&next_resolution_timer_, ms_until_next_resolution,
+                        &on_next_resolution_);
       }
       // TODO(dgq): remove the following two lines once Pick First stops
       // discarding subchannels after selecting.
-      ++r->resolved_version;
-      dns_maybe_finish_next_locked(r);
+      ++resolved_version_;
+      MaybeFinishNextLocked();
       return;
     }
   }
-  dns_start_resolving_locked(r);
+  StartResolvingLocked();
 }
 
-static void dns_start_resolving_locked(dns_resolver* r) {
-  GRPC_RESOLVER_REF(&r->base, "dns-resolving");
-  GPR_ASSERT(!r->resolving);
-  r->resolving = true;
-  r->addresses = nullptr;
-  grpc_resolve_address(
-      r->name_to_resolve, r->default_port, r->interested_parties,
-      GRPC_CLOSURE_CREATE(dns_on_resolved_locked, r,
-                          grpc_combiner_scheduler(r->base.combiner)),
-      &r->addresses);
-  r->last_resolution_timestamp = grpc_core::ExecCtx::Get()->Now();
+void NativeDnsResolver::StartResolvingLocked() {
+  // TODO(roth): We currently deal with this ref manually.  Once the
+  // new closure API is done, find a way to track this ref with the timer
+  // callback as part of the type system.
+  RefCountedPtr<Resolver> self = Ref(DEBUG_LOCATION, "dns-resolving");
+  self.release();
+  GPR_ASSERT(!resolving_);
+  resolving_ = true;
+  addresses_ = nullptr;
+  grpc_resolve_address(name_to_resolve_, kDefaultPort, interested_parties_,
+                       &on_resolved_, &addresses_);
+  last_resolution_timestamp_ = grpc_core::ExecCtx::Get()->Now();
 }
 
-static void dns_maybe_finish_next_locked(dns_resolver* r) {
-  if (r->next_completion != nullptr &&
-      r->resolved_version != r->published_version) {
-    *r->target_result = r->resolved_result == nullptr
-                            ? nullptr
-                            : grpc_channel_args_copy(r->resolved_result);
-    GRPC_CLOSURE_SCHED(r->next_completion, GRPC_ERROR_NONE);
-    r->next_completion = nullptr;
-    r->published_version = r->resolved_version;
+void NativeDnsResolver::MaybeFinishNextLocked() {
+  if (next_completion_ != nullptr && resolved_version_ != published_version_) {
+    *target_result_ = resolved_result_ == nullptr
+                          ? nullptr
+                          : grpc_channel_args_copy(resolved_result_);
+    GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_NONE);
+    next_completion_ = nullptr;
+    published_version_ = resolved_version_;
   }
 }
 
-static void dns_destroy(grpc_resolver* gr) {
-  dns_resolver* r = (dns_resolver*)gr;
-  if (r->resolved_result != nullptr) {
-    grpc_channel_args_destroy(r->resolved_result);
+//
+// Factory
+//
+
+class NativeDnsResolverFactory : public ResolverFactory {
+ public:
+  OrphanablePtr<Resolver> CreateResolver(
+      const ResolverArgs& args) const override {
+    if (0 != strcmp(args.uri->authority, "")) {
+      gpr_log(GPR_ERROR, "authority based dns uri's not supported");
+      return OrphanablePtr<Resolver>(nullptr);
+    }
+    return OrphanablePtr<Resolver>(New<NativeDnsResolver>(args));
   }
-  grpc_pollset_set_destroy(r->interested_parties);
-  gpr_free(r->name_to_resolve);
-  gpr_free(r->default_port);
-  grpc_channel_args_destroy(r->channel_args);
-  gpr_free(r);
-}
 
-static grpc_resolver* dns_create(grpc_resolver_args* args,
-                                 const char* default_port) {
-  if (0 != strcmp(args->uri->authority, "")) {
-    gpr_log(GPR_ERROR, "authority based dns uri's not supported");
-    return nullptr;
-  }
-  // Get name from args.
-  char* path = args->uri->path;
-  if (path[0] == '/') ++path;
-  // Create resolver.
-  dns_resolver* r = (dns_resolver*)gpr_zalloc(sizeof(dns_resolver));
-  grpc_resolver_init(&r->base, &dns_resolver_vtable, args->combiner);
-  r->name_to_resolve = gpr_strdup(path);
-  r->default_port = gpr_strdup(default_port);
-  r->channel_args = grpc_channel_args_copy(args->args);
-  r->interested_parties = grpc_pollset_set_create();
-  if (args->pollset_set != nullptr) {
-    grpc_pollset_set_add_pollset_set(r->interested_parties, args->pollset_set);
-  }
-  grpc_core::BackOff::Options backoff_options;
-  backoff_options
-      .set_initial_backoff(GRPC_DNS_INITIAL_CONNECT_BACKOFF_SECONDS * 1000)
-      .set_multiplier(GRPC_DNS_RECONNECT_BACKOFF_MULTIPLIER)
-      .set_jitter(GRPC_DNS_RECONNECT_JITTER)
-      .set_max_backoff(GRPC_DNS_RECONNECT_MAX_BACKOFF_SECONDS * 1000);
-  r->backoff.Init(grpc_core::BackOff(backoff_options));
-  const grpc_arg* period_arg = grpc_channel_args_find(
-      args->args, GRPC_ARG_DNS_MIN_TIME_BETWEEN_RESOLUTIONS_MS);
-  r->min_time_between_resolutions =
-      grpc_channel_arg_get_integer(period_arg, {1000, 0, INT_MAX});
-  r->last_resolution_timestamp = -1;
-  GRPC_CLOSURE_INIT(&r->next_resolution_closure,
-                    dns_on_next_resolution_timer_locked, r,
-                    grpc_combiner_scheduler(r->base.combiner));
-  return &r->base;
-}
+  const char* scheme() const override { return "dns"; }
+};
 
-/*
- * FACTORY
- */
+}  // namespace
 
-static void dns_factory_ref(grpc_resolver_factory* factory) {}
+}  // namespace grpc_core
 
-static void dns_factory_unref(grpc_resolver_factory* factory) {}
-
-static grpc_resolver* dns_factory_create_resolver(
-    grpc_resolver_factory* factory, grpc_resolver_args* args) {
-  return dns_create(args, "https");
-}
-
-static char* dns_factory_get_default_host_name(grpc_resolver_factory* factory,
-                                               grpc_uri* uri) {
-  const char* path = uri->path;
-  if (path[0] == '/') ++path;
-  return gpr_strdup(path);
-}
-
-static const grpc_resolver_factory_vtable dns_factory_vtable = {
-    dns_factory_ref, dns_factory_unref, dns_factory_create_resolver,
-    dns_factory_get_default_host_name, "dns"};
-static grpc_resolver_factory dns_resolver_factory = {&dns_factory_vtable};
-
-static grpc_resolver_factory* dns_resolver_factory_create() {
-  return &dns_resolver_factory;
-}
-
-void grpc_resolver_dns_native_init(void) {
-  char* resolver = gpr_getenv("GRPC_DNS_RESOLVER");
-  if (resolver != nullptr && gpr_stricmp(resolver, "native") == 0) {
+void grpc_resolver_dns_native_init() {
+  char* resolver_env = gpr_getenv("GRPC_DNS_RESOLVER");
+  if (resolver_env != nullptr && gpr_stricmp(resolver_env, "native") == 0) {
     gpr_log(GPR_DEBUG, "Using native dns resolver");
-    grpc_register_resolver_type(dns_resolver_factory_create());
+    grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
+        grpc_core::UniquePtr<grpc_core::ResolverFactory>(
+            grpc_core::New<grpc_core::NativeDnsResolverFactory>()));
   } else {
-    grpc_resolver_factory* existing_factory =
-        grpc_resolver_factory_lookup("dns");
+    grpc_core::ResolverRegistry::Builder::InitRegistry();
+    grpc_core::ResolverFactory* existing_factory =
+        grpc_core::ResolverRegistry::LookupResolverFactory("dns");
     if (existing_factory == nullptr) {
       gpr_log(GPR_DEBUG, "Using native dns resolver");
-      grpc_register_resolver_type(dns_resolver_factory_create());
-    } else {
-      grpc_resolver_factory_unref(existing_factory);
+      grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
+          grpc_core::UniquePtr<grpc_core::ResolverFactory>(
+              grpc_core::New<grpc_core::NativeDnsResolverFactory>()));
     }
   }
-  gpr_free(resolver);
+  gpr_free(resolver_env);
 }
 
-void grpc_resolver_dns_native_shutdown(void) {}
+void grpc_resolver_dns_native_shutdown() {}
diff --git a/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc b/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc
index f457917..b01e608 100644
--- a/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc
+++ b/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc
@@ -42,190 +42,177 @@
 
 #include "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h"
 
-//
-// fake_resolver
-//
+namespace grpc_core {
 
-typedef struct {
-  // Base class -- must be first
-  grpc_resolver base;
+// This cannot be in an anonymous namespace, because it is a friend of
+// FakeResolverResponseGenerator.
+class FakeResolver : public Resolver {
+ public:
+  explicit FakeResolver(const ResolverArgs& args);
 
-  // Passed-in parameters
-  grpc_channel_args* channel_args;
+  void NextLocked(grpc_channel_args** result,
+                  grpc_closure* on_complete) override;
 
+  void RequestReresolutionLocked() override;
+
+ private:
+  friend class FakeResolverResponseGenerator;
+
+  virtual ~FakeResolver();
+
+  void MaybeFinishNextLocked();
+
+  void ShutdownLocked() override;
+
+  // passed-in parameters
+  grpc_channel_args* channel_args_ = nullptr;
   // If not NULL, the next set of resolution results to be returned to
-  // grpc_resolver_next_locked()'s closure.
-  grpc_channel_args* next_results;
-
+  // NextLocked()'s closure.
+  grpc_channel_args* next_results_ = nullptr;
   // Results to use for the pretended re-resolution in
-  // fake_resolver_channel_saw_error_locked().
-  grpc_channel_args* results_upon_error;
-
+  // RequestReresolutionLocked().
+  grpc_channel_args* reresolution_results_ = nullptr;
   // TODO(juanlishen): This can go away once pick_first is changed to not throw
   // away its subchannels, since that will eliminate its dependence on
   // channel_saw_error_locked() causing an immediate resolver return.
   // A copy of the most-recently used resolution results.
-  grpc_channel_args* last_used_results;
-
-  // Pending next completion, or NULL
-  grpc_closure* next_completion;
-
-  // Target result address for next completion
-  grpc_channel_args** target_result;
-} fake_resolver;
-
-static void fake_resolver_destroy(grpc_resolver* gr) {
-  fake_resolver* r = (fake_resolver*)gr;
-  grpc_channel_args_destroy(r->next_results);
-  grpc_channel_args_destroy(r->results_upon_error);
-  grpc_channel_args_destroy(r->last_used_results);
-  grpc_channel_args_destroy(r->channel_args);
-  gpr_free(r);
-}
-
-static void fake_resolver_shutdown_locked(grpc_resolver* resolver) {
-  fake_resolver* r = (fake_resolver*)resolver;
-  if (r->next_completion != nullptr) {
-    *r->target_result = nullptr;
-    GRPC_CLOSURE_SCHED(r->next_completion, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
-                                               "Resolver Shutdown"));
-    r->next_completion = nullptr;
-  }
-}
-
-static void fake_resolver_maybe_finish_next_locked(fake_resolver* r) {
-  if (r->next_completion != nullptr && r->next_results != nullptr) {
-    *r->target_result =
-        grpc_channel_args_union(r->next_results, r->channel_args);
-    grpc_channel_args_destroy(r->next_results);
-    r->next_results = nullptr;
-    GRPC_CLOSURE_SCHED(r->next_completion, GRPC_ERROR_NONE);
-    r->next_completion = nullptr;
-  }
-}
-
-static void fake_resolver_channel_saw_error_locked(grpc_resolver* resolver) {
-  fake_resolver* r = (fake_resolver*)resolver;
-  // A resolution must have been returned before an error is seen.
-  GPR_ASSERT(r->last_used_results != nullptr);
-  grpc_channel_args_destroy(r->next_results);
-  if (r->results_upon_error != nullptr) {
-    r->next_results = grpc_channel_args_copy(r->results_upon_error);
-  } else {
-    // If results_upon_error is unavailable, re-resolve with the most-recently
-    // used results to avoid a no-op re-resolution.
-    r->next_results = grpc_channel_args_copy(r->last_used_results);
-  }
-  fake_resolver_maybe_finish_next_locked(r);
-}
-
-static void fake_resolver_next_locked(grpc_resolver* resolver,
-                                      grpc_channel_args** target_result,
-                                      grpc_closure* on_complete) {
-  fake_resolver* r = (fake_resolver*)resolver;
-  GPR_ASSERT(!r->next_completion);
-  r->next_completion = on_complete;
-  r->target_result = target_result;
-  fake_resolver_maybe_finish_next_locked(r);
-}
-
-static const grpc_resolver_vtable fake_resolver_vtable = {
-    fake_resolver_destroy, fake_resolver_shutdown_locked,
-    fake_resolver_channel_saw_error_locked, fake_resolver_next_locked};
-
-struct grpc_fake_resolver_response_generator {
-  fake_resolver* resolver;  // Set by the fake_resolver constructor to itself.
-  gpr_refcount refcount;
+  grpc_channel_args* last_used_results_ = nullptr;
+  // pending next completion, or NULL
+  grpc_closure* next_completion_ = nullptr;
+  // target result address for next completion
+  grpc_channel_args** target_result_ = nullptr;
 };
 
-grpc_fake_resolver_response_generator*
-grpc_fake_resolver_response_generator_create() {
-  grpc_fake_resolver_response_generator* generator =
-      (grpc_fake_resolver_response_generator*)gpr_zalloc(sizeof(*generator));
-  gpr_ref_init(&generator->refcount, 1);
-  return generator;
+FakeResolver::FakeResolver(const ResolverArgs& args) : Resolver(args.combiner) {
+  channel_args_ = grpc_channel_args_copy(args.args);
+  FakeResolverResponseGenerator* response_generator =
+      FakeResolverResponseGenerator::GetFromArgs(args.args);
+  if (response_generator != nullptr) response_generator->resolver_ = this;
 }
 
-grpc_fake_resolver_response_generator*
-grpc_fake_resolver_response_generator_ref(
-    grpc_fake_resolver_response_generator* generator) {
-  gpr_ref(&generator->refcount);
-  return generator;
+FakeResolver::~FakeResolver() {
+  grpc_channel_args_destroy(next_results_);
+  grpc_channel_args_destroy(reresolution_results_);
+  grpc_channel_args_destroy(last_used_results_);
+  grpc_channel_args_destroy(channel_args_);
 }
 
-void grpc_fake_resolver_response_generator_unref(
-    grpc_fake_resolver_response_generator* generator) {
-  if (gpr_unref(&generator->refcount)) {
-    gpr_free(generator);
-  }
+void FakeResolver::NextLocked(grpc_channel_args** target_result,
+                              grpc_closure* on_complete) {
+  GPR_ASSERT(next_completion_ == nullptr);
+  next_completion_ = on_complete;
+  target_result_ = target_result;
+  MaybeFinishNextLocked();
 }
 
-typedef struct set_response_closure_arg {
-  grpc_closure set_response_closure;
-  grpc_fake_resolver_response_generator* generator;
-  grpc_channel_args* response;
-  bool upon_error;
-} set_response_closure_arg;
-
-static void set_response_closure_locked(void* arg, grpc_error* error) {
-  set_response_closure_arg* closure_arg = (set_response_closure_arg*)arg;
-  grpc_fake_resolver_response_generator* generator = closure_arg->generator;
-  fake_resolver* r = generator->resolver;
-  if (!closure_arg->upon_error) {
-    grpc_channel_args_destroy(r->next_results);
-    r->next_results = closure_arg->response;
-    grpc_channel_args_destroy(r->last_used_results);
-    r->last_used_results = grpc_channel_args_copy(closure_arg->response);
-    fake_resolver_maybe_finish_next_locked(r);
+void FakeResolver::RequestReresolutionLocked() {
+  // A resolution must have been returned before an error is seen.
+  GPR_ASSERT(last_used_results_ != nullptr);
+  grpc_channel_args_destroy(next_results_);
+  if (reresolution_results_ != nullptr) {
+    next_results_ = grpc_channel_args_copy(reresolution_results_);
   } else {
-    grpc_channel_args_destroy(r->results_upon_error);
-    r->results_upon_error = closure_arg->response;
+    // If reresolution_results is unavailable, re-resolve with the most-recently
+    // used results to avoid a no-op re-resolution.
+    next_results_ = grpc_channel_args_copy(last_used_results_);
   }
-  gpr_free(closure_arg);
+  MaybeFinishNextLocked();
 }
 
-void grpc_fake_resolver_response_generator_set_response(
-    grpc_fake_resolver_response_generator* generator,
-    grpc_channel_args* response) {
-  GPR_ASSERT(generator->resolver != nullptr);
+void FakeResolver::MaybeFinishNextLocked() {
+  if (next_completion_ != nullptr && next_results_ != nullptr) {
+    *target_result_ = grpc_channel_args_union(next_results_, channel_args_);
+    grpc_channel_args_destroy(next_results_);
+    next_results_ = nullptr;
+    GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_NONE);
+    next_completion_ = nullptr;
+  }
+}
+
+void FakeResolver::ShutdownLocked() {
+  if (next_completion_ != nullptr) {
+    *target_result_ = nullptr;
+    GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                                             "Resolver Shutdown"));
+    next_completion_ = nullptr;
+  }
+}
+
+//
+// FakeResolverResponseGenerator
+//
+
+struct SetResponseClosureArg {
+  grpc_closure set_response_closure;
+  FakeResolverResponseGenerator* generator;
+  grpc_channel_args* response;
+};
+
+void FakeResolverResponseGenerator::SetResponseLocked(void* arg,
+                                                      grpc_error* error) {
+  SetResponseClosureArg* closure_arg = static_cast<SetResponseClosureArg*>(arg);
+  FakeResolver* resolver = closure_arg->generator->resolver_;
+  grpc_channel_args_destroy(resolver->next_results_);
+  resolver->next_results_ = closure_arg->response;
+  grpc_channel_args_destroy(resolver->last_used_results_);
+  resolver->last_used_results_ = grpc_channel_args_copy(closure_arg->response);
+  resolver->MaybeFinishNextLocked();
+  Delete(closure_arg);
+}
+
+void FakeResolverResponseGenerator::SetResponse(grpc_channel_args* response) {
   GPR_ASSERT(response != nullptr);
-  set_response_closure_arg* closure_arg =
-      (set_response_closure_arg*)gpr_zalloc(sizeof(*closure_arg));
-  closure_arg->generator = generator;
+  GPR_ASSERT(resolver_ != nullptr);
+  SetResponseClosureArg* closure_arg = New<SetResponseClosureArg>();
+  closure_arg->generator = this;
   closure_arg->response = grpc_channel_args_copy(response);
-  closure_arg->upon_error = false;
-  GRPC_CLOSURE_SCHED(GRPC_CLOSURE_INIT(&closure_arg->set_response_closure,
-                                       set_response_closure_locked, closure_arg,
-                                       grpc_combiner_scheduler(
-                                           generator->resolver->base.combiner)),
-                     GRPC_ERROR_NONE);
+  GRPC_CLOSURE_SCHED(
+      GRPC_CLOSURE_INIT(&closure_arg->set_response_closure, SetResponseLocked,
+                        closure_arg,
+                        grpc_combiner_scheduler(resolver_->combiner())),
+      GRPC_ERROR_NONE);
 }
 
-void grpc_fake_resolver_response_generator_set_response_upon_error(
-    grpc_fake_resolver_response_generator* generator,
+void FakeResolverResponseGenerator::SetReresolutionResponseLocked(
+    void* arg, grpc_error* error) {
+  SetResponseClosureArg* closure_arg = static_cast<SetResponseClosureArg*>(arg);
+  FakeResolver* resolver = closure_arg->generator->resolver_;
+  grpc_channel_args_destroy(resolver->reresolution_results_);
+  resolver->reresolution_results_ = closure_arg->response;
+  Delete(closure_arg);
+}
+
+void FakeResolverResponseGenerator::SetReresolutionResponse(
     grpc_channel_args* response) {
-  GPR_ASSERT(generator->resolver != nullptr);
-  set_response_closure_arg* closure_arg =
-      (set_response_closure_arg*)gpr_zalloc(sizeof(*closure_arg));
-  closure_arg->generator = generator;
+  GPR_ASSERT(resolver_ != nullptr);
+  SetResponseClosureArg* closure_arg = New<SetResponseClosureArg>();
+  closure_arg->generator = this;
   closure_arg->response =
       response != nullptr ? grpc_channel_args_copy(response) : nullptr;
-  closure_arg->upon_error = true;
-  GRPC_CLOSURE_SCHED(GRPC_CLOSURE_INIT(&closure_arg->set_response_closure,
-                                       set_response_closure_locked, closure_arg,
-                                       grpc_combiner_scheduler(
-                                           generator->resolver->base.combiner)),
-                     GRPC_ERROR_NONE);
+  GRPC_CLOSURE_SCHED(
+      GRPC_CLOSURE_INIT(&closure_arg->set_response_closure,
+                        SetReresolutionResponseLocked, closure_arg,
+                        grpc_combiner_scheduler(resolver_->combiner())),
+      GRPC_ERROR_NONE);
 }
 
+namespace {
+
 static void* response_generator_arg_copy(void* p) {
-  return grpc_fake_resolver_response_generator_ref(
-      (grpc_fake_resolver_response_generator*)p);
+  FakeResolverResponseGenerator* generator =
+      static_cast<FakeResolverResponseGenerator*>(p);
+  // TODO(roth): We currently deal with this ref manually.  Once the
+  // new channel args code is converted to C++, find a way to track this ref
+  // in a cleaner way.
+  RefCountedPtr<FakeResolverResponseGenerator> copy = generator->Ref();
+  copy.release();
+  return p;
 }
 
 static void response_generator_arg_destroy(void* p) {
-  grpc_fake_resolver_response_generator_unref(
-      (grpc_fake_resolver_response_generator*)p);
+  FakeResolverResponseGenerator* generator =
+      static_cast<FakeResolverResponseGenerator*>(p);
+  generator->Unref();
 }
 
 static int response_generator_cmp(void* a, void* b) { return GPR_ICMP(a, b); }
@@ -234,8 +221,10 @@
     response_generator_arg_copy, response_generator_arg_destroy,
     response_generator_cmp};
 
-grpc_arg grpc_fake_resolver_response_generator_arg(
-    grpc_fake_resolver_response_generator* generator) {
+}  // namespace
+
+grpc_arg FakeResolverResponseGenerator::MakeChannelArg(
+    FakeResolverResponseGenerator* generator) {
   grpc_arg arg;
   arg.type = GRPC_ARG_POINTER;
   arg.key = (char*)GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR;
@@ -244,49 +233,38 @@
   return arg;
 }
 
-grpc_fake_resolver_response_generator*
-grpc_fake_resolver_get_response_generator(const grpc_channel_args* args) {
+FakeResolverResponseGenerator* FakeResolverResponseGenerator::GetFromArgs(
+    const grpc_channel_args* args) {
   const grpc_arg* arg =
       grpc_channel_args_find(args, GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR);
   if (arg == nullptr || arg->type != GRPC_ARG_POINTER) return nullptr;
-  return (grpc_fake_resolver_response_generator*)arg->value.pointer.p;
+  return static_cast<FakeResolverResponseGenerator*>(arg->value.pointer.p);
 }
 
 //
-// fake_resolver_factory
+// Factory
 //
 
-static void fake_resolver_factory_ref(grpc_resolver_factory* factory) {}
+namespace {
 
-static void fake_resolver_factory_unref(grpc_resolver_factory* factory) {}
+class FakeResolverFactory : public ResolverFactory {
+ public:
+  OrphanablePtr<Resolver> CreateResolver(
+      const ResolverArgs& args) const override {
+    return OrphanablePtr<Resolver>(New<FakeResolver>(args));
+  }
 
-static grpc_resolver* fake_resolver_create(grpc_resolver_factory* factory,
-                                           grpc_resolver_args* args) {
-  fake_resolver* r = (fake_resolver*)gpr_zalloc(sizeof(*r));
-  r->channel_args = grpc_channel_args_copy(args->args);
-  grpc_resolver_init(&r->base, &fake_resolver_vtable, args->combiner);
-  grpc_fake_resolver_response_generator* response_generator =
-      grpc_fake_resolver_get_response_generator(args->args);
-  if (response_generator != nullptr) response_generator->resolver = r;
-  return &r->base;
+  const char* scheme() const override { return "fake"; }
+};
+
+}  // namespace
+
+}  // namespace grpc_core
+
+void grpc_resolver_fake_init() {
+  grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
+      grpc_core::UniquePtr<grpc_core::ResolverFactory>(
+          grpc_core::New<grpc_core::FakeResolverFactory>()));
 }
 
-static char* fake_resolver_get_default_authority(grpc_resolver_factory* factory,
-                                                 grpc_uri* uri) {
-  const char* path = uri->path;
-  if (path[0] == '/') ++path;
-  return gpr_strdup(path);
-}
-
-static const grpc_resolver_factory_vtable fake_resolver_factory_vtable = {
-    fake_resolver_factory_ref, fake_resolver_factory_unref,
-    fake_resolver_create, fake_resolver_get_default_authority, "fake"};
-
-static grpc_resolver_factory fake_resolver_factory = {
-    &fake_resolver_factory_vtable};
-
-void grpc_resolver_fake_init(void) {
-  grpc_register_resolver_type(&fake_resolver_factory);
-}
-
-void grpc_resolver_fake_shutdown(void) {}
+void grpc_resolver_fake_shutdown() {}
diff --git a/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h b/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h
index 94f9a8e..d42811d 100644
--- a/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h
+++ b/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h
@@ -20,50 +20,57 @@
 #include "src/core/ext/filters/client_channel/lb_policy_factory.h"
 #include "src/core/ext/filters/client_channel/uri_parser.h"
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gprpp/ref_counted.h"
 
 #define GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR \
   "grpc.fake_resolver.response_generator"
 
-void grpc_resolver_fake_init();
+namespace grpc_core {
 
-// Instances of \a grpc_fake_resolver_response_generator are passed to the
-// fake resolver in a channel argument (see \a
-// grpc_fake_resolver_response_generator_arg) in order to inject and trigger
-// custom resolutions. See also \a
-// grpc_fake_resolver_response_generator_set_response.
-typedef struct grpc_fake_resolver_response_generator
-    grpc_fake_resolver_response_generator;
-grpc_fake_resolver_response_generator*
-grpc_fake_resolver_response_generator_create();
+class FakeResolver;
 
-// Set next response of the fake resolver associated with the \a
-// response_generator instance and trigger a new resolution.
-void grpc_fake_resolver_response_generator_set_response(
-    grpc_fake_resolver_response_generator* generator,
-    grpc_channel_args* response);
+/// A mechanism for generating responses for the fake resolver.
+/// An instance of this class is passed to the fake resolver via a channel
+/// argument (see \a MakeChannelArg()) and used to inject and trigger custom
+/// resolutions.
+// TODO(roth): I would ideally like this to be InternallyRefCounted
+// instead of RefCounted, but external refs are currently needed to
+// encode this in channel args.  Once channel_args are converted to C++,
+// see if we can find a way to fix this.
+class FakeResolverResponseGenerator
+    : public RefCounted<FakeResolverResponseGenerator> {
+ public:
+  FakeResolverResponseGenerator() {}
 
-// Set results_upon_error of the fake resolver associated with the \a
-// response_generator instance. When fake_resolver_channel_saw_error_locked() is
-// called, results_upon_error will be returned as long as it's non-NULL,
-// otherwise the last value set by
-// grpc_fake_resolver_response_generator_set_response() will be returned.
-void grpc_fake_resolver_response_generator_set_response_upon_error(
-    grpc_fake_resolver_response_generator* generator,
-    grpc_channel_args* response);
+  // Instructs the fake resolver associated with the response generator
+  // instance to trigger a new resolution with the specified response.
+  void SetResponse(grpc_channel_args* next_response);
 
-// Return a \a grpc_arg for a \a grpc_fake_resolver_response_generator instance.
-grpc_arg grpc_fake_resolver_response_generator_arg(
-    grpc_fake_resolver_response_generator* generator);
-// Return the \a grpc_fake_resolver_response_generator instance in \a args or
-// NULL.
-grpc_fake_resolver_response_generator*
-grpc_fake_resolver_get_response_generator(const grpc_channel_args* args);
+  // Sets the re-resolution response, which is returned by the fake resolver
+  // when re-resolution is requested (via \a RequestReresolutionLocked()).
+  // The new re-resolution response replaces any previous re-resolution
+  // response that may have been set by a previous call.
+  // If the re-resolution response is set to NULL, then the fake
+  // resolver will return the last value set via \a SetResponse().
+  void SetReresolutionResponse(grpc_channel_args* response);
 
-grpc_fake_resolver_response_generator*
-grpc_fake_resolver_response_generator_ref(
-    grpc_fake_resolver_response_generator* generator);
-void grpc_fake_resolver_response_generator_unref(
-    grpc_fake_resolver_response_generator* generator);
+  // Returns a channel arg containing \a generator.
+  static grpc_arg MakeChannelArg(FakeResolverResponseGenerator* generator);
+
+  // Returns the response generator in \a args, or null if not found.
+  static FakeResolverResponseGenerator* GetFromArgs(
+      const grpc_channel_args* args);
+
+ private:
+  friend class FakeResolver;
+
+  static void SetResponseLocked(void* arg, grpc_error* error);
+  static void SetReresolutionResponseLocked(void* arg, grpc_error* error);
+
+  FakeResolver* resolver_ = nullptr;  // Do not own.
+};
+
+}  // namespace grpc_core
 
 #endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_FAKE_FAKE_RESOLVER_H \
         */
diff --git a/src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc b/src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc
index 784935e..966b9fd 100644
--- a/src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc
+++ b/src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc
@@ -37,115 +37,99 @@
 #include "src/core/lib/slice/slice_internal.h"
 #include "src/core/lib/slice/slice_string_helpers.h"
 
-typedef struct {
-  /** base class: must be first */
-  grpc_resolver base;
-  /** the addresses that we've 'resolved' */
-  grpc_lb_addresses* addresses;
-  /** channel args */
-  grpc_channel_args* channel_args;
-  /** have we published? */
-  bool published;
-  /** pending next completion, or NULL */
-  grpc_closure* next_completion;
-  /** target result address for next completion */
-  grpc_channel_args** target_result;
-} sockaddr_resolver;
+namespace grpc_core {
 
-static void sockaddr_destroy(grpc_resolver* r);
+namespace {
 
-static void sockaddr_maybe_finish_next_locked(sockaddr_resolver* r);
+class SockaddrResolver : public Resolver {
+ public:
+  /// Takes ownership of \a addresses.
+  SockaddrResolver(const ResolverArgs& args, grpc_lb_addresses* addresses);
 
-static void sockaddr_shutdown_locked(grpc_resolver* r);
-static void sockaddr_channel_saw_error_locked(grpc_resolver* r);
-static void sockaddr_next_locked(grpc_resolver* r,
-                                 grpc_channel_args** target_result,
-                                 grpc_closure* on_complete);
+  void NextLocked(grpc_channel_args** result,
+                  grpc_closure* on_complete) override;
 
-static const grpc_resolver_vtable sockaddr_resolver_vtable = {
-    sockaddr_destroy, sockaddr_shutdown_locked,
-    sockaddr_channel_saw_error_locked, sockaddr_next_locked};
+  void RequestReresolutionLocked() override;
 
-static void sockaddr_shutdown_locked(grpc_resolver* resolver) {
-  sockaddr_resolver* r = (sockaddr_resolver*)resolver;
-  if (r->next_completion != nullptr) {
-    *r->target_result = nullptr;
-    GRPC_CLOSURE_SCHED(r->next_completion, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
-                                               "Resolver Shutdown"));
-    r->next_completion = nullptr;
+  void ShutdownLocked() override;
+
+ private:
+  virtual ~SockaddrResolver();
+
+  void MaybeFinishNextLocked();
+
+  /// the addresses that we've "resolved"
+  grpc_lb_addresses* addresses_ = nullptr;
+  /// channel args
+  grpc_channel_args* channel_args_ = nullptr;
+  /// have we published?
+  bool published_ = false;
+  /// pending next completion, or NULL
+  grpc_closure* next_completion_ = nullptr;
+  /// target result address for next completion
+  grpc_channel_args** target_result_ = nullptr;
+};
+
+SockaddrResolver::SockaddrResolver(const ResolverArgs& args,
+                                   grpc_lb_addresses* addresses)
+    : Resolver(args.combiner),
+      addresses_(addresses),
+      channel_args_(grpc_channel_args_copy(args.args)) {}
+
+SockaddrResolver::~SockaddrResolver() {
+  grpc_lb_addresses_destroy(addresses_);
+  grpc_channel_args_destroy(channel_args_);
+}
+
+void SockaddrResolver::NextLocked(grpc_channel_args** target_result,
+                                  grpc_closure* on_complete) {
+  GPR_ASSERT(!next_completion_);
+  next_completion_ = on_complete;
+  target_result_ = target_result;
+  MaybeFinishNextLocked();
+}
+
+void SockaddrResolver::RequestReresolutionLocked() {
+  published_ = false;
+  MaybeFinishNextLocked();
+}
+
+void SockaddrResolver::ShutdownLocked() {
+  if (next_completion_ != nullptr) {
+    *target_result_ = nullptr;
+    GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+                                             "Resolver Shutdown"));
+    next_completion_ = nullptr;
   }
 }
 
-static void sockaddr_channel_saw_error_locked(grpc_resolver* resolver) {
-  sockaddr_resolver* r = (sockaddr_resolver*)resolver;
-  r->published = false;
-  sockaddr_maybe_finish_next_locked(r);
-}
-
-static void sockaddr_next_locked(grpc_resolver* resolver,
-                                 grpc_channel_args** target_result,
-                                 grpc_closure* on_complete) {
-  sockaddr_resolver* r = (sockaddr_resolver*)resolver;
-  GPR_ASSERT(!r->next_completion);
-  r->next_completion = on_complete;
-  r->target_result = target_result;
-  sockaddr_maybe_finish_next_locked(r);
-}
-
-static void sockaddr_maybe_finish_next_locked(sockaddr_resolver* r) {
-  if (r->next_completion != nullptr && !r->published) {
-    r->published = true;
-    grpc_arg arg = grpc_lb_addresses_create_channel_arg(r->addresses);
-    *r->target_result =
-        grpc_channel_args_copy_and_add(r->channel_args, &arg, 1);
-    GRPC_CLOSURE_SCHED(r->next_completion, GRPC_ERROR_NONE);
-    r->next_completion = nullptr;
+void SockaddrResolver::MaybeFinishNextLocked() {
+  if (next_completion_ != nullptr && !published_) {
+    published_ = true;
+    grpc_arg arg = grpc_lb_addresses_create_channel_arg(addresses_);
+    *target_result_ = grpc_channel_args_copy_and_add(channel_args_, &arg, 1);
+    GRPC_CLOSURE_SCHED(next_completion_, GRPC_ERROR_NONE);
+    next_completion_ = nullptr;
   }
 }
 
-static void sockaddr_destroy(grpc_resolver* gr) {
-  sockaddr_resolver* r = (sockaddr_resolver*)gr;
-  grpc_lb_addresses_destroy(r->addresses);
-  grpc_channel_args_destroy(r->channel_args);
-  gpr_free(r);
-}
+//
+// Factory
+//
 
-static char* ip_get_default_authority(grpc_uri* uri) {
-  const char* path = uri->path;
-  if (path[0] == '/') ++path;
-  return gpr_strdup(path);
-}
+void DoNothing(void* ignored) {}
 
-static char* ipv4_get_default_authority(grpc_resolver_factory* factory,
-                                        grpc_uri* uri) {
-  return ip_get_default_authority(uri);
-}
-
-static char* ipv6_get_default_authority(grpc_resolver_factory* factory,
-                                        grpc_uri* uri) {
-  return ip_get_default_authority(uri);
-}
-
-#ifdef GRPC_HAVE_UNIX_SOCKET
-char* unix_get_default_authority(grpc_resolver_factory* factory,
-                                 grpc_uri* uri) {
-  return gpr_strdup("localhost");
-}
-#endif
-
-static void do_nothing(void* ignored) {}
-
-static grpc_resolver* sockaddr_create(grpc_resolver_args* args,
-                                      bool parse(const grpc_uri* uri,
-                                                 grpc_resolved_address* dst)) {
-  if (0 != strcmp(args->uri->authority, "")) {
-    gpr_log(GPR_ERROR, "authority based uri's not supported by the %s scheme",
-            args->uri->scheme);
-    return nullptr;
+OrphanablePtr<Resolver> CreateSockaddrResolver(
+    const ResolverArgs& args,
+    bool parse(const grpc_uri* uri, grpc_resolved_address* dst)) {
+  if (0 != strcmp(args.uri->authority, "")) {
+    gpr_log(GPR_ERROR, "authority-based URIs not supported by the %s scheme",
+            args.uri->scheme);
+    return OrphanablePtr<Resolver>(nullptr);
   }
-  /* Construct addresses. */
+  // Construct addresses.
   grpc_slice path_slice =
-      grpc_slice_new(args->uri->path, strlen(args->uri->path), do_nothing);
+      grpc_slice_new(args.uri->path, strlen(args.uri->path), DoNothing);
   grpc_slice_buffer path_parts;
   grpc_slice_buffer_init(&path_parts);
   grpc_slice_split(path_slice, ",", &path_parts);
@@ -153,7 +137,7 @@
       path_parts.count, nullptr /* user_data_vtable */);
   bool errors_found = false;
   for (size_t i = 0; i < addresses->num_addresses; i++) {
-    grpc_uri ith_uri = *args->uri;
+    grpc_uri ith_uri = *args.uri;
     char* part_str = grpc_slice_to_c_string(path_parts.slices[i]);
     ith_uri.path = part_str;
     if (!parse(&ith_uri, &addresses->addresses[i].address)) {
@@ -166,48 +150,64 @@
   grpc_slice_unref_internal(path_slice);
   if (errors_found) {
     grpc_lb_addresses_destroy(addresses);
-    return nullptr;
+    return OrphanablePtr<Resolver>(nullptr);
   }
-  /* Instantiate resolver. */
-  sockaddr_resolver* r =
-      (sockaddr_resolver*)gpr_zalloc(sizeof(sockaddr_resolver));
-  r->addresses = addresses;
-  r->channel_args = grpc_channel_args_copy(args->args);
-  grpc_resolver_init(&r->base, &sockaddr_resolver_vtable, args->combiner);
-  return &r->base;
+  // Instantiate resolver.
+  return OrphanablePtr<Resolver>(New<SockaddrResolver>(args, addresses));
 }
 
-/*
- * FACTORY
- */
+class IPv4ResolverFactory : public ResolverFactory {
+ public:
+  OrphanablePtr<Resolver> CreateResolver(
+      const ResolverArgs& args) const override {
+    return CreateSockaddrResolver(args, grpc_parse_ipv4);
+  }
 
-static void sockaddr_factory_ref(grpc_resolver_factory* factory) {}
+  const char* scheme() const override { return "ipv4"; }
+};
 
-static void sockaddr_factory_unref(grpc_resolver_factory* factory) {}
+class IPv6ResolverFactory : public ResolverFactory {
+ public:
+  OrphanablePtr<Resolver> CreateResolver(
+      const ResolverArgs& args) const override {
+    return CreateSockaddrResolver(args, grpc_parse_ipv6);
+  }
 
-#define DECL_FACTORY(name)                                                  \
-  static grpc_resolver* name##_factory_create_resolver(                     \
-      grpc_resolver_factory* factory, grpc_resolver_args* args) {           \
-    return sockaddr_create(args, grpc_parse_##name);                        \
-  }                                                                         \
-  static const grpc_resolver_factory_vtable name##_factory_vtable = {       \
-      sockaddr_factory_ref, sockaddr_factory_unref,                         \
-      name##_factory_create_resolver, name##_get_default_authority, #name}; \
-  static grpc_resolver_factory name##_resolver_factory = {                  \
-      &name##_factory_vtable}
+  const char* scheme() const override { return "ipv6"; }
+};
 
 #ifdef GRPC_HAVE_UNIX_SOCKET
-DECL_FACTORY(unix);
-#endif
-DECL_FACTORY(ipv4);
-DECL_FACTORY(ipv6);
+class UnixResolverFactory : public ResolverFactory {
+ public:
+  OrphanablePtr<Resolver> CreateResolver(
+      const ResolverArgs& args) const override {
+    return CreateSockaddrResolver(args, grpc_parse_unix);
+  }
 
-void grpc_resolver_sockaddr_init(void) {
-  grpc_register_resolver_type(&ipv4_resolver_factory);
-  grpc_register_resolver_type(&ipv6_resolver_factory);
+  UniquePtr<char> GetDefaultAuthority(grpc_uri* uri) const override {
+    return UniquePtr<char>(gpr_strdup("localhost"));
+  }
+
+  const char* scheme() const override { return "unix"; }
+};
+#endif  // GRPC_HAVE_UNIX_SOCKET
+
+}  // namespace
+
+}  // namespace grpc_core
+
+void grpc_resolver_sockaddr_init() {
+  grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
+      grpc_core::UniquePtr<grpc_core::ResolverFactory>(
+          grpc_core::New<grpc_core::IPv4ResolverFactory>()));
+  grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
+      grpc_core::UniquePtr<grpc_core::ResolverFactory>(
+          grpc_core::New<grpc_core::IPv6ResolverFactory>()));
 #ifdef GRPC_HAVE_UNIX_SOCKET
-  grpc_register_resolver_type(&unix_resolver_factory);
+  grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
+      grpc_core::UniquePtr<grpc_core::ResolverFactory>(
+          grpc_core::New<grpc_core::UnixResolverFactory>()));
 #endif
 }
 
-void grpc_resolver_sockaddr_shutdown(void) {}
+void grpc_resolver_sockaddr_shutdown() {}
diff --git a/src/core/ext/filters/client_channel/resolver_factory.cc b/src/core/ext/filters/client_channel/resolver_factory.cc
deleted file mode 100644
index 9b3ec2f..0000000
--- a/src/core/ext/filters/client_channel/resolver_factory.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- *
- * Copyright 2015 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.
- *
- */
-
-#include "src/core/ext/filters/client_channel/resolver_factory.h"
-
-void grpc_resolver_factory_ref(grpc_resolver_factory* factory) {
-  factory->vtable->ref(factory);
-}
-
-void grpc_resolver_factory_unref(grpc_resolver_factory* factory) {
-  factory->vtable->unref(factory);
-}
-
-/** Create a resolver instance for a name */
-grpc_resolver* grpc_resolver_factory_create_resolver(
-    grpc_resolver_factory* factory, grpc_resolver_args* args) {
-  if (factory == nullptr) return nullptr;
-  return factory->vtable->create_resolver(factory, args);
-}
-
-char* grpc_resolver_factory_get_default_authority(
-    grpc_resolver_factory* factory, grpc_uri* uri) {
-  if (factory == nullptr) return nullptr;
-  return factory->vtable->get_default_authority(factory, uri);
-}
diff --git a/src/core/ext/filters/client_channel/resolver_factory.h b/src/core/ext/filters/client_channel/resolver_factory.h
index 170ecc0..f9b9501 100644
--- a/src/core/ext/filters/client_channel/resolver_factory.h
+++ b/src/core/ext/filters/client_channel/resolver_factory.h
@@ -19,50 +19,51 @@
 #ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_FACTORY_H
 #define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_FACTORY_H
 
-#include "src/core/ext/filters/client_channel/client_channel_factory.h"
+#include <grpc/support/string_util.h>
+
 #include "src/core/ext/filters/client_channel/resolver.h"
 #include "src/core/ext/filters/client_channel/uri_parser.h"
+#include "src/core/lib/gprpp/abstract.h"
+#include "src/core/lib/gprpp/memory.h"
+#include "src/core/lib/gprpp/orphanable.h"
 #include "src/core/lib/iomgr/pollset_set.h"
 
-typedef struct grpc_resolver_factory grpc_resolver_factory;
-typedef struct grpc_resolver_factory_vtable grpc_resolver_factory_vtable;
+namespace grpc_core {
 
-struct grpc_resolver_factory {
-  const grpc_resolver_factory_vtable* vtable;
+struct ResolverArgs {
+  /// The parsed URI to resolve.
+  grpc_uri* uri = nullptr;
+  /// Channel args to be included in resolver results.
+  const grpc_channel_args* args = nullptr;
+  /// Used to drive I/O in the name resolution process.
+  grpc_pollset_set* pollset_set = nullptr;
+  /// The combiner under which all resolver calls will be run.
+  grpc_combiner* combiner = nullptr;
 };
 
-typedef struct grpc_resolver_args {
-  grpc_uri* uri;
-  const grpc_channel_args* args;
-  grpc_pollset_set* pollset_set;
-  grpc_combiner* combiner;
-} grpc_resolver_args;
+class ResolverFactory {
+ public:
+  /// Returns a new resolver instance.
+  virtual OrphanablePtr<Resolver> CreateResolver(const ResolverArgs& args) const
+      GRPC_ABSTRACT;
 
-struct grpc_resolver_factory_vtable {
-  void (*ref)(grpc_resolver_factory* factory);
-  void (*unref)(grpc_resolver_factory* factory);
+  /// Returns a string representing the default authority to use for this
+  /// scheme.
+  virtual UniquePtr<char> GetDefaultAuthority(grpc_uri* uri) const {
+    const char* path = uri->path;
+    if (path[0] == '/') ++path;
+    return UniquePtr<char>(gpr_strdup(path));
+  }
 
-  /** Implementation of grpc_resolver_factory_create_resolver */
-  grpc_resolver* (*create_resolver)(grpc_resolver_factory* factory,
-                                    grpc_resolver_args* args);
+  /// Returns the URI scheme that this factory implements.
+  /// Caller does NOT take ownership of result.
+  virtual const char* scheme() const GRPC_ABSTRACT;
 
-  /** Implementation of grpc_resolver_factory_get_default_authority */
-  char* (*get_default_authority)(grpc_resolver_factory* factory, grpc_uri* uri);
+  virtual ~ResolverFactory() {}
 
-  /** URI scheme that this factory implements */
-  const char* scheme;
+  GRPC_ABSTRACT_BASE_CLASS
 };
 
-void grpc_resolver_factory_ref(grpc_resolver_factory* resolver);
-void grpc_resolver_factory_unref(grpc_resolver_factory* resolver);
-
-/** Create a resolver instance for a name */
-grpc_resolver* grpc_resolver_factory_create_resolver(
-    grpc_resolver_factory* factory, grpc_resolver_args* args);
-
-/** Return a (freshly allocated with gpr_malloc) string representing
-    the default authority to use for this scheme. */
-char* grpc_resolver_factory_get_default_authority(
-    grpc_resolver_factory* factory, grpc_uri* uri);
+}  // namespace grpc_core
 
 #endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_FACTORY_H */
diff --git a/src/core/ext/filters/client_channel/resolver_registry.cc b/src/core/ext/filters/client_channel/resolver_registry.cc
index 3f8451d..036e81d 100644
--- a/src/core/ext/filters/client_channel/resolver_registry.cc
+++ b/src/core/ext/filters/client_channel/resolver_registry.cc
@@ -24,133 +24,153 @@
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 
-#define MAX_RESOLVERS 10
-#define DEFAULT_RESOLVER_PREFIX_MAX_LENGTH 32
+namespace grpc_core {
 
-static grpc_resolver_factory* g_all_of_the_resolvers[MAX_RESOLVERS];
-static int g_number_of_resolvers = 0;
+namespace {
 
-static char g_default_resolver_prefix[DEFAULT_RESOLVER_PREFIX_MAX_LENGTH] =
-    "dns:///";
+class RegistryState {
+ public:
+  RegistryState() : default_prefix_(gpr_strdup("dns:///")) {}
 
-void grpc_resolver_registry_init() {}
-
-void grpc_resolver_registry_shutdown(void) {
-  for (int i = 0; i < g_number_of_resolvers; i++) {
-    grpc_resolver_factory_unref(g_all_of_the_resolvers[i]);
+  void SetDefaultPrefix(const char* default_resolver_prefix) {
+    GPR_ASSERT(default_resolver_prefix != nullptr);
+    GPR_ASSERT(*default_resolver_prefix != '\0');
+    default_prefix_.reset(gpr_strdup(default_resolver_prefix));
   }
-  // FIXME(ctiller): this should live in grpc_resolver_registry_init,
-  // however that would have the client_channel plugin call this AFTER we start
-  // registering resolvers from third party plugins, and so they'd never show
-  // up.
-  // We likely need some kind of dependency system for plugins.... what form
-  // that takes is TBD.
-  g_number_of_resolvers = 0;
-}
 
-void grpc_resolver_registry_set_default_prefix(
-    const char* default_resolver_prefix) {
-  const size_t len = strlen(default_resolver_prefix);
-  GPR_ASSERT(len < DEFAULT_RESOLVER_PREFIX_MAX_LENGTH &&
-             "default resolver prefix too long");
-  GPR_ASSERT(len > 0 && "default resolver prefix can't be empty");
-  // By the previous assert, default_resolver_prefix is safe to be copied with a
-  // plain strcpy.
-  strcpy(g_default_resolver_prefix, default_resolver_prefix);
-}
-
-void grpc_register_resolver_type(grpc_resolver_factory* factory) {
-  int i;
-  for (i = 0; i < g_number_of_resolvers; i++) {
-    GPR_ASSERT(0 != strcmp(factory->vtable->scheme,
-                           g_all_of_the_resolvers[i]->vtable->scheme));
-  }
-  GPR_ASSERT(g_number_of_resolvers != MAX_RESOLVERS);
-  grpc_resolver_factory_ref(factory);
-  g_all_of_the_resolvers[g_number_of_resolvers++] = factory;
-}
-
-static grpc_resolver_factory* lookup_factory(const char* name) {
-  int i;
-
-  for (i = 0; i < g_number_of_resolvers; i++) {
-    if (0 == strcmp(name, g_all_of_the_resolvers[i]->vtable->scheme)) {
-      return g_all_of_the_resolvers[i];
+  void RegisterResolverFactory(UniquePtr<ResolverFactory> factory) {
+    for (size_t i = 0; i < factories_.size(); ++i) {
+      GPR_ASSERT(strcmp(factories_[i]->scheme(), factory->scheme()) != 0);
     }
+    factories_.push_back(std::move(factory));
   }
-  return nullptr;
-}
 
-grpc_resolver_factory* grpc_resolver_factory_lookup(const char* name) {
-  grpc_resolver_factory* f = lookup_factory(name);
-  if (f) grpc_resolver_factory_ref(f);
-  return f;
-}
+  ResolverFactory* LookupResolverFactory(const char* scheme) const {
+    for (size_t i = 0; i < factories_.size(); ++i) {
+      if (strcmp(scheme, factories_[i]->scheme()) == 0) {
+        return factories_[i].get();
+      }
+    }
+    return nullptr;
+  }
 
-static grpc_resolver_factory* lookup_factory_by_uri(grpc_uri* uri) {
-  if (!uri) return nullptr;
-  return lookup_factory(uri->scheme);
-}
-
-static grpc_resolver_factory* resolve_factory(const char* target,
-                                              grpc_uri** uri,
-                                              char** canonical_target) {
-  grpc_resolver_factory* factory = nullptr;
-
-  GPR_ASSERT(uri != nullptr);
-  *uri = grpc_uri_parse(target, 1);
-  factory = lookup_factory_by_uri(*uri);
-  if (factory == nullptr) {
-    grpc_uri_destroy(*uri);
-    gpr_asprintf(canonical_target, "%s%s", g_default_resolver_prefix, target);
-    *uri = grpc_uri_parse(*canonical_target, 1);
-    factory = lookup_factory_by_uri(*uri);
+  // Returns the factory for the scheme of \a target.  If \a target does
+  // not parse as a URI, prepends \a default_prefix_ and tries again.
+  // If URI parsing is successful (in either attempt), sets \a uri to
+  // point to the parsed URI.
+  // If \a default_prefix_ needs to be prepended, sets \a canonical_target
+  // to the canonical target string.
+  ResolverFactory* FindResolverFactory(const char* target, grpc_uri** uri,
+                                       char** canonical_target) const {
+    GPR_ASSERT(uri != nullptr);
+    *uri = grpc_uri_parse(target, 1);
+    ResolverFactory* factory =
+        *uri == nullptr ? nullptr : LookupResolverFactory((*uri)->scheme);
     if (factory == nullptr) {
-      grpc_uri_destroy(grpc_uri_parse(target, 0));
-      grpc_uri_destroy(grpc_uri_parse(*canonical_target, 0));
-      gpr_log(GPR_ERROR, "don't know how to resolve '%s' or '%s'", target,
-              *canonical_target);
+      grpc_uri_destroy(*uri);
+      gpr_asprintf(canonical_target, "%s%s", default_prefix_.get(), target);
+      *uri = grpc_uri_parse(*canonical_target, 1);
+      factory =
+          *uri == nullptr ? nullptr : LookupResolverFactory((*uri)->scheme);
+      if (factory == nullptr) {
+        grpc_uri_destroy(grpc_uri_parse(target, 0));
+        grpc_uri_destroy(grpc_uri_parse(*canonical_target, 0));
+        gpr_log(GPR_ERROR, "don't know how to resolve '%s' or '%s'", target,
+                *canonical_target);
+      }
     }
+    return factory;
   }
-  return factory;
+
+ private:
+  // We currently support 10 factories without doing additional
+  // allocation.  This number could be raised if there is a case where
+  // more factories are needed and the additional allocations are
+  // hurting performance (which is unlikely, since these allocations
+  // only occur at gRPC initialization time).
+  InlinedVector<UniquePtr<ResolverFactory>, 10> factories_;
+  UniquePtr<char> default_prefix_;
+};
+
+static RegistryState* g_state = nullptr;
+
+}  // namespace
+
+//
+// ResolverRegistry::Builder
+//
+
+void ResolverRegistry::Builder::InitRegistry() {
+  if (g_state == nullptr) g_state = New<RegistryState>();
 }
 
-grpc_resolver* grpc_resolver_create(const char* target,
-                                    const grpc_channel_args* args,
-                                    grpc_pollset_set* pollset_set,
-                                    grpc_combiner* combiner) {
+void ResolverRegistry::Builder::ShutdownRegistry() {
+  Delete(g_state);
+  g_state = nullptr;
+}
+
+void ResolverRegistry::Builder::SetDefaultPrefix(
+    const char* default_resolver_prefix) {
+  InitRegistry();
+  g_state->SetDefaultPrefix(default_resolver_prefix);
+}
+
+void ResolverRegistry::Builder::RegisterResolverFactory(
+    UniquePtr<ResolverFactory> factory) {
+  InitRegistry();
+  g_state->RegisterResolverFactory(std::move(factory));
+}
+
+//
+// ResolverRegistry
+//
+
+ResolverFactory* ResolverRegistry::LookupResolverFactory(const char* scheme) {
+  GPR_ASSERT(g_state != nullptr);
+  return g_state->LookupResolverFactory(scheme);
+}
+
+OrphanablePtr<Resolver> ResolverRegistry::CreateResolver(
+    const char* target, const grpc_channel_args* args,
+    grpc_pollset_set* pollset_set, grpc_combiner* combiner) {
+  GPR_ASSERT(g_state != nullptr);
   grpc_uri* uri = nullptr;
   char* canonical_target = nullptr;
-  grpc_resolver_factory* factory =
-      resolve_factory(target, &uri, &canonical_target);
-  grpc_resolver* resolver;
-  grpc_resolver_args resolver_args;
-  memset(&resolver_args, 0, sizeof(resolver_args));
+  ResolverFactory* factory =
+      g_state->FindResolverFactory(target, &uri, &canonical_target);
+  ResolverArgs resolver_args;
   resolver_args.uri = uri;
   resolver_args.args = args;
   resolver_args.pollset_set = pollset_set;
   resolver_args.combiner = combiner;
-  resolver = grpc_resolver_factory_create_resolver(factory, &resolver_args);
+  OrphanablePtr<Resolver> resolver =
+      factory == nullptr ? nullptr : factory->CreateResolver(resolver_args);
   grpc_uri_destroy(uri);
   gpr_free(canonical_target);
   return resolver;
 }
 
-char* grpc_get_default_authority(const char* target) {
+UniquePtr<char> ResolverRegistry::GetDefaultAuthority(const char* target) {
+  GPR_ASSERT(g_state != nullptr);
   grpc_uri* uri = nullptr;
   char* canonical_target = nullptr;
-  grpc_resolver_factory* factory =
-      resolve_factory(target, &uri, &canonical_target);
-  char* authority = grpc_resolver_factory_get_default_authority(factory, uri);
+  ResolverFactory* factory =
+      g_state->FindResolverFactory(target, &uri, &canonical_target);
+  UniquePtr<char> authority =
+      factory == nullptr ? nullptr : factory->GetDefaultAuthority(uri);
   grpc_uri_destroy(uri);
   gpr_free(canonical_target);
   return authority;
 }
 
-char* grpc_resolver_factory_add_default_prefix_if_needed(const char* target) {
+UniquePtr<char> ResolverRegistry::AddDefaultPrefixIfNeeded(const char* target) {
+  GPR_ASSERT(g_state != nullptr);
   grpc_uri* uri = nullptr;
   char* canonical_target = nullptr;
-  resolve_factory(target, &uri, &canonical_target);
+  g_state->FindResolverFactory(target, &uri, &canonical_target);
   grpc_uri_destroy(uri);
-  return canonical_target == nullptr ? gpr_strdup(target) : canonical_target;
+  return UniquePtr<char>(canonical_target == nullptr ? gpr_strdup(target)
+                                                     : canonical_target);
 }
+
+}  // namespace grpc_core
diff --git a/src/core/ext/filters/client_channel/resolver_registry.h b/src/core/ext/filters/client_channel/resolver_registry.h
index bbd30df..260336d 100644
--- a/src/core/ext/filters/client_channel/resolver_registry.h
+++ b/src/core/ext/filters/client_channel/resolver_registry.h
@@ -20,49 +20,62 @@
 #define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_REGISTRY_H
 
 #include "src/core/ext/filters/client_channel/resolver_factory.h"
+#include "src/core/lib/gprpp/inlined_vector.h"
+#include "src/core/lib/gprpp/memory.h"
+#include "src/core/lib/gprpp/orphanable.h"
 #include "src/core/lib/iomgr/pollset_set.h"
 
-void grpc_resolver_registry_init();
-void grpc_resolver_registry_shutdown(void);
+namespace grpc_core {
 
-/** Set the default URI prefix to \a default_prefix. */
-void grpc_resolver_registry_set_default_prefix(const char* default_prefix);
+class ResolverRegistry {
+ public:
+  /// Methods used to create and populate the ResolverRegistry.
+  /// NOT THREAD SAFE -- to be used only during global gRPC
+  /// initialization and shutdown.
+  class Builder {
+   public:
+    /// Global initialization and shutdown hooks.
+    static void InitRegistry();
+    static void ShutdownRegistry();
 
-/** Register a resolver type.
-    URI's of \a scheme will be resolved with the given resolver.
-    If \a priority is greater than zero, then the resolver will be eligible
-    to resolve names that are passed in with no scheme. Higher priority
-    resolvers will be tried before lower priority schemes. */
-void grpc_register_resolver_type(grpc_resolver_factory* factory);
+    /// Sets the default URI prefix to \a default_prefix.
+    /// Calls InitRegistry() if it has not already been called.
+    static void SetDefaultPrefix(const char* default_prefix);
 
-/** Create a resolver given \a target.
-    First tries to parse \a target as a URI. If this succeeds, tries
-    to locate a registered resolver factory based on the URI scheme.
-    If parsing or location fails, prefixes default_prefix from
-    grpc_resolver_registry_init to target, and tries again (if default_prefix
-    was not NULL).
-    If a resolver factory was found, use it to instantiate a resolver and
-    return it.
-    If a resolver factory was not found, return NULL.
-    \a args is a set of channel arguments to be included in the result
-    (typically the set of arguments passed in from the client API).
-    \a pollset_set is used to drive IO in the name resolution process, it
-    should not be NULL. */
-grpc_resolver* grpc_resolver_create(const char* target,
-                                    const grpc_channel_args* args,
-                                    grpc_pollset_set* pollset_set,
-                                    grpc_combiner* combiner);
+    /// Registers a resolver factory.  The factory will be used to create a
+    /// resolver for any URI whose scheme matches that of the factory.
+    /// Calls InitRegistry() if it has not already been called.
+    static void RegisterResolverFactory(UniquePtr<ResolverFactory> factory);
+  };
 
-/** Find a resolver factory given a name and return an (owned-by-the-caller)
- *  reference to it */
-grpc_resolver_factory* grpc_resolver_factory_lookup(const char* name);
+  /// Creates a resolver given \a target.
+  /// First tries to parse \a target as a URI. If this succeeds, tries
+  /// to locate a registered resolver factory based on the URI scheme.
+  /// If parsing fails or there is no factory for the URI's scheme,
+  /// prepends default_prefix to target and tries again.
+  /// If a resolver factory is found, uses it to instantiate a resolver and
+  /// returns it; otherwise, returns nullptr.
+  /// \a args, \a pollset_set, and \a combiner are passed to the factory's
+  /// \a CreateResolver() method.
+  /// \a args are the channel args to be included in resolver results.
+  /// \a pollset_set is used to drive I/O in the name resolution process.
+  /// \a combiner is the combiner under which all resolver calls will be run.
+  static OrphanablePtr<Resolver> CreateResolver(const char* target,
+                                                const grpc_channel_args* args,
+                                                grpc_pollset_set* pollset_set,
+                                                grpc_combiner* combiner);
 
-/** Given a target, return a (freshly allocated with gpr_malloc) string
-    representing the default authority to pass from a client. */
-char* grpc_get_default_authority(const char* target);
+  /// Returns the default authority to pass from a client for \a target.
+  static UniquePtr<char> GetDefaultAuthority(const char* target);
 
-/** Returns a newly allocated string containing \a target, adding the
-    default prefix if needed. */
-char* grpc_resolver_factory_add_default_prefix_if_needed(const char* target);
+  /// Returns \a target with the default prefix prepended, if needed.
+  static UniquePtr<char> AddDefaultPrefixIfNeeded(const char* target);
+
+  /// Returns the resolver factory for \a scheme.
+  /// Caller does NOT own the return value.
+  static ResolverFactory* LookupResolverFactory(const char* scheme);
+};
+
+}  // namespace grpc_core
 
 #endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVER_REGISTRY_H */
diff --git a/src/core/ext/filters/client_channel/subchannel_index.cc b/src/core/ext/filters/client_channel/subchannel_index.cc
index 42647ea..69dbdcc 100644
--- a/src/core/ext/filters/client_channel/subchannel_index.cc
+++ b/src/core/ext/filters/client_channel/subchannel_index.cc
@@ -23,10 +23,10 @@
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/string_util.h>
-#include <grpc/support/tls.h>
 
 #include "src/core/lib/avl/avl.h"
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gpr/tls.h"
 
 // a map of subchannel_key --> subchannel, used for detecting connections
 // to the same destination in order to share them
diff --git a/src/core/ext/transport/chttp2/client/insecure/channel_create.cc b/src/core/ext/transport/chttp2/client/insecure/channel_create.cc
index 6a1b709..ef1d3fb 100644
--- a/src/core/ext/transport/chttp2/client/insecure/channel_create.cc
+++ b/src/core/ext/transport/chttp2/client/insecure/channel_create.cc
@@ -52,13 +52,13 @@
     return nullptr;
   }
   // Add channel arg containing the server URI.
-  grpc_arg arg = grpc_channel_arg_string_create(
-      (char*)GRPC_ARG_SERVER_URI,
-      grpc_resolver_factory_add_default_prefix_if_needed(target));
+  grpc_core::UniquePtr<char> canonical_target =
+      grpc_core::ResolverRegistry::AddDefaultPrefixIfNeeded(target);
+  grpc_arg arg = grpc_channel_arg_string_create((char*)GRPC_ARG_SERVER_URI,
+                                                canonical_target.get());
   const char* to_remove[] = {GRPC_ARG_SERVER_URI};
   grpc_channel_args* new_args =
       grpc_channel_args_copy_and_add_and_remove(args, to_remove, 1, &arg, 1);
-  gpr_free(arg.value.string);
   grpc_channel* channel =
       grpc_channel_create(target, new_args, GRPC_CLIENT_CHANNEL, nullptr);
   grpc_channel_args_destroy(new_args);
diff --git a/src/core/ext/transport/chttp2/client/secure/secure_channel_create.cc b/src/core/ext/transport/chttp2/client/secure/secure_channel_create.cc
index 27c5b96..d39c362 100644
--- a/src/core/ext/transport/chttp2/client/secure/secure_channel_create.cc
+++ b/src/core/ext/transport/chttp2/client/secure/secure_channel_create.cc
@@ -161,13 +161,13 @@
     return nullptr;
   }
   // Add channel arg containing the server URI.
-  grpc_arg arg = grpc_channel_arg_string_create(
-      (char*)GRPC_ARG_SERVER_URI,
-      grpc_resolver_factory_add_default_prefix_if_needed(target));
+  grpc_core::UniquePtr<char> canonical_target =
+      grpc_core::ResolverRegistry::AddDefaultPrefixIfNeeded(target);
+  grpc_arg arg = grpc_channel_arg_string_create((char*)GRPC_ARG_SERVER_URI,
+                                                canonical_target.get());
   const char* to_remove[] = {GRPC_ARG_SERVER_URI};
   grpc_channel_args* new_args =
       grpc_channel_args_copy_and_add_and_remove(args, to_remove, 1, &arg, 1);
-  gpr_free(arg.value.string);
   grpc_channel* channel =
       grpc_channel_create(target, new_args, GRPC_CLIENT_CHANNEL, nullptr);
   grpc_channel_args_destroy(new_args);
diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
index bc1762d..7c35faf 100644
--- a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
+++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc
@@ -1667,6 +1667,7 @@
   if (error == GRPC_ERROR_NONE) {
     grpc_chttp2_initiate_write(t, GRPC_CHTTP2_INITIATE_WRITE_RETRY_SEND_PING);
   }
+  GRPC_CHTTP2_UNREF_TRANSPORT(t, "retry_initiate_ping_locked");
 }
 
 void grpc_chttp2_ack_ping(grpc_chttp2_transport* t, uint64_t id) {
diff --git a/src/core/ext/transport/chttp2/transport/writing.cc b/src/core/ext/transport/chttp2/transport/writing.cc
index 95358ab..1d6c9b0 100644
--- a/src/core/ext/transport/chttp2/transport/writing.cc
+++ b/src/core/ext/transport/chttp2/transport/writing.cc
@@ -88,6 +88,7 @@
     }
     if (!t->ping_state.is_delayed_ping_timer_set) {
       t->ping_state.is_delayed_ping_timer_set = true;
+      GRPC_CHTTP2_REF_TRANSPORT(t, "retry_initiate_ping_locked");
       grpc_timer_init(&t->ping_state.delayed_ping_timer, next_allowed_ping,
                       &t->retry_initiate_ping_locked);
     }
diff --git a/include/grpc/support/tls.h b/src/core/lib/gpr/tls.h
similarity index 87%
rename from include/grpc/support/tls.h
rename to src/core/lib/gpr/tls.h
index 4c9e79b..aee8f4d 100644
--- a/include/grpc/support/tls.h
+++ b/src/core/lib/gpr/tls.h
@@ -16,8 +16,8 @@
  *
  */
 
-#ifndef GRPC_SUPPORT_TLS_H
-#define GRPC_SUPPORT_TLS_H
+#ifndef GRPC_CORE_LIB_GPR_TLS_H
+#define GRPC_CORE_LIB_GPR_TLS_H
 
 #include <grpc/support/port_platform.h>
 
@@ -54,15 +54,15 @@
    ALL functions here may be implemented as macros. */
 
 #ifdef GPR_GCC_TLS
-#include <grpc/support/tls_gcc.h>
+#include "src/core/lib/gpr/tls_gcc.h"
 #endif
 
 #ifdef GPR_MSVC_TLS
-#include <grpc/support/tls_msvc.h>
+#include "src/core/lib/gpr/tls_msvc.h"
 #endif
 
 #ifdef GPR_PTHREAD_TLS
-#include <grpc/support/tls_pthread.h>
+#include "src/core/lib/gpr/tls_pthread.h"
 #endif
 
-#endif /* GRPC_SUPPORT_TLS_H */
+#endif /* GRPC_CORE_LIB_GPR_TLS_H */
diff --git a/include/grpc/support/tls_gcc.h b/src/core/lib/gpr/tls_gcc.h
similarity index 91%
rename from include/grpc/support/tls_gcc.h
rename to src/core/lib/gpr/tls_gcc.h
index b44f0f1..14c59ec 100644
--- a/include/grpc/support/tls_gcc.h
+++ b/src/core/lib/gpr/tls_gcc.h
@@ -16,8 +16,8 @@
  *
  */
 
-#ifndef GRPC_SUPPORT_TLS_GCC_H
-#define GRPC_SUPPORT_TLS_GCC_H
+#ifndef GRPC_CORE_LIB_GPR_TLS_GCC_H
+#define GRPC_CORE_LIB_GPR_TLS_GCC_H
 
 #include <stdbool.h>
 
@@ -47,4 +47,4 @@
 #define gpr_tls_set(tls, new_value) (((tls)->value) = (new_value))
 #define gpr_tls_get(tls) ((tls)->value)
 
-#endif /* GRPC_SUPPORT_TLS_GCC_H */
+#endif /* GRPC_CORE_LIB_GPR_TLS_GCC_H */
diff --git a/include/grpc/support/tls_msvc.h b/src/core/lib/gpr/tls_msvc.h
similarity index 92%
rename from include/grpc/support/tls_msvc.h
rename to src/core/lib/gpr/tls_msvc.h
index 68a411f..a6cc417 100644
--- a/include/grpc/support/tls_msvc.h
+++ b/src/core/lib/gpr/tls_msvc.h
@@ -16,8 +16,8 @@
  *
  */
 
-#ifndef GRPC_SUPPORT_TLS_MSVC_H
-#define GRPC_SUPPORT_TLS_MSVC_H
+#ifndef GRPC_CORE_LIB_GPR_TLS_MSVC_H
+#define GRPC_CORE_LIB_GPR_TLS_MSVC_H
 
 /** Thread local storage based on ms visual c compiler primitives.
    #include tls.h to use this - and see that file for documentation */
@@ -47,4 +47,4 @@
 #define gpr_tls_set(tls, new_value) (((tls)->value) = (new_value))
 #define gpr_tls_get(tls) ((tls)->value)
 
-#endif /* GRPC_SUPPORT_TLS_MSVC_H */
+#endif /* GRPC_CORE_LIB_GPR_TLS_MSVC_H */
diff --git a/src/core/lib/gpr/tls_pthread.cc b/src/core/lib/gpr/tls_pthread.cc
index ebeef2a..2e5b306 100644
--- a/src/core/lib/gpr/tls_pthread.cc
+++ b/src/core/lib/gpr/tls_pthread.cc
@@ -20,7 +20,7 @@
 
 #ifdef GPR_PTHREAD_TLS
 
-#include <grpc/support/tls.h>
+#include "src/core/lib/gpr/tls.h"
 
 intptr_t gpr_tls_set(struct gpr_pthread_thread_local* tls, intptr_t value) {
   GPR_ASSERT(0 == pthread_setspecific(tls->key, (void*)value));
diff --git a/include/grpc/support/tls_pthread.h b/src/core/lib/gpr/tls_pthread.h
similarity index 93%
rename from include/grpc/support/tls_pthread.h
rename to src/core/lib/gpr/tls_pthread.h
index 249c8b1..9202653 100644
--- a/include/grpc/support/tls_pthread.h
+++ b/src/core/lib/gpr/tls_pthread.h
@@ -16,8 +16,8 @@
  *
  */
 
-#ifndef GRPC_SUPPORT_TLS_PTHREAD_H
-#define GRPC_SUPPORT_TLS_PTHREAD_H
+#ifndef GRPC_CORE_LIB_GPR_TLS_PTHREAD_H
+#define GRPC_CORE_LIB_GPR_TLS_PTHREAD_H
 
 #include <grpc/support/log.h> /* for GPR_ASSERT */
 #include <pthread.h>
@@ -51,4 +51,4 @@
 }
 #endif
 
-#endif /* GRPC_SUPPORT_TLS_PTHREAD_H */
+#endif /* GRPC_CORE_LIB_GPR_TLS_PTHREAD_H */
diff --git a/src/core/lib/iomgr/ev_epoll1_linux.cc b/src/core/lib/iomgr/ev_epoll1_linux.cc
index 512905d..a05d74c 100644
--- a/src/core/lib/iomgr/ev_epoll1_linux.cc
+++ b/src/core/lib/iomgr/ev_epoll1_linux.cc
@@ -39,10 +39,10 @@
 #include <grpc/support/alloc.h>
 #include <grpc/support/cpu.h>
 #include <grpc/support/string_util.h>
-#include <grpc/support/tls.h>
 
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
 #include "src/core/lib/iomgr/block_annotate.h"
diff --git a/src/core/lib/iomgr/ev_epollex_linux.cc b/src/core/lib/iomgr/ev_epollex_linux.cc
index 7697a85..000faed 100644
--- a/src/core/lib/iomgr/ev_epollex_linux.cc
+++ b/src/core/lib/iomgr/ev_epollex_linux.cc
@@ -37,10 +37,10 @@
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/string_util.h>
-#include <grpc/support/tls.h>
 
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/gpr/spinlock.h"
+#include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
 #include "src/core/lib/iomgr/block_annotate.h"
diff --git a/src/core/lib/iomgr/ev_epollsig_linux.cc b/src/core/lib/iomgr/ev_epollsig_linux.cc
index b174615..4c26bbd 100644
--- a/src/core/lib/iomgr/ev_epollsig_linux.cc
+++ b/src/core/lib/iomgr/ev_epollsig_linux.cc
@@ -39,9 +39,9 @@
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/string_util.h>
-#include <grpc/support/tls.h>
 
 #include "src/core/lib/debug/stats.h"
+#include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/manual_constructor.h"
 #include "src/core/lib/iomgr/block_annotate.h"
diff --git a/src/core/lib/iomgr/ev_poll_posix.cc b/src/core/lib/iomgr/ev_poll_posix.cc
index 179512b..9833b47 100644
--- a/src/core/lib/iomgr/ev_poll_posix.cc
+++ b/src/core/lib/iomgr/ev_poll_posix.cc
@@ -34,10 +34,10 @@
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 #include <grpc/support/thd.h>
-#include <grpc/support/tls.h>
 
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/gpr/murmur_hash.h"
+#include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/iomgr/block_annotate.h"
 #include "src/core/lib/iomgr/iomgr_internal.h"
diff --git a/src/core/lib/iomgr/exec_ctx.h b/src/core/lib/iomgr/exec_ctx.h
index 2e71482..3d9a157 100644
--- a/src/core/lib/iomgr/exec_ctx.h
+++ b/src/core/lib/iomgr/exec_ctx.h
@@ -22,8 +22,8 @@
 #include <grpc/support/atm.h>
 #include <grpc/support/cpu.h>
 #include <grpc/support/log.h>
-#include <grpc/support/tls.h>
 
+#include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/iomgr/closure.h"
 
 typedef gpr_atm grpc_millis;
diff --git a/src/core/lib/iomgr/executor.cc b/src/core/lib/iomgr/executor.cc
index ca67c04..3288ea3 100644
--- a/src/core/lib/iomgr/executor.cc
+++ b/src/core/lib/iomgr/executor.cc
@@ -25,10 +25,10 @@
 #include <grpc/support/log.h>
 #include <grpc/support/sync.h>
 #include <grpc/support/thd.h>
-#include <grpc/support/tls.h>
 
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/gpr/spinlock.h"
+#include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/iomgr/exec_ctx.h"
 
diff --git a/src/core/lib/iomgr/timer_generic.cc b/src/core/lib/iomgr/timer_generic.cc
index 5d0fcde..ef86d23 100644
--- a/src/core/lib/iomgr/timer_generic.cc
+++ b/src/core/lib/iomgr/timer_generic.cc
@@ -29,10 +29,10 @@
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 #include <grpc/support/sync.h>
-#include <grpc/support/tls.h>
 
 #include "src/core/lib/debug/trace.h"
 #include "src/core/lib/gpr/spinlock.h"
+#include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/iomgr/time_averaged_stats.h"
 #include "src/core/lib/iomgr/timer_heap.h"
diff --git a/src/core/lib/surface/completion_queue.cc b/src/core/lib/surface/completion_queue.cc
index 8362522..a2c5208 100644
--- a/src/core/lib/surface/completion_queue.cc
+++ b/src/core/lib/surface/completion_queue.cc
@@ -28,11 +28,11 @@
 #include <grpc/support/log.h>
 #include <grpc/support/string_util.h>
 #include <grpc/support/time.h>
-#include <grpc/support/tls.h>
 
 #include "src/core/lib/debug/stats.h"
 #include "src/core/lib/gpr/spinlock.h"
 #include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/iomgr/pollset.h"
 #include "src/core/lib/iomgr/timer.h"
 #include "src/core/lib/profiling/timers.h"
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index b1b6c54..2da3c0b 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -266,7 +266,6 @@
     'src/core/ext/filters/client_channel/proxy_mapper.cc',
     'src/core/ext/filters/client_channel/proxy_mapper_registry.cc',
     'src/core/ext/filters/client_channel/resolver.cc',
-    'src/core/ext/filters/client_channel/resolver_factory.cc',
     'src/core/ext/filters/client_channel/resolver_registry.cc',
     'src/core/ext/filters/client_channel/retry_throttle.cc',
     'src/core/ext/filters/client_channel/subchannel.cc',
diff --git a/test/core/bad_ssl/generate_tests.bzl b/test/core/bad_ssl/generate_tests.bzl
index f2eab54..3cf7fd7 100755
--- a/test/core/bad_ssl/generate_tests.bzl
+++ b/test/core/bad_ssl/generate_tests.bzl
@@ -39,9 +39,10 @@
     native.cc_test(
         name = 'bad_ssl_%s_test' % t,
         srcs = ['bad_ssl_test.cc'],
-        data = [':bad_ssl_%s_server' % t],
+        data = [':bad_ssl_%s_server' % t,
+                '//src/core/tsi/test_creds:badserver.key',
+                '//src/core/tsi/test_creds:badserver.pem',],
         deps = ['//test/core/util:grpc_test_util',
                 '//:gpr',
                 '//test/core/end2end:cq_verifier'],
     )
-
diff --git a/test/core/client_channel/resolvers/dns_resolver_connectivity_test.cc b/test/core/client_channel/resolvers/dns_resolver_connectivity_test.cc
index 18a795f..8cafadf 100644
--- a/test/core/client_channel/resolvers/dns_resolver_connectivity_test.cc
+++ b/test/core/client_channel/resolvers/dns_resolver_connectivity_test.cc
@@ -79,17 +79,17 @@
   return nullptr;
 }
 
-static grpc_resolver* create_resolver(const char* name) {
-  grpc_resolver_factory* factory = grpc_resolver_factory_lookup("dns");
+static grpc_core::OrphanablePtr<grpc_core::Resolver> create_resolver(
+    const char* name) {
+  grpc_core::ResolverFactory* factory =
+      grpc_core::ResolverRegistry::LookupResolverFactory("dns");
   grpc_uri* uri = grpc_uri_parse(name, 0);
   GPR_ASSERT(uri);
-  grpc_resolver_args args;
-  memset(&args, 0, sizeof(args));
+  grpc_core::ResolverArgs args;
   args.uri = uri;
   args.combiner = g_combiner;
-  grpc_resolver* resolver =
-      grpc_resolver_factory_create_resolver(factory, &args);
-  grpc_resolver_factory_unref(factory);
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
+      factory->CreateResolver(args);
   grpc_uri_destroy(uri);
   return resolver;
 }
@@ -112,7 +112,7 @@
 }
 
 typedef struct next_args {
-  grpc_resolver* resolver;
+  grpc_core::Resolver* resolver;
   grpc_channel_args** result;
   grpc_closure* on_complete;
 } next_args;
@@ -120,21 +120,21 @@
 static void call_resolver_next_now_lock_taken(void* arg,
                                               grpc_error* error_unused) {
   next_args* a = static_cast<next_args*>(arg);
-  grpc_resolver_next_locked(a->resolver, a->result, a->on_complete);
+  a->resolver->NextLocked(a->result, a->on_complete);
   gpr_free(a);
 }
 
-static void call_resolver_next_after_locking(grpc_resolver* resolver,
+static void call_resolver_next_after_locking(grpc_core::Resolver* resolver,
                                              grpc_channel_args** result,
-                                             grpc_closure* on_complete) {
+                                             grpc_closure* on_complete,
+                                             grpc_combiner* combiner) {
   next_args* a = static_cast<next_args*>(gpr_malloc(sizeof(*a)));
   a->resolver = resolver;
   a->result = result;
   a->on_complete = on_complete;
-  GRPC_CLOSURE_SCHED(
-      GRPC_CLOSURE_CREATE(call_resolver_next_now_lock_taken, a,
-                          grpc_combiner_scheduler(resolver->combiner)),
-      GRPC_ERROR_NONE);
+  GRPC_CLOSURE_SCHED(GRPC_CLOSURE_CREATE(call_resolver_next_now_lock_taken, a,
+                                         grpc_combiner_scheduler(combiner)),
+                     GRPC_ERROR_NONE);
 }
 
 int main(int argc, char** argv) {
@@ -149,12 +149,14 @@
 
   {
     grpc_core::ExecCtx exec_ctx;
-    grpc_resolver* resolver = create_resolver("dns:test");
+    grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
+        create_resolver("dns:test");
     gpr_event ev1;
     gpr_event_init(&ev1);
     call_resolver_next_after_locking(
-        resolver, &result,
-        GRPC_CLOSURE_CREATE(on_done, &ev1, grpc_schedule_on_exec_ctx));
+        resolver.get(), &result,
+        GRPC_CLOSURE_CREATE(on_done, &ev1, grpc_schedule_on_exec_ctx),
+        g_combiner);
     grpc_core::ExecCtx::Get()->Flush();
     GPR_ASSERT(wait_loop(5, &ev1));
     GPR_ASSERT(result == nullptr);
@@ -162,14 +164,14 @@
     gpr_event ev2;
     gpr_event_init(&ev2);
     call_resolver_next_after_locking(
-        resolver, &result,
-        GRPC_CLOSURE_CREATE(on_done, &ev2, grpc_schedule_on_exec_ctx));
+        resolver.get(), &result,
+        GRPC_CLOSURE_CREATE(on_done, &ev2, grpc_schedule_on_exec_ctx),
+        g_combiner);
     grpc_core::ExecCtx::Get()->Flush();
     GPR_ASSERT(wait_loop(30, &ev2));
     GPR_ASSERT(result != nullptr);
 
     grpc_channel_args_destroy(result);
-    GRPC_RESOLVER_UNREF(resolver, "test");
     GRPC_COMBINER_UNREF(g_combiner, "test");
   }
 
diff --git a/test/core/client_channel/resolvers/dns_resolver_cooldown_test.cc b/test/core/client_channel/resolvers/dns_resolver_cooldown_test.cc
index 64342b4..9402a605 100644
--- a/test/core/client_channel/resolvers/dns_resolver_cooldown_test.cc
+++ b/test/core/client_channel/resolvers/dns_resolver_cooldown_test.cc
@@ -23,6 +23,7 @@
 #include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gprpp/memory.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/iomgr/sockaddr_utils.h"
 #include "test/core/util/test_config.h"
@@ -131,13 +132,13 @@
   gpr_event_set(&args->ev, (void*)1);
 }
 
-typedef struct on_resolution_cb_arg {
-  const char* uri_str;
-  grpc_resolver* resolver;
-  grpc_channel_args* result;
-  grpc_millis delay_before_second_resolution;
-  bool using_cares;
-} on_resolution_cb_arg;
+struct OnResolutionCallbackArg {
+  const char* uri_str = nullptr;
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver;
+  grpc_channel_args* result = nullptr;
+  grpc_millis delay_before_second_resolution = 0;
+  bool using_cares = false;
+};
 
 // Counter for the number of times a resolution notification callback has been
 // invoked.
@@ -147,7 +148,7 @@
 bool g_all_callbacks_invoked;
 
 void on_third_resolution(void* arg, grpc_error* error) {
-  on_resolution_cb_arg* cb_arg = static_cast<on_resolution_cb_arg*>(arg);
+  OnResolutionCallbackArg* cb_arg = static_cast<OnResolutionCallbackArg*>(arg);
   GPR_ASSERT(error == GRPC_ERROR_NONE);
   ++g_on_resolution_invocations_count;
   grpc_channel_args_destroy(cb_arg->result);
@@ -159,8 +160,7 @@
   // period.
   GPR_ASSERT(g_on_resolution_invocations_count == 3);
   GPR_ASSERT(g_resolution_count == 2);
-  grpc_resolver_shutdown_locked(cb_arg->resolver);
-  GRPC_RESOLVER_UNREF(cb_arg->resolver, "on_third_resolution");
+  cb_arg->resolver.reset();
   if (cb_arg->using_cares) {
     gpr_atm_rel_store(&g_iomgr_args.done_atm, 1);
     gpr_mu_lock(g_iomgr_args.mu);
@@ -168,12 +168,12 @@
                       grpc_pollset_kick(g_iomgr_args.pollset, nullptr));
     gpr_mu_unlock(g_iomgr_args.mu);
   }
-  gpr_free(cb_arg);
+  grpc_core::Delete(cb_arg);
   g_all_callbacks_invoked = true;
 }
 
 void on_second_resolution(void* arg, grpc_error* error) {
-  on_resolution_cb_arg* cb_arg = static_cast<on_resolution_cb_arg*>(arg);
+  OnResolutionCallbackArg* cb_arg = static_cast<OnResolutionCallbackArg*>(arg);
   ++g_on_resolution_invocations_count;
   grpc_channel_args_destroy(cb_arg->result);
 
@@ -187,11 +187,11 @@
   GPR_ASSERT(g_resolution_count == 1);
   grpc_core::ExecCtx::Get()->TestOnlySetNow(
       cb_arg->delay_before_second_resolution * 2);
-  grpc_resolver_next_locked(
-      cb_arg->resolver, &cb_arg->result,
+  cb_arg->resolver->NextLocked(
+      &cb_arg->result,
       GRPC_CLOSURE_CREATE(on_third_resolution, arg,
                           grpc_combiner_scheduler(g_combiner)));
-  grpc_resolver_channel_saw_error_locked(cb_arg->resolver);
+  cb_arg->resolver->RequestReresolutionLocked();
   if (cb_arg->using_cares) {
     gpr_mu_lock(g_iomgr_args.mu);
     GRPC_LOG_IF_ERROR("pollset_kick",
@@ -201,14 +201,14 @@
 }
 
 void on_first_resolution(void* arg, grpc_error* error) {
-  on_resolution_cb_arg* cb_arg = static_cast<on_resolution_cb_arg*>(arg);
+  OnResolutionCallbackArg* cb_arg = static_cast<OnResolutionCallbackArg*>(arg);
   ++g_on_resolution_invocations_count;
   grpc_channel_args_destroy(cb_arg->result);
-  grpc_resolver_next_locked(
-      cb_arg->resolver, &cb_arg->result,
+  cb_arg->resolver->NextLocked(
+      &cb_arg->result,
       GRPC_CLOSURE_CREATE(on_second_resolution, arg,
                           grpc_combiner_scheduler(g_combiner)));
-  grpc_resolver_channel_saw_error_locked(cb_arg->resolver);
+  cb_arg->resolver->RequestReresolutionLocked();
   gpr_log(GPR_INFO,
           "1st: g_on_resolution_invocations_count: %d, g_resolution_count: %d",
           g_on_resolution_invocations_count, g_resolution_count);
@@ -225,15 +225,16 @@
 }
 
 static void start_test_under_combiner(void* arg, grpc_error* error) {
-  on_resolution_cb_arg* res_cb_arg = static_cast<on_resolution_cb_arg*>(arg);
-  grpc_resolver* resolver;
-  grpc_resolver_factory* factory = grpc_resolver_factory_lookup("dns");
+  OnResolutionCallbackArg* res_cb_arg =
+      static_cast<OnResolutionCallbackArg*>(arg);
+
+  grpc_core::ResolverFactory* factory =
+      grpc_core::ResolverRegistry::LookupResolverFactory("dns");
   grpc_uri* uri = grpc_uri_parse(res_cb_arg->uri_str, 0);
-  grpc_resolver_args args;
   gpr_log(GPR_DEBUG, "test: '%s' should be valid for '%s'", res_cb_arg->uri_str,
-          factory->vtable->scheme);
-  GPR_ASSERT(uri);
-  memset(&args, 0, sizeof(args));
+          factory->scheme());
+  GPR_ASSERT(uri != nullptr);
+  grpc_core::ResolverArgs args;
   args.uri = uri;
   args.combiner = g_combiner;
   g_on_resolution_invocations_count = 0;
@@ -248,25 +249,23 @@
   auto* cooldown_channel_args =
       grpc_channel_args_copy_and_add(nullptr, &cooldown_arg, 1);
   args.args = cooldown_channel_args;
-  resolver = grpc_resolver_factory_create_resolver(factory, &args);
+  res_cb_arg->resolver = factory->CreateResolver(args);
   grpc_channel_args_destroy(cooldown_channel_args);
-  GPR_ASSERT(resolver != nullptr);
-  res_cb_arg->resolver = resolver;
+  GPR_ASSERT(res_cb_arg->resolver != nullptr);
   res_cb_arg->delay_before_second_resolution = kMinResolutionPeriodMs;
   // First resolution, would incur in system-level resolution.
-  grpc_resolver_next_locked(
-      resolver, &res_cb_arg->result,
+  res_cb_arg->resolver->NextLocked(
+      &res_cb_arg->result,
       GRPC_CLOSURE_CREATE(on_first_resolution, res_cb_arg,
                           grpc_combiner_scheduler(g_combiner)));
   grpc_uri_destroy(uri);
-  grpc_resolver_factory_unref(factory);
 }
 
 static void test_cooldown(bool using_cares) {
   grpc_core::ExecCtx exec_ctx;
   if (using_cares) iomgr_args_init(&g_iomgr_args);
-  on_resolution_cb_arg* res_cb_arg =
-      static_cast<on_resolution_cb_arg*>(gpr_zalloc(sizeof(*res_cb_arg)));
+  OnResolutionCallbackArg* res_cb_arg =
+      grpc_core::New<OnResolutionCallbackArg>();
   res_cb_arg->uri_str = "dns:127.0.0.1";
   res_cb_arg->using_cares = using_cares;
 
diff --git a/test/core/client_channel/resolvers/dns_resolver_test.cc b/test/core/client_channel/resolvers/dns_resolver_test.cc
index 8066790..e3fba28 100644
--- a/test/core/client_channel/resolvers/dns_resolver_test.cc
+++ b/test/core/client_channel/resolvers/dns_resolver_test.cc
@@ -27,47 +27,46 @@
 
 static grpc_combiner* g_combiner;
 
-static void test_succeeds(grpc_resolver_factory* factory, const char* string) {
+static void test_succeeds(grpc_core::ResolverFactory* factory,
+                          const char* string) {
+  gpr_log(GPR_DEBUG, "test: '%s' should be valid for '%s'", string,
+          factory->scheme());
   grpc_core::ExecCtx exec_ctx;
   grpc_uri* uri = grpc_uri_parse(string, 0);
-  grpc_resolver_args args;
-  grpc_resolver* resolver;
-  gpr_log(GPR_DEBUG, "test: '%s' should be valid for '%s'", string,
-          factory->vtable->scheme);
   GPR_ASSERT(uri);
-  memset(&args, 0, sizeof(args));
+  grpc_core::ResolverArgs args;
   args.uri = uri;
   args.combiner = g_combiner;
-  resolver = grpc_resolver_factory_create_resolver(factory, &args);
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
+      factory->CreateResolver(args);
   GPR_ASSERT(resolver != nullptr);
-  GRPC_RESOLVER_UNREF(resolver, "test_succeeds");
   grpc_uri_destroy(uri);
 }
 
-static void test_fails(grpc_resolver_factory* factory, const char* string) {
+static void test_fails(grpc_core::ResolverFactory* factory,
+                       const char* string) {
+  gpr_log(GPR_DEBUG, "test: '%s' should be invalid for '%s'", string,
+          factory->scheme());
   grpc_core::ExecCtx exec_ctx;
   grpc_uri* uri = grpc_uri_parse(string, 0);
-  grpc_resolver_args args;
-  grpc_resolver* resolver;
-  gpr_log(GPR_DEBUG, "test: '%s' should be invalid for '%s'", string,
-          factory->vtable->scheme);
   GPR_ASSERT(uri);
-  memset(&args, 0, sizeof(args));
+  grpc_core::ResolverArgs args;
   args.uri = uri;
   args.combiner = g_combiner;
-  resolver = grpc_resolver_factory_create_resolver(factory, &args);
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
+      factory->CreateResolver(args);
   GPR_ASSERT(resolver == nullptr);
   grpc_uri_destroy(uri);
 }
 
 int main(int argc, char** argv) {
-  grpc_resolver_factory* dns;
   grpc_test_init(argc, argv);
   grpc_init();
 
   g_combiner = grpc_combiner_create();
 
-  dns = grpc_resolver_factory_lookup("dns");
+  grpc_core::ResolverFactory* dns =
+      grpc_core::ResolverRegistry::LookupResolverFactory("dns");
 
   test_succeeds(dns, "dns:10.2.1.1");
   test_succeeds(dns, "dns:10.2.1.1:1234");
@@ -78,7 +77,6 @@
     test_fails(dns, "ipv4://8.8.8.8/8.8.8.8:8888");
   }
 
-  grpc_resolver_factory_unref(dns);
   {
     grpc_core::ExecCtx exec_ctx;
     GRPC_COMBINER_UNREF(g_combiner, "test");
diff --git a/test/core/client_channel/resolvers/fake_resolver_test.cc b/test/core/client_channel/resolvers/fake_resolver_test.cc
index 9c11c01..03af895 100644
--- a/test/core/client_channel/resolvers/fake_resolver_test.cc
+++ b/test/core/client_channel/resolvers/fake_resolver_test.cc
@@ -27,25 +27,26 @@
 #include "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h"
 #include "src/core/ext/filters/client_channel/resolver_registry.h"
 #include "src/core/lib/channel/channel_args.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/security/credentials/fake/fake_credentials.h"
 
 #include "test/core/util/test_config.h"
 
-static grpc_resolver* build_fake_resolver(
+static grpc_core::OrphanablePtr<grpc_core::Resolver> build_fake_resolver(
     grpc_combiner* combiner,
-    grpc_fake_resolver_response_generator* response_generator) {
-  grpc_resolver_factory* factory = grpc_resolver_factory_lookup("fake");
+    grpc_core::FakeResolverResponseGenerator* response_generator) {
+  grpc_core::ResolverFactory* factory =
+      grpc_core::ResolverRegistry::LookupResolverFactory("fake");
   grpc_arg generator_arg =
-      grpc_fake_resolver_response_generator_arg(response_generator);
-  grpc_resolver_args args;
-  memset(&args, 0, sizeof(args));
+      grpc_core::FakeResolverResponseGenerator::MakeChannelArg(
+          response_generator);
   grpc_channel_args channel_args = {1, &generator_arg};
+  grpc_core::ResolverArgs args;
   args.args = &channel_args;
   args.combiner = combiner;
-  grpc_resolver* resolver =
-      grpc_resolver_factory_create_resolver(factory, &args);
-  grpc_resolver_factory_unref(factory);
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
+      factory->CreateResolver(args);
   return resolver;
 }
 
@@ -57,10 +58,11 @@
 
 // Callback to check the resolution result is as expected.
 void on_resolution_cb(void* arg, grpc_error* error) {
+  if (error != GRPC_ERROR_NONE) return;
   on_resolution_arg* res = static_cast<on_resolution_arg*>(arg);
   // We only check the addresses channel arg because that's the only one
   // explicitly set by the test via
-  // grpc_fake_resolver_response_generator_set_response.
+  // FakeResolverResponseGenerator::SetResponse().
   const grpc_lb_addresses* actual_lb_addresses =
       grpc_lb_addresses_find_channel_arg(res->resolver_result);
   const grpc_lb_addresses* expected_lb_addresses =
@@ -115,27 +117,27 @@
   grpc_core::ExecCtx exec_ctx;
   grpc_combiner* combiner = grpc_combiner_create();
   // Create resolver.
-  grpc_fake_resolver_response_generator* response_generator =
-      grpc_fake_resolver_response_generator_create();
-  grpc_resolver* resolver = build_fake_resolver(combiner, response_generator);
-  GPR_ASSERT(resolver != nullptr);
+  grpc_core::RefCountedPtr<grpc_core::FakeResolverResponseGenerator>
+      response_generator =
+          grpc_core::MakeRefCounted<grpc_core::FakeResolverResponseGenerator>();
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
+      build_fake_resolver(combiner, response_generator.get());
+  GPR_ASSERT(resolver.get() != nullptr);
   // Test 1: normal resolution.
-  // next_results != NULL, results_upon_error == NULL, last_used_results ==
+  // next_results != NULL, reresolution_results == NULL, last_used_results ==
   // NULL. Expected response is next_results.
   grpc_channel_args* results = create_new_resolver_result();
   on_resolution_arg on_res_arg = create_on_resolution_arg(results);
   grpc_closure* on_resolution = GRPC_CLOSURE_CREATE(
       on_resolution_cb, &on_res_arg, grpc_combiner_scheduler(combiner));
   // Resolution won't be triggered until next_results is set.
-  grpc_resolver_next_locked(resolver, &on_res_arg.resolver_result,
-                            on_resolution);
-  grpc_fake_resolver_response_generator_set_response(response_generator,
-                                                     results);
+  resolver->NextLocked(&on_res_arg.resolver_result, on_resolution);
+  response_generator->SetResponse(results);
   grpc_core::ExecCtx::Get()->Flush();
   GPR_ASSERT(gpr_event_wait(&on_res_arg.ev,
                             grpc_timeout_seconds_to_deadline(5)) != nullptr);
   // Test 2: update resolution.
-  // next_results != NULL, results_upon_error == NULL, last_used_results !=
+  // next_results != NULL, reresolution_results == NULL, last_used_results !=
   // NULL. Expected response is next_results.
   results = create_new_resolver_result();
   grpc_channel_args* last_used_results = grpc_channel_args_copy(results);
@@ -143,61 +145,55 @@
   on_resolution = GRPC_CLOSURE_CREATE(on_resolution_cb, &on_res_arg,
                                       grpc_combiner_scheduler(combiner));
   // Resolution won't be triggered until next_results is set.
-  grpc_resolver_next_locked(resolver, &on_res_arg.resolver_result,
-                            on_resolution);
-  grpc_fake_resolver_response_generator_set_response(response_generator,
-                                                     results);
+  resolver->NextLocked(&on_res_arg.resolver_result, on_resolution);
+  response_generator->SetResponse(results);
   grpc_core::ExecCtx::Get()->Flush();
   GPR_ASSERT(gpr_event_wait(&on_res_arg.ev,
                             grpc_timeout_seconds_to_deadline(5)) != nullptr);
   // Test 3: fallback re-resolution.
-  // next_results == NULL, results_upon_error == NULL, last_used_results !=
+  // next_results == NULL, reresolution_results == NULL, last_used_results !=
   // NULL. Expected response is last_used_results.
   on_res_arg = create_on_resolution_arg(last_used_results);
   on_resolution = GRPC_CLOSURE_CREATE(on_resolution_cb, &on_res_arg,
                                       grpc_combiner_scheduler(combiner));
-  grpc_resolver_next_locked(resolver, &on_res_arg.resolver_result,
-                            on_resolution);
+  resolver->NextLocked(&on_res_arg.resolver_result, on_resolution);
   // Trigger a re-resolution.
-  grpc_resolver_channel_saw_error_locked(resolver);
+  resolver->RequestReresolutionLocked();
   grpc_core::ExecCtx::Get()->Flush();
   GPR_ASSERT(gpr_event_wait(&on_res_arg.ev,
                             grpc_timeout_seconds_to_deadline(5)) != nullptr);
   // Test 4: normal re-resolution.
-  // next_results == NULL, results_upon_error != NULL, last_used_results !=
-  // NULL. Expected response is results_upon_error.
-  grpc_channel_args* results_upon_error = create_new_resolver_result();
+  // next_results == NULL, reresolution_results != NULL, last_used_results !=
+  // NULL. Expected response is reresolution_results.
+  grpc_channel_args* reresolution_results = create_new_resolver_result();
   on_res_arg =
-      create_on_resolution_arg(grpc_channel_args_copy(results_upon_error));
+      create_on_resolution_arg(grpc_channel_args_copy(reresolution_results));
   on_resolution = GRPC_CLOSURE_CREATE(on_resolution_cb, &on_res_arg,
                                       grpc_combiner_scheduler(combiner));
-  grpc_resolver_next_locked(resolver, &on_res_arg.resolver_result,
-                            on_resolution);
-  // Set results_upon_error.
-  grpc_fake_resolver_response_generator_set_response_upon_error(
-      response_generator, results_upon_error);
+  resolver->NextLocked(&on_res_arg.resolver_result, on_resolution);
+  // Set reresolution_results.
+  response_generator->SetReresolutionResponse(reresolution_results);
   // Flush here to guarantee that the response has been set.
   grpc_core::ExecCtx::Get()->Flush();
   // Trigger a re-resolution.
-  grpc_resolver_channel_saw_error_locked(resolver);
+  resolver->RequestReresolutionLocked();
   grpc_core::ExecCtx::Get()->Flush();
   GPR_ASSERT(gpr_event_wait(&on_res_arg.ev,
                             grpc_timeout_seconds_to_deadline(5)) != nullptr);
   // Test 5: repeat re-resolution.
-  // next_results == NULL, results_upon_error != NULL, last_used_results !=
-  // NULL. Expected response is results_upon_error.
-  on_res_arg = create_on_resolution_arg(results_upon_error);
+  // next_results == NULL, reresolution_results != NULL, last_used_results !=
+  // NULL. Expected response is reresolution_results.
+  on_res_arg = create_on_resolution_arg(reresolution_results);
   on_resolution = GRPC_CLOSURE_CREATE(on_resolution_cb, &on_res_arg,
                                       grpc_combiner_scheduler(combiner));
-  grpc_resolver_next_locked(resolver, &on_res_arg.resolver_result,
-                            on_resolution);
+  resolver->NextLocked(&on_res_arg.resolver_result, on_resolution);
   // Trigger a re-resolution.
-  grpc_resolver_channel_saw_error_locked(resolver);
+  resolver->RequestReresolutionLocked();
   grpc_core::ExecCtx::Get()->Flush();
   GPR_ASSERT(gpr_event_wait(&on_res_arg.ev,
                             grpc_timeout_seconds_to_deadline(5)) != nullptr);
   // Test 6: normal resolution.
-  // next_results != NULL, results_upon_error != NULL, last_used_results !=
+  // next_results != NULL, reresolution_results != NULL, last_used_results !=
   // NULL. Expected response is next_results.
   results = create_new_resolver_result();
   last_used_results = grpc_channel_args_copy(results);
@@ -205,28 +201,24 @@
   on_resolution = GRPC_CLOSURE_CREATE(on_resolution_cb, &on_res_arg,
                                       grpc_combiner_scheduler(combiner));
   // Resolution won't be triggered until next_results is set.
-  grpc_resolver_next_locked(resolver, &on_res_arg.resolver_result,
-                            on_resolution);
-  grpc_fake_resolver_response_generator_set_response(response_generator,
-                                                     results);
+  resolver->NextLocked(&on_res_arg.resolver_result, on_resolution);
+  response_generator->SetResponse(results);
   grpc_core::ExecCtx::Get()->Flush();
   GPR_ASSERT(gpr_event_wait(&on_res_arg.ev,
                             grpc_timeout_seconds_to_deadline(5)) != nullptr);
   // Test 7: fallback re-resolution.
-  // next_results == NULL, results_upon_error == NULL, last_used_results !=
+  // next_results == NULL, reresolution_results == NULL, last_used_results !=
   // NULL. Expected response is last_used_results.
   on_res_arg = create_on_resolution_arg(last_used_results);
   on_resolution = GRPC_CLOSURE_CREATE(on_resolution_cb, &on_res_arg,
                                       grpc_combiner_scheduler(combiner));
-  grpc_resolver_next_locked(resolver, &on_res_arg.resolver_result,
-                            on_resolution);
-  // Reset results_upon_error.
-  grpc_fake_resolver_response_generator_set_response_upon_error(
-      response_generator, nullptr);
-  // Flush here to guarantee that results_upon_error has been reset.
+  resolver->NextLocked(&on_res_arg.resolver_result, on_resolution);
+  // Reset reresolution_results.
+  response_generator->SetReresolutionResponse(nullptr);
+  // Flush here to guarantee that reresolution_results has been reset.
   grpc_core::ExecCtx::Get()->Flush();
   // Trigger a re-resolution.
-  grpc_resolver_channel_saw_error_locked(resolver);
+  resolver->RequestReresolutionLocked();
   grpc_core::ExecCtx::Get()->Flush();
   GPR_ASSERT(gpr_event_wait(&on_res_arg.ev,
                             grpc_timeout_seconds_to_deadline(5)) != nullptr);
@@ -234,16 +226,15 @@
   // Requesting a new resolution without setting the response shouldn't trigger
   // the resolution callback.
   memset(&on_res_arg, 0, sizeof(on_res_arg));
-  grpc_resolver_next_locked(resolver, &on_res_arg.resolver_result,
-                            on_resolution);
+  on_resolution = GRPC_CLOSURE_CREATE(on_resolution_cb, &on_res_arg,
+                                      grpc_combiner_scheduler(combiner));
+  resolver->NextLocked(&on_res_arg.resolver_result, on_resolution);
   grpc_core::ExecCtx::Get()->Flush();
   GPR_ASSERT(gpr_event_wait(&on_res_arg.ev,
                             grpc_timeout_milliseconds_to_deadline(100)) ==
              nullptr);
   // Clean up.
   GRPC_COMBINER_UNREF(combiner, "test_fake_resolver");
-  GRPC_RESOLVER_UNREF(resolver, "test_fake_resolver");
-  grpc_fake_resolver_response_generator_unref(response_generator);
 }
 
 int main(int argc, char** argv) {
diff --git a/test/core/client_channel/resolvers/sockaddr_resolver_test.cc b/test/core/client_channel/resolvers/sockaddr_resolver_test.cc
index 07ee133..b9287c2 100644
--- a/test/core/client_channel/resolvers/sockaddr_resolver_test.cc
+++ b/test/core/client_channel/resolvers/sockaddr_resolver_test.cc
@@ -40,18 +40,18 @@
   grpc_channel_args_destroy(res->resolver_result);
 }
 
-static void test_succeeds(grpc_resolver_factory* factory, const char* string) {
+static void test_succeeds(grpc_core::ResolverFactory* factory,
+                          const char* string) {
+  gpr_log(GPR_DEBUG, "test: '%s' should be valid for '%s'", string,
+          factory->scheme());
   grpc_core::ExecCtx exec_ctx;
   grpc_uri* uri = grpc_uri_parse(string, 0);
-  grpc_resolver_args args;
-  grpc_resolver* resolver;
-  gpr_log(GPR_DEBUG, "test: '%s' should be valid for '%s'", string,
-          factory->vtable->scheme);
   GPR_ASSERT(uri);
-  memset(&args, 0, sizeof(args));
+  grpc_core::ResolverArgs args;
   args.uri = uri;
   args.combiner = g_combiner;
-  resolver = grpc_resolver_factory_create_resolver(factory, &args);
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
+      factory->CreateResolver(args);
   GPR_ASSERT(resolver != nullptr);
 
   on_resolution_arg on_res_arg;
@@ -60,40 +60,39 @@
   grpc_closure* on_resolution = GRPC_CLOSURE_CREATE(
       on_resolution_cb, &on_res_arg, grpc_schedule_on_exec_ctx);
 
-  grpc_resolver_next_locked(resolver, &on_res_arg.resolver_result,
-                            on_resolution);
-  GRPC_RESOLVER_UNREF(resolver, "test_succeeds");
+  resolver->NextLocked(&on_res_arg.resolver_result, on_resolution);
   grpc_uri_destroy(uri);
   /* Flush ExecCtx to avoid stack-use-after-scope on on_res_arg which is
    * accessed in the closure on_resolution_cb */
   grpc_core::ExecCtx::Get()->Flush();
 }
 
-static void test_fails(grpc_resolver_factory* factory, const char* string) {
+static void test_fails(grpc_core::ResolverFactory* factory,
+                       const char* string) {
+  gpr_log(GPR_DEBUG, "test: '%s' should be invalid for '%s'", string,
+          factory->scheme());
   grpc_core::ExecCtx exec_ctx;
   grpc_uri* uri = grpc_uri_parse(string, 0);
-  grpc_resolver_args args;
-  grpc_resolver* resolver;
-  gpr_log(GPR_DEBUG, "test: '%s' should be invalid for '%s'", string,
-          factory->vtable->scheme);
   GPR_ASSERT(uri);
-  memset(&args, 0, sizeof(args));
+  grpc_core::ResolverArgs args;
   args.uri = uri;
   args.combiner = g_combiner;
-  resolver = grpc_resolver_factory_create_resolver(factory, &args);
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
+      factory->CreateResolver(args);
   GPR_ASSERT(resolver == nullptr);
   grpc_uri_destroy(uri);
 }
 
 int main(int argc, char** argv) {
-  grpc_resolver_factory *ipv4, *ipv6;
   grpc_test_init(argc, argv);
   grpc_init();
 
   g_combiner = grpc_combiner_create();
 
-  ipv4 = grpc_resolver_factory_lookup("ipv4");
-  ipv6 = grpc_resolver_factory_lookup("ipv6");
+  grpc_core::ResolverFactory* ipv4 =
+      grpc_core::ResolverRegistry::LookupResolverFactory("ipv4");
+  grpc_core::ResolverFactory* ipv6 =
+      grpc_core::ResolverRegistry::LookupResolverFactory("ipv6");
 
   test_fails(ipv4, "ipv4:10.2.1.1");
   test_succeeds(ipv4, "ipv4:10.2.1.1:1234");
@@ -109,9 +108,6 @@
   test_fails(ipv6, "ipv6:[::]:123456");
   test_fails(ipv6, "ipv6:www.google.com");
 
-  grpc_resolver_factory_unref(ipv4);
-  grpc_resolver_factory_unref(ipv6);
-
   {
     grpc_core::ExecCtx exec_ctx;
     GRPC_COMBINER_UNREF(g_combiner, "test");
diff --git a/test/core/end2end/fuzzers/api_fuzzer_corpus/poc-2d730ebd78b3052e4367ad0d485208dcb205482cbcd6289f17907989b8de1fba b/test/core/end2end/fuzzers/api_fuzzer_corpus/poc-2d730ebd78b3052e4367ad0d485208dcb205482cbcd6289f17907989b8de1fba
new file mode 100644
index 0000000..89e28bb
--- /dev/null
+++ b/test/core/end2end/fuzzers/api_fuzzer_corpus/poc-2d730ebd78b3052e4367ad0d485208dcb205482cbcd6289f17907989b8de1fba
Binary files differ
diff --git a/test/core/gpr/tls_test.cc b/test/core/gpr/tls_test.cc
index 743b10f..1ef253e 100644
--- a/test/core/gpr/tls_test.cc
+++ b/test/core/gpr/tls_test.cc
@@ -21,9 +21,10 @@
 #include <grpc/support/log.h>
 #include <grpc/support/sync.h>
 #include <grpc/support/thd.h>
-#include <grpc/support/tls.h>
 #include <stdio.h>
 #include <stdlib.h>
+
+#include "src/core/lib/gpr/tls.h"
 #include "test/core/util/test_config.h"
 
 #define NUM_THREADS 100
diff --git a/test/core/surface/channel_create_test.cc b/test/core/surface/channel_create_test.cc
index 37247f8..56f4f60 100644
--- a/test/core/surface/channel_create_test.cc
+++ b/test/core/surface/channel_create_test.cc
@@ -29,8 +29,8 @@
 void test_unknown_scheme_target(void) {
   grpc_channel* chan;
   /* avoid default prefix */
-  grpc_resolver_registry_shutdown();
-  grpc_resolver_registry_init();
+  grpc_core::ResolverRegistry::Builder::ShutdownRegistry();
+  grpc_core::ResolverRegistry::Builder::InitRegistry();
 
   chan = grpc_insecure_channel_create("blah://blah", nullptr, nullptr);
   GPR_ASSERT(chan != nullptr);
diff --git a/test/core/surface/public_headers_must_be_c89.c b/test/core/surface/public_headers_must_be_c89.c
index 2dd6dc8..14267c1 100644
--- a/test/core/surface/public_headers_must_be_c89.c
+++ b/test/core/surface/public_headers_must_be_c89.c
@@ -55,7 +55,6 @@
 #include <grpc/support/sync_generic.h>
 #include <grpc/support/thd.h>
 #include <grpc/support/time.h>
-#include <grpc/support/tls.h>
 #include <grpc/support/workaround_list.h>
 
 #include <stdio.h>
diff --git a/test/core/surface/secure_channel_create_test.cc b/test/core/surface/secure_channel_create_test.cc
index fa22cd6..c10d679 100644
--- a/test/core/surface/secure_channel_create_test.cc
+++ b/test/core/surface/secure_channel_create_test.cc
@@ -28,8 +28,8 @@
 #include "test/core/util/test_config.h"
 
 void test_unknown_scheme_target(void) {
-  grpc_resolver_registry_shutdown();
-  grpc_resolver_registry_init();
+  grpc_core::ResolverRegistry::Builder::ShutdownRegistry();
+  grpc_core::ResolverRegistry::Builder::InitRegistry();
   grpc_channel_credentials* creds =
       grpc_fake_transport_security_credentials_create();
   grpc_channel* chan =
diff --git a/test/cpp/client/client_channel_stress_test.cc b/test/cpp/client/client_channel_stress_test.cc
index 80d1583..bb8553f 100644
--- a/test/cpp/client/client_channel_stress_test.cc
+++ b/test/cpp/client/client_channel_stress_test.cc
@@ -36,6 +36,7 @@
 #include <grpc/support/time.h>
 
 #include "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/sockaddr.h"
 
 #include "test/core/util/port.h"
@@ -230,8 +231,7 @@
     }
     grpc_arg fake_addresses = grpc_lb_addresses_create_channel_arg(addresses);
     grpc_channel_args fake_result = {1, &fake_addresses};
-    grpc_fake_resolver_response_generator_set_response(response_generator_,
-                                                       &fake_result);
+    response_generator_->SetResponse(&fake_result);
     grpc_lb_addresses_destroy(addresses);
   }
 
@@ -253,9 +253,10 @@
 
   void CreateStub() {
     ChannelArguments args;
-    response_generator_ = grpc_fake_resolver_response_generator_create();
+    response_generator_ =
+        grpc_core::MakeRefCounted<grpc_core::FakeResolverResponseGenerator>();
     args.SetPointer(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR,
-                    response_generator_);
+                    response_generator_.get());
     std::ostringstream uri;
     uri << "fake:///servername_not_used";
     channel_ =
@@ -298,7 +299,6 @@
     for (size_t i = 0; i < backends_.size(); ++i) {
       backend_servers_[i].Shutdown();
     }
-    grpc_fake_resolver_response_generator_unref(response_generator_);
   }
 
   std::atomic_bool shutdown_{false};
@@ -310,7 +310,8 @@
   std::vector<std::unique_ptr<BalancerServiceImpl>> balancers_;
   std::vector<ServerThread<BackendServiceImpl>> backend_servers_;
   std::vector<ServerThread<BalancerServiceImpl>> balancer_servers_;
-  grpc_fake_resolver_response_generator* response_generator_;
+  grpc_core::RefCountedPtr<grpc_core::FakeResolverResponseGenerator>
+      response_generator_;
   std::vector<std::thread> client_threads_;
 };
 
diff --git a/test/cpp/end2end/async_end2end_test.cc b/test/cpp/end2end/async_end2end_test.cc
index 44cd81a..482f0cb 100644
--- a/test/cpp/end2end/async_end2end_test.cc
+++ b/test/cpp/end2end/async_end2end_test.cc
@@ -32,9 +32,9 @@
 #include <grpc/support/log.h>
 #include <grpc/support/thd.h>
 #include <grpc/support/time.h>
-#include <grpc/support/tls.h>
 
 #include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/iomgr/port.h"
 #include "src/proto/grpc/health/v1/health.grpc.pb.h"
 #include "src/proto/grpc/testing/duplicate/echo_duplicate.grpc.pb.h"
diff --git a/test/cpp/end2end/client_lb_end2end_test.cc b/test/cpp/end2end/client_lb_end2end_test.cc
index ee5adbc..386d18e 100644
--- a/test/cpp/end2end/client_lb_end2end_test.cc
+++ b/test/cpp/end2end/client_lb_end2end_test.cc
@@ -40,6 +40,7 @@
 #include "src/core/lib/backoff/backoff.h"
 #include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gprpp/debug_location.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
 
 #include "src/proto/grpc/testing/echo.grpc.pb.h"
 #include "test/core/util/port.h"
@@ -119,11 +120,11 @@
   }
 
   void SetUp() override {
-    response_generator_ = grpc_fake_resolver_response_generator_create();
+    response_generator_ =
+        grpc_core::MakeRefCounted<grpc_core::FakeResolverResponseGenerator>();
   }
 
   void TearDown() override {
-    grpc_fake_resolver_response_generator_unref(response_generator_);
     for (size_t i = 0; i < servers_.size(); ++i) {
       servers_[i]->Shutdown();
     }
@@ -153,13 +154,9 @@
       grpc_uri_destroy(lb_uri);
       gpr_free(lb_uri_str);
     }
-    const grpc_arg fake_addresses =
-        grpc_lb_addresses_create_channel_arg(addresses);
-    grpc_channel_args* fake_result =
-        grpc_channel_args_copy_and_add(nullptr, &fake_addresses, 1);
-    grpc_fake_resolver_response_generator_set_response(response_generator_,
-                                                       fake_result);
-    grpc_channel_args_destroy(fake_result);
+    grpc_arg fake_addresses = grpc_lb_addresses_create_channel_arg(addresses);
+    grpc_channel_args fake_result = {1, &fake_addresses};
+    response_generator_->SetResponse(&fake_result);
     grpc_lb_addresses_destroy(addresses);
   }
 
@@ -178,13 +175,9 @@
       grpc_uri_destroy(lb_uri);
       gpr_free(lb_uri_str);
     }
-    const grpc_arg fake_addresses =
-        grpc_lb_addresses_create_channel_arg(addresses);
-    grpc_channel_args* fake_result =
-        grpc_channel_args_copy_and_add(nullptr, &fake_addresses, 1);
-    grpc_fake_resolver_response_generator_set_response_upon_error(
-        response_generator_, fake_result);
-    grpc_channel_args_destroy(fake_result);
+    grpc_arg fake_addresses = grpc_lb_addresses_create_channel_arg(addresses);
+    grpc_channel_args fake_result = {1, &fake_addresses};
+    response_generator_->SetReresolutionResponse(&fake_result);
     grpc_lb_addresses_destroy(addresses);
   }
 
@@ -206,7 +199,7 @@
       args.SetLoadBalancingPolicyName(lb_policy_name);
     }  // else, default to pick first
     args.SetPointer(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR,
-                    response_generator_);
+                    response_generator_.get());
     return CreateCustomChannel("fake:///", InsecureChannelCredentials(), args);
   }
 
@@ -323,7 +316,8 @@
   const grpc::string server_host_;
   std::unique_ptr<grpc::testing::EchoTestService::Stub> stub_;
   std::vector<std::unique_ptr<ServerData>> servers_;
-  grpc_fake_resolver_response_generator* response_generator_;
+  grpc_core::RefCountedPtr<grpc_core::FakeResolverResponseGenerator>
+      response_generator_;
   const grpc::string kRequestMessage_;
 };
 
diff --git a/test/cpp/end2end/grpclb_end2end_test.cc b/test/cpp/end2end/grpclb_end2end_test.cc
index 5591acf..89c9c2b 100644
--- a/test/cpp/end2end/grpclb_end2end_test.cc
+++ b/test/cpp/end2end/grpclb_end2end_test.cc
@@ -35,6 +35,7 @@
 
 #include "src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h"
 #include "src/core/lib/gpr/env.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/sockaddr.h"
 
 #include "test/core/util/port.h"
@@ -339,7 +340,8 @@
   }
 
   void SetUp() override {
-    response_generator_ = grpc_fake_resolver_response_generator_create();
+    response_generator_ =
+        grpc_core::MakeRefCounted<grpc_core::FakeResolverResponseGenerator>();
     // Start the backends.
     for (size_t i = 0; i < num_backends_; ++i) {
       backends_.emplace_back(new BackendServiceImpl());
@@ -363,7 +365,6 @@
     for (size_t i = 0; i < balancers_.size(); ++i) {
       if (balancers_[i]->Shutdown()) balancer_servers_[i].Shutdown();
     }
-    grpc_fake_resolver_response_generator_unref(response_generator_);
   }
 
   void SetNextResolutionAllBalancers() {
@@ -378,7 +379,7 @@
     ChannelArguments args;
     args.SetGrpclbFallbackTimeout(fallback_timeout);
     args.SetPointer(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR,
-                    response_generator_);
+                    response_generator_.get());
     std::ostringstream uri;
     uri << "fake:///servername_not_used";
     channel_ =
@@ -478,20 +479,18 @@
         CreateLbAddressesFromAddressDataList(address_data);
     grpc_arg fake_addresses = grpc_lb_addresses_create_channel_arg(addresses);
     grpc_channel_args fake_result = {1, &fake_addresses};
-    grpc_fake_resolver_response_generator_set_response(response_generator_,
-                                                       &fake_result);
+    response_generator_->SetResponse(&fake_result);
     grpc_lb_addresses_destroy(addresses);
   }
 
-  void SetNextResolutionUponError(
+  void SetNextReresolutionResponse(
       const std::vector<AddressData>& address_data) {
     grpc_core::ExecCtx exec_ctx;
     grpc_lb_addresses* addresses =
         CreateLbAddressesFromAddressDataList(address_data);
     grpc_arg fake_addresses = grpc_lb_addresses_create_channel_arg(addresses);
     grpc_channel_args fake_result = {1, &fake_addresses};
-    grpc_fake_resolver_response_generator_set_response_upon_error(
-        response_generator_, &fake_result);
+    response_generator_->SetReresolutionResponse(&fake_result);
     grpc_lb_addresses_destroy(addresses);
   }
 
@@ -593,7 +592,8 @@
   std::vector<std::unique_ptr<BalancerServiceImpl>> balancers_;
   std::vector<ServerThread<BackendService>> backend_servers_;
   std::vector<ServerThread<BalancerService>> balancer_servers_;
-  grpc_fake_resolver_response_generator* response_generator_;
+  grpc_core::RefCountedPtr<grpc_core::FakeResolverResponseGenerator>
+      response_generator_;
   const grpc::string kRequestMessage_ = "Live long and prosper.";
 };
 
diff --git a/test/cpp/grpclb/grpclb_test.cc b/test/cpp/grpclb/grpclb_test.cc
index 4a9fd7c..06ab8c3 100644
--- a/test/cpp/grpclb/grpclb_test.cc
+++ b/test/cpp/grpclb/grpclb_test.cc
@@ -43,6 +43,7 @@
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/tmpfile.h"
+#include "src/core/lib/gprpp/ref_counted_ptr.h"
 #include "src/core/lib/iomgr/sockaddr.h"
 #include "src/core/lib/security/credentials/fake/fake_credentials.h"
 #include "src/core/lib/surface/channel.h"
@@ -558,8 +559,8 @@
   const char* backends_name = lb_server->servers_hostport;
   gpr_asprintf(&expected_target_names, "%s;%s", backends_name, BALANCERS_NAME);
 
-  grpc_fake_resolver_response_generator* response_generator =
-      grpc_fake_resolver_response_generator_create();
+  auto response_generator =
+      grpc_core::MakeRefCounted<grpc_core::FakeResolverResponseGenerator>();
 
   grpc_lb_addresses* addresses = grpc_lb_addresses_create(1, nullptr);
   char* lb_uri_str;
@@ -578,25 +579,22 @@
       grpc_channel_args_copy_and_add(nullptr, &fake_addresses, 1);
   grpc_lb_addresses_destroy(addresses);
 
-  const grpc_arg new_args[] = {
+  grpc_arg new_args[] = {
       grpc_fake_transport_expected_targets_arg(expected_target_names),
-      grpc_fake_resolver_response_generator_arg(response_generator)};
+      grpc_core::FakeResolverResponseGenerator::MakeChannelArg(
+          response_generator.get())};
 
-  grpc_channel_args* args = grpc_channel_args_copy_and_add(
-      nullptr, new_args, GPR_ARRAY_SIZE(new_args));
-  gpr_free(expected_target_names);
+  grpc_channel_args args = {GPR_ARRAY_SIZE(new_args), new_args};
 
   cf->cq = grpc_completion_queue_create_for_next(nullptr);
   grpc_channel_credentials* fake_creds =
       grpc_fake_transport_security_credentials_create();
   cf->client =
-      grpc_secure_channel_create(fake_creds, cf->server_uri, args, nullptr);
-  grpc_fake_resolver_response_generator_set_response(response_generator,
-                                                     fake_result);
+      grpc_secure_channel_create(fake_creds, cf->server_uri, &args, nullptr);
+  response_generator->SetResponse(fake_result);
   grpc_channel_args_destroy(fake_result);
   grpc_channel_credentials_unref(fake_creds);
-  grpc_channel_args_destroy(args);
-  grpc_fake_resolver_response_generator_unref(response_generator);
+  gpr_free(expected_target_names);
 }
 
 static void teardown_client(client_fixture* cf) {
diff --git a/test/cpp/naming/resolver_component_test.cc b/test/cpp/naming/resolver_component_test.cc
index aad2fa5..f4be064 100644
--- a/test/cpp/naming/resolver_component_test.cc
+++ b/test/cpp/naming/resolver_component_test.cc
@@ -39,6 +39,7 @@
 #include "src/core/lib/gpr/env.h"
 #include "src/core/lib/gpr/host_port.h"
 #include "src/core/lib/gpr/string.h"
+#include "src/core/lib/gprpp/orphanable.h"
 #include "src/core/lib/iomgr/combiner.h"
 #include "src/core/lib/iomgr/executor.h"
 #include "src/core/lib/iomgr/iomgr.h"
@@ -61,11 +62,9 @@
 
 DEFINE_string(target_name, "", "Target name to resolve.");
 DEFINE_string(expected_addrs, "",
-              "Comma-separated list of expected "
-              "'<ip0:port0>,<is_balancer0>;<ip1:port1>,<is_balancer1>;...' "
-              "addresses of "
-              "backend and/or balancers. 'is_balancer' should be bool, i.e. "
-              "true or false.");
+              "List of expected backend or balancer addresses in the form "
+              "'<ip0:port0>,<is_balancer0>;<ip1:port1>,<is_balancer1>;...'. "
+              "'is_balancer' should be bool, i.e. true or false.");
 DEFINE_string(expected_chosen_service_config, "",
               "Expected service config json string that gets chosen (no "
               "whitespace). Empty for none.");
@@ -102,12 +101,10 @@
     // get the next <ip>,<port> (v4 or v6)
     size_t next_comma = expected_addrs.find(",");
     if (next_comma == std::string::npos) {
-      gpr_log(
-          GPR_ERROR,
-          "Missing ','. Expected_addrs arg should be a semi-colon-separated "
-          "list of "
-          "<ip-port>,<bool> pairs. Left-to-be-parsed arg is |%s|",
-          expected_addrs.c_str());
+      gpr_log(GPR_ERROR,
+              "Missing ','. Expected_addrs arg should be a semicolon-separated "
+              "list of <ip-port>,<bool> pairs. Left-to-be-parsed arg is |%s|",
+              expected_addrs.c_str());
       abort();
     }
     std::string next_addr = expected_addrs.substr(0, next_comma);
@@ -125,7 +122,7 @@
   }
   if (out.size() == 0) {
     gpr_log(GPR_ERROR,
-            "expected_addrs arg should be a comma-separated list of "
+            "expected_addrs arg should be a semicolon-separated list of "
             "<ip-port>,<bool> pairs");
     abort();
   }
@@ -287,17 +284,16 @@
                       FLAGS_local_dns_server_address.c_str(),
                       FLAGS_target_name.c_str()));
   // create resolver and resolve
-  grpc_resolver* resolver =
-      grpc_resolver_create(whole_uri, nullptr, args.pollset_set, args.lock);
+  grpc_core::OrphanablePtr<grpc_core::Resolver> resolver =
+      grpc_core::ResolverRegistry::CreateResolver(whole_uri, nullptr,
+                                                  args.pollset_set, args.lock);
   gpr_free(whole_uri);
   grpc_closure on_resolver_result_changed;
   GRPC_CLOSURE_INIT(&on_resolver_result_changed, CheckResolverResultLocked,
                     (void*)&args, grpc_combiner_scheduler(args.lock));
-  grpc_resolver_next_locked(resolver, &args.channel_args,
-                            &on_resolver_result_changed);
+  resolver->NextLocked(&args.channel_args, &on_resolver_result_changed);
   grpc_core::ExecCtx::Get()->Flush();
   PollPollsetUntilRequestDone(&args);
-  GRPC_RESOLVER_UNREF(resolver, nullptr);
   ArgsFinish(&args);
 }
 
diff --git a/tools/doxygen/Doxyfile.c++ b/tools/doxygen/Doxyfile.c++
index 0f8344d..4877535 100644
--- a/tools/doxygen/Doxyfile.c++
+++ b/tools/doxygen/Doxyfile.c++
@@ -921,10 +921,6 @@
 include/grpc/support/sync_windows.h \
 include/grpc/support/thd.h \
 include/grpc/support/time.h \
-include/grpc/support/tls.h \
-include/grpc/support/tls_gcc.h \
-include/grpc/support/tls_msvc.h \
-include/grpc/support/tls_pthread.h \
 include/grpc/support/workaround_list.h
 
 # This tag can be used to specify the character encoding of the source files
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index 8e1f3bc..8dd282a 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -922,10 +922,6 @@
 include/grpc/support/sync_windows.h \
 include/grpc/support/thd.h \
 include/grpc/support/time.h \
-include/grpc/support/tls.h \
-include/grpc/support/tls_gcc.h \
-include/grpc/support/tls_msvc.h \
-include/grpc/support/tls_pthread.h \
 include/grpc/support/workaround_list.h \
 src/core/ext/transport/inproc/inproc_transport.h \
 src/core/lib/avl/avl.h \
@@ -958,6 +954,10 @@
 src/core/lib/gpr/string_windows.h \
 src/core/lib/gpr/thd_internal.h \
 src/core/lib/gpr/time_precise.h \
+src/core/lib/gpr/tls.h \
+src/core/lib/gpr/tls_gcc.h \
+src/core/lib/gpr/tls_msvc.h \
+src/core/lib/gpr/tls_pthread.h \
 src/core/lib/gpr/tmpfile.h \
 src/core/lib/gpr/useful.h \
 src/core/lib/gprpp/abstract.h \
diff --git a/tools/doxygen/Doxyfile.core b/tools/doxygen/Doxyfile.core
index 80f9c0c..d398783 100644
--- a/tools/doxygen/Doxyfile.core
+++ b/tools/doxygen/Doxyfile.core
@@ -861,10 +861,6 @@
 include/grpc/support/sync_windows.h \
 include/grpc/support/thd.h \
 include/grpc/support/time.h \
-include/grpc/support/tls.h \
-include/grpc/support/tls_gcc.h \
-include/grpc/support/tls_msvc.h \
-include/grpc/support/tls_pthread.h \
 include/grpc/support/workaround_list.h
 
 # This tag can be used to specify the character encoding of the source files
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index e8280d9..c563f40 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -861,10 +861,6 @@
 include/grpc/support/sync_windows.h \
 include/grpc/support/thd.h \
 include/grpc/support/time.h \
-include/grpc/support/tls.h \
-include/grpc/support/tls_gcc.h \
-include/grpc/support/tls_msvc.h \
-include/grpc/support/tls_pthread.h \
 include/grpc/support/workaround_list.h \
 src/core/README.md \
 src/core/ext/README.md \
@@ -927,7 +923,6 @@
 src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h \
 src/core/ext/filters/client_channel/resolver/sockaddr/README.md \
 src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc \
-src/core/ext/filters/client_channel/resolver_factory.cc \
 src/core/ext/filters/client_channel/resolver_factory.h \
 src/core/ext/filters/client_channel/resolver_registry.cc \
 src/core/ext/filters/client_channel/resolver_registry.h \
@@ -1108,7 +1103,11 @@
 src/core/lib/gpr/time_precise.cc \
 src/core/lib/gpr/time_precise.h \
 src/core/lib/gpr/time_windows.cc \
+src/core/lib/gpr/tls.h \
+src/core/lib/gpr/tls_gcc.h \
+src/core/lib/gpr/tls_msvc.h \
 src/core/lib/gpr/tls_pthread.cc \
+src/core/lib/gpr/tls_pthread.h \
 src/core/lib/gpr/tmpfile.h \
 src/core/lib/gpr/tmpfile_msys.cc \
 src/core/lib/gpr/tmpfile_posix.cc \
diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json
index 03697f7..e1ac146 100644
--- a/tools/run_tests/generated/sources_and_headers.json
+++ b/tools/run_tests/generated/sources_and_headers.json
@@ -8259,10 +8259,6 @@
       "include/grpc/support/sync_windows.h", 
       "include/grpc/support/thd.h", 
       "include/grpc/support/time.h", 
-      "include/grpc/support/tls.h", 
-      "include/grpc/support/tls_gcc.h", 
-      "include/grpc/support/tls_msvc.h", 
-      "include/grpc/support/tls_pthread.h", 
       "src/core/lib/gpr/arena.h", 
       "src/core/lib/gpr/env.h", 
       "src/core/lib/gpr/fork.h", 
@@ -8274,6 +8270,10 @@
       "src/core/lib/gpr/string_windows.h", 
       "src/core/lib/gpr/thd_internal.h", 
       "src/core/lib/gpr/time_precise.h", 
+      "src/core/lib/gpr/tls.h", 
+      "src/core/lib/gpr/tls_gcc.h", 
+      "src/core/lib/gpr/tls_msvc.h", 
+      "src/core/lib/gpr/tls_pthread.h", 
       "src/core/lib/gpr/tmpfile.h", 
       "src/core/lib/gpr/useful.h", 
       "src/core/lib/gprpp/abstract.h", 
@@ -8305,10 +8305,6 @@
       "include/grpc/support/sync_windows.h", 
       "include/grpc/support/thd.h", 
       "include/grpc/support/time.h", 
-      "include/grpc/support/tls.h", 
-      "include/grpc/support/tls_gcc.h", 
-      "include/grpc/support/tls_msvc.h", 
-      "include/grpc/support/tls_pthread.h", 
       "src/core/lib/gpr/arena.h", 
       "src/core/lib/gpr/env.h", 
       "src/core/lib/gpr/fork.h", 
@@ -8320,6 +8316,10 @@
       "src/core/lib/gpr/string_windows.h", 
       "src/core/lib/gpr/thd_internal.h", 
       "src/core/lib/gpr/time_precise.h", 
+      "src/core/lib/gpr/tls.h", 
+      "src/core/lib/gpr/tls_gcc.h", 
+      "src/core/lib/gpr/tls_msvc.h", 
+      "src/core/lib/gpr/tls_pthread.h", 
       "src/core/lib/gpr/tmpfile.h", 
       "src/core/lib/gpr/useful.h", 
       "src/core/lib/gprpp/abstract.h", 
@@ -8900,7 +8900,6 @@
       "src/core/ext/filters/client_channel/proxy_mapper_registry.h", 
       "src/core/ext/filters/client_channel/resolver.cc", 
       "src/core/ext/filters/client_channel/resolver.h", 
-      "src/core/ext/filters/client_channel/resolver_factory.cc", 
       "src/core/ext/filters/client_channel/resolver_factory.h", 
       "src/core/ext/filters/client_channel/resolver_registry.cc", 
       "src/core/ext/filters/client_channel/resolver_registry.h", 
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index 322b3f6..ae5d6cb 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -105337,6 +105337,29 @@
   }, 
   {
     "args": [
+      "test/core/end2end/fuzzers/api_fuzzer_corpus/poc-2d730ebd78b3052e4367ad0d485208dcb205482cbcd6289f17907989b8de1fba"
+    ], 
+    "ci_platforms": [
+      "linux"
+    ], 
+    "cpu_cost": 0.1, 
+    "exclude_configs": [
+      "tsan"
+    ], 
+    "exclude_iomgrs": [
+      "uv"
+    ], 
+    "flaky": false, 
+    "language": "c", 
+    "name": "api_fuzzer_one_entry", 
+    "platforms": [
+      "mac", 
+      "linux"
+    ], 
+    "uses_polling": false
+  }, 
+  {
+    "args": [
       "test/core/end2end/fuzzers/api_fuzzer_corpus/poc-c726ee220e980ed6ad17809fd9efe2844ee61555ac08e4f88afd8901cc2dd53a"
     ], 
     "ci_platforms": [