You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by jo...@apache.org on 2020/02/05 01:25:16 UTC

[impala] branch master updated (7b280e5 -> 19a4d8f)

This is an automated email from the ASF dual-hosted git repository.

joemcdonnell pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git.


    from 7b280e5  IMPALA-9349: free output_unmatched_batch_ buffers promptly in PHJ
     new 4024d82  IMPALA-9336: [DOCS] Primary and foreign key constraint syntax
     new 66e6879  IMPALA-9346: Fix TestImpalaShell.test_config_file failing issue on CentOS6/Python 2.6
     new fe001a7  IMPALA-9335 (part 1): Rebase copied Kudu source code
     new 19a4d8f  IMPALA-9335 (part 2): Fix rebased KRPC to compile

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 be/src/kudu/rpc/CMakeLists.txt                     |  15 +-
 be/src/kudu/rpc/acceptor_pool.cc                   |   3 +-
 be/src/kudu/rpc/acceptor_pool.h                    |   7 +-
 be/src/kudu/rpc/blocking_ops.h                     |   6 +-
 be/src/kudu/rpc/client_negotiation.cc              |  46 +-
 be/src/kudu/rpc/connection.cc                      |  31 +-
 be/src/kudu/rpc/connection.h                       |  17 +-
 be/src/kudu/rpc/constants.h                        |   6 +-
 be/src/kudu/rpc/inbound_call.cc                    |  12 +-
 be/src/kudu/rpc/inbound_call.h                     |  21 +-
 be/src/kudu/rpc/messenger.cc                       |  30 +-
 be/src/kudu/rpc/messenger.h                        |  16 +-
 be/src/kudu/rpc/mt-rpc-test.cc                     |  12 +-
 be/src/kudu/rpc/negotiation-test.cc                |  24 +-
 be/src/kudu/rpc/negotiation.h                      |   4 +-
 be/src/kudu/rpc/outbound_call.cc                   |   8 +-
 be/src/kudu/rpc/outbound_call.h                    |  14 +-
 be/src/kudu/rpc/protoc-gen-krpc.cc                 | 247 +++----
 be/src/kudu/rpc/proxy.h                            |   6 +-
 be/src/kudu/rpc/reactor.cc                         |   5 +-
 be/src/kudu/rpc/reactor.h                          |   5 +-
 be/src/kudu/rpc/remote_method.h                    |   5 +-
 be/src/kudu/rpc/response_callback.h                |  10 +-
 be/src/kudu/rpc/result_tracker.cc                  |   3 +-
 be/src/kudu/rpc/retriable_rpc.h                    | 100 ++-
 be/src/kudu/rpc/rpc-bench.cc                       |  13 +-
 be/src/kudu/rpc/rpc-test-base.h                    |  30 +-
 be/src/kudu/rpc/rpc-test.cc                        | 121 ++--
 be/src/kudu/rpc/rpc.cc                             |  47 +-
 be/src/kudu/rpc/rpc.h                              |  74 ++-
 be/src/kudu/rpc/rpc_context.cc                     |  19 +-
 be/src/kudu/rpc/rpc_context.h                      |  26 +-
 be/src/kudu/rpc/rpc_controller.cc                  |   1 -
 be/src/kudu/rpc/rpc_controller.h                   |   4 +-
 be/src/kudu/rpc/rpc_header.proto                   |  11 +-
 be/src/kudu/rpc/rpc_introspection.proto            |   6 +-
 be/src/kudu/rpc/rpc_service.h                      |  11 +-
 be/src/kudu/rpc/rpc_sidecar.h                      |   6 +-
 be/src/kudu/rpc/rpc_stub-test.cc                   |  48 +-
 be/src/kudu/rpc/rpc_verification_util.cc           |  67 ++
 .../test_pass.h => rpc/rpc_verification_util.h}    |  24 +-
 be/src/kudu/rpc/rpcz_store.cc                      |  14 +-
 be/src/kudu/rpc/sasl_common.cc                     |  11 +
 be/src/kudu/rpc/sasl_common.h                      |  17 +-
 be/src/kudu/rpc/sasl_helper.h                      |   6 +-
 be/src/kudu/rpc/serialization.cc                   |   2 +-
 be/src/kudu/rpc/serialization.h                    |   5 +-
 be/src/kudu/rpc/server_negotiation.cc              |  69 +-
 be/src/kudu/rpc/service_if.cc                      |  12 +-
 be/src/kudu/rpc/service_if.h                       |   4 +-
 be/src/kudu/rpc/service_pool.cc                    |  18 +-
 be/src/kudu/rpc/service_pool.h                     |  14 +-
 be/src/kudu/rpc/service_queue.h                    |   5 +-
 be/src/kudu/rpc/transfer.cc                        |  13 +-
 be/src/kudu/rpc/transfer.h                         |   5 +-
 be/src/kudu/security/CMakeLists.txt                |   5 +-
 be/src/kudu/security/test/mini_kdc.cc              |   4 +-
 be/src/kudu/util/CMakeLists.txt                    | 185 ++++--
 be/src/kudu/util/async_util-test.cc                |  90 ++-
 be/src/kudu/util/async_util.h                      |   9 +-
 be/src/kudu/util/bitmap-test.cc                    | 134 +++-
 be/src/kudu/util/bitmap.cc                         |  37 +-
 be/src/kudu/util/bitmap.h                          |  35 +-
 be/src/kudu/util/bitset-test.cc                    | 170 +++++
 be/src/kudu/util/bitset.h                          | 202 ++++++
 be/src/kudu/util/block_bloom_filter-test.cc        | 243 +++++++
 be/src/kudu/util/block_bloom_filter.cc             | 199 ++++++
 be/src/kudu/util/block_bloom_filter.h              | 231 +++++++
 be/src/kudu/util/block_bloom_filter_avx2.cc        |  78 +++
 .../{cache_metrics.cc => block_cache_metrics.cc}   |  48 +-
 .../kerberos_util.h => util/block_cache_metrics.h} |  14 +-
 be/src/kudu/util/bloom_filter.h                    |  27 +-
 be/src/kudu/util/cache-bench.cc                    |  20 +-
 be/src/kudu/util/cache-test.cc                     | 455 ++++++++++---
 be/src/kudu/util/cache.cc                          | 497 +++++++++-----
 be/src/kudu/util/cache.h                           | 313 ++++++---
 be/src/kudu/util/cache_metrics.h                   |  11 +-
 be/src/kudu/util/char_util-test.cc                 | 126 ++++
 be/src/kudu/util/char_util.cc                      |  71 ++
 be/src/kudu/util/{zlib.h => char_util.h}           |  28 +-
 be/src/kudu/util/cloud/instance_detector-test.cc   |  97 +++
 be/src/kudu/util/cloud/instance_detector.cc        | 127 ++++
 be/src/kudu/util/cloud/instance_detector.h         |  95 +++
 be/src/kudu/util/cloud/instance_metadata.cc        | 205 ++++++
 be/src/kudu/util/cloud/instance_metadata.h         | 166 +++++
 be/src/kudu/util/compression/compression-test.cc   |  97 ++-
 be/src/kudu/util/compression/compression_codec.cc  |  18 +-
 be/src/kudu/util/countdown_latch-test.cc           |   5 +-
 be/src/kudu/util/countdown_latch.h                 |   2 +-
 .../{version_util-test.cc => curl_util-test.cc}    |  64 +-
 be/src/kudu/util/curl_util.cc                      |  66 +-
 be/src/kudu/util/curl_util.h                       |  29 +
 be/src/kudu/util/debug/trace_event_impl.cc         |  18 +-
 be/src/kudu/util/debug/trace_logging.h             |   5 +
 be/src/kudu/util/easy_json.cc                      |  73 +--
 be/src/kudu/util/env-test.cc                       |  60 +-
 be/src/kudu/util/env.cc                            |  11 +-
 be/src/kudu/util/env.h                             |  87 ++-
 be/src/kudu/util/env_posix.cc                      | 126 ++--
 be/src/kudu/util/env_util.cc                       |  65 +-
 be/src/kudu/util/env_util.h                        |   9 +-
 be/src/kudu/util/faststring-test.cc                |  63 +-
 be/src/kudu/util/faststring.cc                     |   9 +-
 be/src/kudu/util/faststring.h                      |  17 +-
 be/src/kudu/util/fault_injection.cc                |   7 +-
 be/src/kudu/util/fault_injection.h                 |  15 +-
 be/src/kudu/util/file_cache-stress-test.cc         | 140 ++--
 be/src/kudu/util/file_cache-test.cc                | 176 ++++-
 be/src/kudu/util/file_cache.cc                     | 431 +++++++-----
 be/src/kudu/util/file_cache.h                      | 101 ++-
 be/src/kudu/util/file_cache_metrics.cc             |  78 +++
 .../kerberos_util.h => util/file_cache_metrics.h}  |  14 +-
 be/src/kudu/util/flag_tags-test.cc                 |  16 +-
 be/src/kudu/util/flag_tags.h                       |  10 +-
 be/src/kudu/util/flags-test.cc                     |   9 +-
 be/src/kudu/util/flags.cc                          |  35 +-
 be/src/kudu/util/flags.h                           |   7 +-
 .../{compression/compression.proto => hash.proto}  |  12 +-
 be/src/kudu/util/hash_util-test.cc                 |  29 +
 be/src/kudu/util/hash_util.h                       |  62 +-
 be/src/kudu/util/hdr_histogram-test.cc             |  28 +
 be/src/kudu/util/hdr_histogram.cc                  |  80 ++-
 be/src/kudu/util/hdr_histogram.h                   |  16 +-
 be/src/kudu/util/high_water_mark.h                 |   4 +
 be/src/kudu/util/init.cc                           |   6 +-
 be/src/kudu/util/init.h                            |  15 +-
 be/src/kudu/util/int128_util.h                     |   3 +-
 be/src/kudu/util/interval_tree-inl.h               |  44 +-
 be/src/kudu/util/interval_tree-test.cc             | 132 +++-
 be/src/kudu/util/interval_tree.h                   |  12 +-
 be/src/kudu/util/jsonreader-test.cc                | 193 ++++++
 be/src/kudu/util/jsonreader.cc                     | 123 +++-
 be/src/kudu/util/jsonreader.h                      |  18 +
 be/src/kudu/util/jsonwriter-test.cc                |   8 +-
 be/src/kudu/util/jsonwriter.cc                     |  10 +-
 be/src/kudu/util/jsonwriter.h                      |   2 +-
 be/src/kudu/util/logging-test.cc                   |  17 +
 be/src/kudu/util/logging.cc                        |  17 +-
 be/src/kudu/util/logging.h                         |  14 +-
 be/src/kudu/util/maintenance_manager-test.cc       | 284 +++++++-
 be/src/kudu/util/maintenance_manager.cc            | 316 +++++----
 be/src/kudu/util/maintenance_manager.h             |  68 +-
 be/src/kudu/util/maintenance_manager.proto         |  11 +-
 be/src/kudu/util/map-util-test.cc                  | 111 +++-
 be/src/kudu/util/mem_tracker-test.cc               | 143 ++++
 be/src/kudu/util/mem_tracker.cc                    |  42 +-
 be/src/kudu/util/mem_tracker.h                     |  11 +-
 .../compression.proto => mem_tracker.proto}        |  24 +-
 be/src/kudu/util/metrics-test.cc                   | 727 ++++++++++++++++++++-
 be/src/kudu/util/metrics.cc                        | 409 ++++++++++--
 be/src/kudu/util/metrics.h                         | 652 ++++++++++++++----
 be/src/kudu/util/minidump.cc                       |  32 +-
 be/src/kudu/util/monotime-test.cc                  |  14 +-
 be/src/kudu/util/monotime.cc                       |  22 +-
 be/src/kudu/util/monotime.h                        |  31 +-
 be/src/kudu/util/mt-metrics-test.cc                |   6 +-
 be/src/kudu/util/net/dns_resolver-test.cc          |  60 +-
 be/src/kudu/util/net/dns_resolver.cc               | 112 +++-
 be/src/kudu/util/net/dns_resolver.h                |  77 ++-
 be/src/kudu/util/net/net_util-test.cc              |  46 +-
 be/src/kudu/util/net/net_util.cc                   | 125 +++-
 be/src/kudu/util/net/net_util.h                    |  93 ++-
 be/src/kudu/util/net/sockaddr.cc                   |  20 +-
 be/src/kudu/util/net/sockaddr.h                    |   5 +
 be/src/kudu/util/net/socket.cc                     |   7 +-
 be/src/kudu/util/nvm_cache.cc                      | 382 +++++++----
 be/src/kudu/util/nvm_cache.h                       |  28 +-
 be/src/kudu/util/once-test.cc                      |  57 +-
 be/src/kudu/util/once.cc                           |   8 +
 be/src/kudu/util/once.h                            |  47 +-
 be/src/kudu/util/os-util.cc                        |  23 +-
 be/src/kudu/util/pb_util-test.cc                   |   4 +-
 be/src/kudu/util/pb_util.cc                        |   3 +-
 be/src/kudu/util/pb_util.h                         |   4 +-
 be/src/kudu/util/process_memory.cc                 |  20 +-
 be/src/kudu/util/process_memory.h                  |   3 +
 be/src/kudu/util/random-test.cc                    |   9 +-
 be/src/kudu/util/random.h                          |  41 --
 be/src/kudu/util/random_util.h                     |  64 +-
 be/src/kudu/util/rle-encoding.h                    |  12 +-
 be/src/kudu/util/rle-test.cc                       |  43 ++
 be/src/kudu/util/rolling_log-test.cc               |   6 +-
 be/src/kudu/util/rolling_log.cc                    |  12 +-
 be/src/kudu/util/rolling_log.h                     |   6 +-
 be/src/kudu/util/sanitizer_options.cc              | 218 ++++++
 be/src/kudu/util/scoped_cleanup.h                  |   2 +-
 be/src/kudu/util/signal.cc                         |  28 +-
 be/src/kudu/util/spinlock_profiling.cc             |   1 +
 be/src/kudu/util/status.cc                         |   3 +
 be/src/kudu/util/status.h                          |  35 +-
 be/src/kudu/util/stopwatch.h                       |  12 +
 be/src/kudu/util/string_case.cc                    |  24 +-
 be/src/kudu/util/string_case.h                     |  30 +-
 be/src/kudu/util/subprocess-test.cc                |  50 +-
 be/src/kudu/util/subprocess.cc                     |  74 ++-
 be/src/kudu/util/subprocess.h                      |   7 +-
 be/src/kudu/util/test_macros.h                     |  26 +-
 be/src/kudu/util/test_util.cc                      |  45 +-
 be/src/kudu/util/testdata/char_truncate_ascii.txt  |  35 +
 be/src/kudu/util/testdata/char_truncate_utf8.txt   | 258 ++++++++
 be/src/kudu/util/thread.cc                         | 264 +++++---
 be/src/kudu/util/thread.h                          |  31 +-
 be/src/kudu/util/threadpool-test.cc                |  17 +-
 be/src/kudu/util/threadpool.cc                     |   5 +-
 be/src/kudu/util/threadpool.h                      |   9 +-
 be/src/kudu/util/trace-test.cc                     |  27 +-
 be/src/kudu/util/trace.h                           |  17 +-
 be/src/kudu/util/ttl_cache-test.cc                 | 653 ++++++++++++++++++
 be/src/kudu/util/ttl_cache.h                       | 339 ++++++++++
 .../kerberos_util.h => util/ttl_cache_metrics.h}   |  17 +-
 be/src/kudu/util/user.cc                           |   9 +
 be/src/kudu/util/version_util-test.cc              |  96 ++-
 be/src/kudu/util/version_util.cc                   |  69 +-
 be/src/kudu/util/version_util.h                    |  21 +-
 ...{test_util_prod.cc => web_callback_registry.cc} |   8 +-
 be/src/kudu/util/web_callback_registry.h           |  29 +-
 be/src/kudu/util/yamlreader-test.cc                | 176 +++++
 be/src/kudu/util/yamlreader.cc                     | 100 +++
 be/src/kudu/util/yamlreader.h                      | 129 ++++
 be/src/kudu/util/zlib.cc                           |   6 +-
 be/src/kudu/util/zlib.h                            |   4 +
 be/src/rpc/impala-service-pool.cc                  |   7 +-
 be/src/rpc/impala-service-pool.h                   |   4 +-
 be/src/runtime/io/data-cache.cc                    |  48 +-
 be/src/runtime/io/data-cache.h                     |   5 +-
 be/src/util/webserver.cc                           |   2 +
 bin/rat_exclude_files.txt                          |   3 +
 cmake_modules/kudu_cmake_fns.txt                   |   9 +
 docs/topics/impala_create_table.xml                |  39 +-
 tests/custom_cluster/test_restart_services.py      |   2 +-
 tests/shell/impalarc_with_error2                   |   2 +-
 ...mpalarc_with_error2 => impalarc_with_warnings2} |   2 +-
 tests/shell/test_shell_commandline.py              |  11 +-
 233 files changed, 12261 insertions(+), 2953 deletions(-)
 create mode 100644 be/src/kudu/rpc/rpc_verification_util.cc
 copy be/src/kudu/{security/test/test_pass.h => rpc/rpc_verification_util.h} (58%)
 create mode 100644 be/src/kudu/util/bitset-test.cc
 create mode 100644 be/src/kudu/util/bitset.h
 create mode 100644 be/src/kudu/util/block_bloom_filter-test.cc
 create mode 100644 be/src/kudu/util/block_bloom_filter.cc
 create mode 100644 be/src/kudu/util/block_bloom_filter.h
 create mode 100644 be/src/kudu/util/block_bloom_filter_avx2.cc
 rename be/src/kudu/util/{cache_metrics.cc => block_cache_metrics.cc} (63%)
 copy be/src/kudu/{security/kerberos_util.h => util/block_cache_metrics.h} (79%)
 create mode 100644 be/src/kudu/util/char_util-test.cc
 create mode 100644 be/src/kudu/util/char_util.cc
 copy be/src/kudu/util/{zlib.h => char_util.h} (62%)
 create mode 100644 be/src/kudu/util/cloud/instance_detector-test.cc
 create mode 100644 be/src/kudu/util/cloud/instance_detector.cc
 create mode 100644 be/src/kudu/util/cloud/instance_detector.h
 create mode 100644 be/src/kudu/util/cloud/instance_metadata.cc
 create mode 100644 be/src/kudu/util/cloud/instance_metadata.h
 copy be/src/kudu/util/{version_util-test.cc => curl_util-test.cc} (50%)
 create mode 100644 be/src/kudu/util/file_cache_metrics.cc
 copy be/src/kudu/{security/kerberos_util.h => util/file_cache_metrics.h} (79%)
 copy be/src/kudu/util/{compression/compression.proto => hash.proto} (86%)
 copy be/src/kudu/util/{compression/compression.proto => mem_tracker.proto} (57%)
 create mode 100644 be/src/kudu/util/sanitizer_options.cc
 create mode 100644 be/src/kudu/util/testdata/char_truncate_ascii.txt
 create mode 100644 be/src/kudu/util/testdata/char_truncate_utf8.txt
 create mode 100644 be/src/kudu/util/ttl_cache-test.cc
 create mode 100644 be/src/kudu/util/ttl_cache.h
 copy be/src/kudu/{security/kerberos_util.h => util/ttl_cache_metrics.h} (70%)
 copy be/src/kudu/util/{test_util_prod.cc => web_callback_registry.cc} (80%)
 create mode 100644 be/src/kudu/util/yamlreader-test.cc
 create mode 100644 be/src/kudu/util/yamlreader.cc
 create mode 100644 be/src/kudu/util/yamlreader.h
 copy tests/shell/{impalarc_with_error2 => impalarc_with_warnings2} (87%)


[impala] 02/04: IMPALA-9346: Fix TestImpalaShell.test_config_file failing issue on CentOS6/Python 2.6

Posted by jo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

joemcdonnell pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit 66e6879e8ccf18f9af82a49ff28cd95fe9fcf390
Author: wzhou-code <wz...@cloudera.com>
AuthorDate: Thu Jan 30 18:00:27 2020 -0800

    IMPALA-9346: Fix TestImpalaShell.test_config_file failing issue
    on CentOS6/Python 2.6
    
    ImpalaShell.test_config_file failed in negative test case, which
    ran impala shell with bad format config file - wrong option name and
    wrong option value. The testing code expect impala shell return both
    warning and error messages. But on CentOS6/Python 2.6, Impala shell
    only return error message. To fix it, separate the test cases as two
    test cases by running Impala shell in two different config file.
    
    Testing:
     - Passed all test cases in test_shell_commandline.py and
       test_shell_interactive.py.
     - Passed all core test in pre-review-test.
     - Passed EE tests in impala-private-parameterized with CentOS6.
    
    Change-Id: Ief5e825aa3baead5519132d47efcf0d5300860fd
    Reviewed-on: http://gerrit.cloudera.org:8080/15139
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 bin/rat_exclude_files.txt                                     |  1 +
 tests/shell/impalarc_with_error2                              |  2 +-
 tests/shell/{impalarc_with_error2 => impalarc_with_warnings2} |  2 +-
 tests/shell/test_shell_commandline.py                         | 11 ++++++++---
 4 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/bin/rat_exclude_files.txt b/bin/rat_exclude_files.txt
index ece2d3c..29798f8 100644
--- a/bin/rat_exclude_files.txt
+++ b/bin/rat_exclude_files.txt
@@ -148,6 +148,7 @@ tests/shell/impalarc_with_error
 tests/shell/impalarc_with_error2
 tests/shell/impalarc_with_query_options
 tests/shell/impalarc_with_warnings
+tests/shell/impalarc_with_warnings2
 tests/shell/shell.cmds
 tests/shell/shell2.cmds
 tests/shell/shell_case_sensitive.cmds
diff --git a/tests/shell/impalarc_with_error2 b/tests/shell/impalarc_with_error2
index b828c1e..34e4a37 100644
--- a/tests/shell/impalarc_with_error2
+++ b/tests/shell/impalarc_with_error2
@@ -2,7 +2,7 @@
 keyval=msg3=test
 verbose=true
 Q=DEFAULT_FILE_FORMAT=avro
-Live_Progress=true
+live_progress=true
 live_summary=maybe
 
 [impala.query_options]
diff --git a/tests/shell/impalarc_with_error2 b/tests/shell/impalarc_with_warnings2
similarity index 87%
copy from tests/shell/impalarc_with_error2
copy to tests/shell/impalarc_with_warnings2
index b828c1e..0c5ea38 100644
--- a/tests/shell/impalarc_with_error2
+++ b/tests/shell/impalarc_with_warnings2
@@ -3,7 +3,7 @@ keyval=msg3=test
 verbose=true
 Q=DEFAULT_FILE_FORMAT=avro
 Live_Progress=true
-live_summary=maybe
+liveSummary=false
 
 [impala.query_options]
 DEFAULT_FILE_FORMAT=text
diff --git a/tests/shell/test_shell_commandline.py b/tests/shell/test_shell_commandline.py
index f9cb5fd..d4417c0 100644
--- a/tests/shell/test_shell_commandline.py
+++ b/tests/shell/test_shell_commandline.py
@@ -586,11 +586,16 @@ class TestImpalaShell(ImpalaTestSuite):
     args = ['--config_file=%s/good_impalarc3' % QUERY_FILE_PATH, '--query=select 3']
     result = run_impala_shell_cmd(vector, args, expect_success=False)
     assert 'Live reporting is available for interactive mode only' in result.stderr
-    # bad formatting of config file
+    # testing config file related warning messages
+    args = ['--config_file=%s/impalarc_with_warnings2' % QUERY_FILE_PATH]
+    result = run_impala_shell_cmd(
+      vector, args, expect_success=True, wait_until_connected=False)
+    err_msg = ("WARNING: Unable to read configuration file correctly. "
+               "Ignoring unrecognized config option: 'Live_Progress'\n")
+    assert err_msg in result.stderr
+    # bad formatting of config file with invalid value
     args = ['--config_file={0}/impalarc_with_error2'.format(QUERY_FILE_PATH)]
     result = run_impala_shell_cmd(vector, args, expect_success=False)
-    err_msg = "Ignoring unrecognized config option"
-    assert err_msg in result.stderr
     err_msg = ("Unexpected value in configuration file. "
                "'maybe' is not a valid value for a boolean option.")
     assert err_msg in result.stderr


[impala] 03/04: IMPALA-9335 (part 1): Rebase copied Kudu source code

Posted by jo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

joemcdonnell pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit fe001a7ded5d4c3da6b698722b26085f83dfca4e
Author: Thomas Tauber-Marshall <tm...@cloudera.com>
AuthorDate: Wed Jan 22 16:52:59 2020 -0800

    IMPALA-9335 (part 1): Rebase copied Kudu source code
    
    Removed be/src/kudu/{util|security|rpc} and copied back over from Kudu
    commit: fc4ab691a502067bc4d5bdff30507cac7feb7cfe
    
    Change-Id: Ia0190f15b96563f5edc40065fa4690c467c50f83
    Reviewed-on: http://gerrit.cloudera.org:8080/15143
    Reviewed-by: Joe McDonnell <jo...@cloudera.com>
    Tested-by: Thomas Tauber-Marshall <tm...@cloudera.com>
---
 be/src/kudu/rpc/CMakeLists.txt                     |  20 +-
 be/src/kudu/rpc/acceptor_pool.cc                   |   3 +-
 be/src/kudu/rpc/acceptor_pool.h                    |   7 +-
 be/src/kudu/rpc/blocking_ops.h                     |   6 +-
 be/src/kudu/rpc/client_negotiation.cc              |  46 +-
 be/src/kudu/rpc/connection.cc                      |  31 +-
 be/src/kudu/rpc/connection.h                       |  17 +-
 be/src/kudu/rpc/constants.h                        |   6 +-
 be/src/kudu/rpc/inbound_call.cc                    |  12 +-
 be/src/kudu/rpc/inbound_call.h                     |  21 +-
 be/src/kudu/rpc/messenger.cc                       |  30 +-
 be/src/kudu/rpc/messenger.h                        |  16 +-
 be/src/kudu/rpc/mt-rpc-test.cc                     |  12 +-
 be/src/kudu/rpc/negotiation-test.cc                |  24 +-
 be/src/kudu/rpc/negotiation.h                      |   4 +-
 be/src/kudu/rpc/outbound_call.cc                   |   8 +-
 be/src/kudu/rpc/outbound_call.h                    |  14 +-
 be/src/kudu/rpc/protoc-gen-krpc.cc                 | 247 +++----
 be/src/kudu/rpc/proxy.h                            |   6 +-
 be/src/kudu/rpc/reactor.cc                         |   5 +-
 be/src/kudu/rpc/reactor.h                          |   5 +-
 be/src/kudu/rpc/remote_method.h                    |   5 +-
 be/src/kudu/rpc/response_callback.h                |  10 +-
 be/src/kudu/rpc/result_tracker.cc                  |   3 +-
 be/src/kudu/rpc/retriable_rpc.h                    | 100 ++-
 be/src/kudu/rpc/rpc-bench.cc                       |  13 +-
 be/src/kudu/rpc/rpc-test-base.h                    |  30 +-
 be/src/kudu/rpc/rpc-test.cc                        | 121 ++--
 be/src/kudu/rpc/rpc.cc                             |  47 +-
 be/src/kudu/rpc/rpc.h                              |  74 ++-
 be/src/kudu/rpc/rpc_context.cc                     |  19 +-
 be/src/kudu/rpc/rpc_context.h                      |  26 +-
 be/src/kudu/rpc/rpc_controller.cc                  |   1 -
 be/src/kudu/rpc/rpc_controller.h                   |   4 +-
 be/src/kudu/rpc/rpc_header.proto                   |  11 +-
 be/src/kudu/rpc/rpc_introspection.proto            |   6 +-
 be/src/kudu/rpc/rpc_service.h                      |  11 +-
 be/src/kudu/rpc/rpc_sidecar.h                      |   6 +-
 be/src/kudu/rpc/rpc_stub-test.cc                   |  48 +-
 be/src/kudu/rpc/rpc_verification_util.cc           |  67 ++
 .../rpc/{rpc_service.h => rpc_verification_util.h} |  36 +-
 be/src/kudu/rpc/rpcz_store.cc                      |  14 +-
 be/src/kudu/rpc/sasl_common.cc                     |  11 +
 be/src/kudu/rpc/sasl_common.h                      |  17 +-
 be/src/kudu/rpc/sasl_helper.h                      |   6 +-
 be/src/kudu/rpc/serialization.cc                   |   2 +-
 be/src/kudu/rpc/serialization.h                    |   5 +-
 be/src/kudu/rpc/server_negotiation.cc              |  69 +-
 be/src/kudu/rpc/service_if.cc                      |  12 +-
 be/src/kudu/rpc/service_if.h                       |   4 +-
 be/src/kudu/rpc/service_pool.cc                    |  18 +-
 be/src/kudu/rpc/service_pool.h                     |  14 +-
 be/src/kudu/rpc/service_queue.h                    |   5 +-
 be/src/kudu/rpc/transfer.cc                        |  13 +-
 be/src/kudu/rpc/transfer.h                         |   5 +-
 be/src/kudu/security/CMakeLists.txt                |  14 +-
 be/src/kudu/util/CMakeLists.txt                    | 189 ++++--
 be/src/kudu/util/async_util-test.cc                |  90 ++-
 be/src/kudu/util/async_util.h                      |   9 +-
 be/src/kudu/util/bitmap-test.cc                    | 134 +++-
 be/src/kudu/util/bitmap.cc                         |  37 +-
 be/src/kudu/util/bitmap.h                          |  35 +-
 be/src/kudu/util/bitset-test.cc                    | 170 +++++
 be/src/kudu/util/bitset.h                          | 202 ++++++
 be/src/kudu/util/block_bloom_filter-test.cc        | 243 +++++++
 be/src/kudu/util/block_bloom_filter.cc             | 199 ++++++
 be/src/kudu/util/block_bloom_filter.h              | 231 +++++++
 be/src/kudu/util/block_bloom_filter_avx2.cc        |  78 +++
 .../{cache_metrics.cc => block_cache_metrics.cc}   |  48 +-
 .../block_cache_metrics.h}                         |  16 +-
 be/src/kudu/util/bloom_filter.h                    |  27 +-
 be/src/kudu/util/cache-bench.cc                    |  20 +-
 be/src/kudu/util/cache-test.cc                     | 455 ++++++++++---
 be/src/kudu/util/cache.cc                          | 497 +++++++++-----
 be/src/kudu/util/cache.h                           | 313 ++++++---
 be/src/kudu/util/cache_metrics.h                   |  11 +-
 be/src/kudu/util/char_util-test.cc                 | 126 ++++
 be/src/kudu/util/char_util.cc                      |  71 ++
 be/src/kudu/util/{zlib.h => char_util.h}           |  28 +-
 be/src/kudu/util/cloud/instance_detector-test.cc   |  97 +++
 be/src/kudu/util/cloud/instance_detector.cc        | 127 ++++
 be/src/kudu/util/cloud/instance_detector.h         |  95 +++
 be/src/kudu/util/cloud/instance_metadata.cc        | 205 ++++++
 be/src/kudu/util/cloud/instance_metadata.h         | 166 +++++
 be/src/kudu/util/compression/compression-test.cc   |  97 ++-
 be/src/kudu/util/compression/compression_codec.cc  |  18 +-
 be/src/kudu/util/countdown_latch-test.cc           |   5 +-
 be/src/kudu/util/countdown_latch.h                 |   2 +-
 .../{version_util-test.cc => curl_util-test.cc}    |  64 +-
 be/src/kudu/util/curl_util.cc                      |  66 +-
 be/src/kudu/util/curl_util.h                       |  29 +
 be/src/kudu/util/debug/trace_event_impl.cc         |  18 +-
 be/src/kudu/util/debug/trace_logging.h             |   5 +
 be/src/kudu/util/easy_json.cc                      |  73 +--
 be/src/kudu/util/env-test.cc                       |  60 +-
 be/src/kudu/util/env.cc                            |  11 +-
 be/src/kudu/util/env.h                             |  87 ++-
 be/src/kudu/util/env_posix.cc                      | 126 ++--
 be/src/kudu/util/env_util.cc                       |  65 +-
 be/src/kudu/util/env_util.h                        |   9 +-
 be/src/kudu/util/faststring-test.cc                |  63 +-
 be/src/kudu/util/faststring.cc                     |   9 +-
 be/src/kudu/util/faststring.h                      |  17 +-
 be/src/kudu/util/fault_injection.cc                |   7 +-
 be/src/kudu/util/fault_injection.h                 |  15 +-
 be/src/kudu/util/file_cache-stress-test.cc         | 140 ++--
 be/src/kudu/util/file_cache-test.cc                | 176 ++++-
 be/src/kudu/util/file_cache.cc                     | 431 +++++++-----
 be/src/kudu/util/file_cache.h                      | 101 ++-
 be/src/kudu/util/file_cache_metrics.cc             |  78 +++
 .../file_cache_metrics.h}                          |  16 +-
 be/src/kudu/util/flag_tags-test.cc                 |  16 +-
 be/src/kudu/util/flag_tags.h                       |  10 +-
 be/src/kudu/util/flags-test.cc                     |   9 +-
 be/src/kudu/util/flags.cc                          |  38 +-
 be/src/kudu/util/flags.h                           |   7 +-
 .../{rpc/response_callback.h => util/hash.proto}   |  20 +-
 be/src/kudu/util/hash_util-test.cc                 |  29 +
 be/src/kudu/util/hash_util.h                       |  62 +-
 be/src/kudu/util/hdr_histogram-test.cc             |  28 +
 be/src/kudu/util/hdr_histogram.cc                  |  80 ++-
 be/src/kudu/util/hdr_histogram.h                   |  16 +-
 be/src/kudu/util/high_water_mark.h                 |   4 +
 be/src/kudu/util/init.cc                           |   6 +-
 be/src/kudu/util/init.h                            |  15 +-
 be/src/kudu/util/int128_util.h                     |   3 +-
 be/src/kudu/util/interval_tree-inl.h               |  44 +-
 be/src/kudu/util/interval_tree-test.cc             | 132 +++-
 be/src/kudu/util/interval_tree.h                   |  12 +-
 be/src/kudu/util/jsonreader-test.cc                | 193 ++++++
 be/src/kudu/util/jsonreader.cc                     | 123 +++-
 be/src/kudu/util/jsonreader.h                      |  18 +
 be/src/kudu/util/jsonwriter-test.cc                |   8 +-
 be/src/kudu/util/jsonwriter.cc                     |  10 +-
 be/src/kudu/util/jsonwriter.h                      |   2 +-
 be/src/kudu/util/kudu_export.h                     |  62 --
 be/src/kudu/util/logging-test.cc                   |  17 +
 be/src/kudu/util/logging.cc                        |  24 +-
 be/src/kudu/util/logging.h                         |  14 +-
 be/src/kudu/util/maintenance_manager-test.cc       | 284 +++++++-
 be/src/kudu/util/maintenance_manager.cc            | 316 +++++----
 be/src/kudu/util/maintenance_manager.h             |  68 +-
 be/src/kudu/util/maintenance_manager.proto         |  11 +-
 be/src/kudu/util/map-util-test.cc                  | 111 +++-
 be/src/kudu/util/mem_tracker-test.cc               | 143 ++++
 be/src/kudu/util/mem_tracker.cc                    |  42 +-
 be/src/kudu/util/mem_tracker.h                     |  11 +-
 be/src/kudu/util/{zlib.h => mem_tracker.proto}     |  38 +-
 be/src/kudu/util/metrics-test.cc                   | 727 ++++++++++++++++++++-
 be/src/kudu/util/metrics.cc                        | 409 ++++++++++--
 be/src/kudu/util/metrics.h                         | 652 ++++++++++++++----
 be/src/kudu/util/minidump.cc                       |  32 +-
 be/src/kudu/util/monotime-test.cc                  |  14 +-
 be/src/kudu/util/monotime.cc                       |  22 +-
 be/src/kudu/util/monotime.h                        |  31 +-
 be/src/kudu/util/mt-metrics-test.cc                |   6 +-
 be/src/kudu/util/net/dns_resolver-test.cc          |  60 +-
 be/src/kudu/util/net/dns_resolver.cc               | 112 +++-
 be/src/kudu/util/net/dns_resolver.h                |  77 ++-
 be/src/kudu/util/net/net_util-test.cc              |  46 +-
 be/src/kudu/util/net/net_util.cc                   | 125 +++-
 be/src/kudu/util/net/net_util.h                    |  93 ++-
 be/src/kudu/util/net/sockaddr.cc                   |  20 +-
 be/src/kudu/util/net/sockaddr.h                    |   5 +
 be/src/kudu/util/net/socket.cc                     |   7 +-
 be/src/kudu/util/nvm_cache.cc                      | 382 +++++++----
 be/src/kudu/util/nvm_cache.h                       |  28 +-
 be/src/kudu/util/once-test.cc                      |  57 +-
 be/src/kudu/util/once.cc                           |   8 +
 be/src/kudu/util/once.h                            |  47 +-
 be/src/kudu/util/os-util.cc                        |  23 +-
 be/src/kudu/util/pb_util-test.cc                   |   4 +-
 be/src/kudu/util/pb_util.cc                        |   3 +-
 be/src/kudu/util/pb_util.h                         |   4 +-
 be/src/kudu/util/process_memory.cc                 |  20 +-
 be/src/kudu/util/process_memory.h                  |   3 +
 be/src/kudu/util/random-test.cc                    |   9 +-
 be/src/kudu/util/random.h                          |  41 --
 be/src/kudu/util/random_util.h                     |  64 +-
 be/src/kudu/util/rle-encoding.h                    |  12 +-
 be/src/kudu/util/rle-test.cc                       |  43 ++
 be/src/kudu/util/rolling_log-test.cc               |   6 +-
 be/src/kudu/util/rolling_log.cc                    |  12 +-
 be/src/kudu/util/rolling_log.h                     |   6 +-
 be/src/kudu/util/sanitizer_options.cc              | 218 ++++++
 be/src/kudu/util/scoped_cleanup.h                  |   2 +-
 be/src/kudu/util/signal.cc                         |  28 +-
 be/src/kudu/util/spinlock_profiling.cc             |   1 +
 be/src/kudu/util/status.cc                         |   3 +
 be/src/kudu/util/status.h                          |  35 +-
 be/src/kudu/util/stopwatch.h                       |  12 +
 be/src/kudu/util/string_case.cc                    |  24 +-
 be/src/kudu/util/string_case.h                     |  30 +-
 be/src/kudu/util/subprocess-test.cc                |  50 +-
 be/src/kudu/util/subprocess.cc                     |  74 ++-
 be/src/kudu/util/subprocess.h                      |   7 +-
 be/src/kudu/util/test_macros.h                     |  26 +-
 be/src/kudu/util/test_util.cc                      |  45 +-
 be/src/kudu/util/testdata/char_truncate_ascii.txt  |  35 +
 be/src/kudu/util/testdata/char_truncate_utf8.txt   | 258 ++++++++
 be/src/kudu/util/thread.cc                         | 264 +++++---
 be/src/kudu/util/thread.h                          |  31 +-
 be/src/kudu/util/threadpool-test.cc                |  17 +-
 be/src/kudu/util/threadpool.cc                     |   5 +-
 be/src/kudu/util/threadpool.h                      |   9 +-
 be/src/kudu/util/trace-test.cc                     |  27 +-
 be/src/kudu/util/trace.h                           |  17 +-
 be/src/kudu/util/ttl_cache-test.cc                 | 653 ++++++++++++++++++
 be/src/kudu/util/ttl_cache.h                       | 339 ++++++++++
 be/src/kudu/util/{once.cc => ttl_cache_metrics.h}  |  18 +-
 be/src/kudu/util/user.cc                           |   9 +
 be/src/kudu/util/version_util-test.cc              |  96 ++-
 be/src/kudu/util/version_util.cc                   |  69 +-
 be/src/kudu/util/version_util.h                    |  21 +-
 .../web_callback_registry.cc}                      |  13 +-
 be/src/kudu/util/web_callback_registry.h           |  29 +-
 be/src/kudu/util/yamlreader-test.cc                | 176 +++++
 be/src/kudu/util/yamlreader.cc                     | 100 +++
 be/src/kudu/util/yamlreader.h                      | 129 ++++
 be/src/kudu/util/zlib.cc                           |   6 +-
 be/src/kudu/util/zlib.h                            |   4 +
 221 files changed, 12181 insertions(+), 3029 deletions(-)

diff --git a/be/src/kudu/rpc/CMakeLists.txt b/be/src/kudu/rpc/CMakeLists.txt
index 3b31ec5..1190968 100644
--- a/be/src/kudu/rpc/CMakeLists.txt
+++ b/be/src/kudu/rpc/CMakeLists.txt
@@ -62,6 +62,7 @@ set(KRPC_SRCS
     rpc_context.cc
     rpc_controller.cc
     rpc_sidecar.cc
+    rpc_verification_util.cc
     rpcz_store.cc
     sasl_common.cc
     sasl_helper.cc
@@ -89,23 +90,13 @@ ADD_EXPORTABLE_LIBRARY(krpc
   DEPS ${KRPC_LIBS})
 
 ### RPC generator tool
-add_executable(protoc-gen-krpc protoc-gen-krpc.cc
-  # Impala - add stub for kudu::VersionInfo
-  ${CMAKE_CURRENT_SOURCE_DIR}/../../common/kudu_version.cc
-  # Impala - add definition for any flag names shared between Impala / Kudu.
-  # TODO: Consider either removing code that depends on these flags, or namespacing them
-  # somehow.
-  ${CMAKE_CURRENT_SOURCE_DIR}/../../common/global-flags.cc)
-# IMPALA-8642: kudu_version.cc depends on gen-cpp/Status_types.h in target thrift-deps
-add_dependencies(protoc-gen-krpc thrift-deps)
+add_executable(protoc-gen-krpc protoc-gen-krpc.cc)
 target_link_libraries(protoc-gen-krpc
     ${KUDU_BASE_LIBS}
     rpc_header_proto
     protoc
     protobuf
-    gutil
-    libunwind
-    kudu_util)
+    gutil)
 
 #### RPC test
 PROTOBUF_GENERATE_CPP(
@@ -128,13 +119,12 @@ target_link_libraries(rtest_krpc
   rtest_diff_package_proto)
 
 # Tests
-set(KUDU_TEST_LINK_LIBS
+SET_KUDU_TEST_LINK_LIBS(
   krpc
   mini_kdc
   rpc_header_proto
   rtest_krpc
-  security_test_util
-  ${KUDU_MIN_TEST_LIBS})
+  security_test_util)
 ADD_KUDU_TEST(exactly_once_rpc-test PROCESSORS 10)
 ADD_KUDU_TEST(mt-rpc-test RUN_SERIAL true)
 ADD_KUDU_TEST(negotiation-test)
diff --git a/be/src/kudu/rpc/acceptor_pool.cc b/be/src/kudu/rpc/acceptor_pool.cc
index e4bcbd1..88d48da 100644
--- a/be/src/kudu/rpc/acceptor_pool.cc
+++ b/be/src/kudu/rpc/acceptor_pool.cc
@@ -50,7 +50,8 @@ using std::string;
 METRIC_DEFINE_counter(server, rpc_connections_accepted,
                       "RPC Connections Accepted",
                       kudu::MetricUnit::kConnections,
-                      "Number of incoming TCP connections made to the RPC server");
+                      "Number of incoming TCP connections made to the RPC server",
+                      kudu::MetricLevel::kInfo);
 
 DEFINE_int32(rpc_acceptor_listen_backlog, 128,
              "Socket backlog parameter used when listening for RPC connections. "
diff --git a/be/src/kudu/rpc/acceptor_pool.h b/be/src/kudu/rpc/acceptor_pool.h
index ba1996a..6e5ae35 100644
--- a/be/src/kudu/rpc/acceptor_pool.h
+++ b/be/src/kudu/rpc/acceptor_pool.h
@@ -14,11 +14,9 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+#pragma once
 
-#ifndef KUDU_RPC_ACCEPTOR_POOL_H
-#define KUDU_RPC_ACCEPTOR_POOL_H
-
-#include <stdint.h>
+#include <cstdint>
 #include <vector>
 
 #include "kudu/gutil/atomicops.h"
@@ -81,4 +79,3 @@ class AcceptorPool {
 
 } // namespace rpc
 } // namespace kudu
-#endif
diff --git a/be/src/kudu/rpc/blocking_ops.h b/be/src/kudu/rpc/blocking_ops.h
index b305ba7..7dc5a35 100644
--- a/be/src/kudu/rpc/blocking_ops.h
+++ b/be/src/kudu/rpc/blocking_ops.h
@@ -14,9 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_RPC_BLOCKING_OPS_H
-#define KUDU_RPC_BLOCKING_OPS_H
+#pragma once
 
 namespace google {
 namespace protobuf {
@@ -54,5 +52,3 @@ Status ReceiveFramedMessageBlocking(Socket* sock, faststring* recv_buf,
 
 } // namespace rpc
 } // namespace kudu
-
-#endif  // KUDU_RPC_BLOCKING_OPS_H
diff --git a/be/src/kudu/rpc/client_negotiation.cc b/be/src/kudu/rpc/client_negotiation.cc
index c405687..b43f55d 100644
--- a/be/src/kudu/rpc/client_negotiation.cc
+++ b/be/src/kudu/rpc/client_negotiation.cc
@@ -44,6 +44,7 @@
 #include "kudu/rpc/sasl_helper.h"
 #include "kudu/rpc/serialization.h"
 #include "kudu/security/cert.h"
+#include "kudu/security/gssapi.h"
 #include "kudu/security/tls_context.h"
 #include "kudu/security/tls_handshake.h"
 #include "kudu/util/faststring.h"
@@ -52,6 +53,13 @@
 #include "kudu/util/slice.h"
 #include "kudu/util/trace.h"
 
+#if defined(__APPLE__)
+// Almost all functions in the SASL API are marked as deprecated
+// since macOS 10.11.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif // #if defined(__APPLE__)
+
 using std::map;
 using std::set;
 using std::string;
@@ -784,36 +792,6 @@ int ClientNegotiation::SecretCb(sasl_conn_t* conn, int id, sasl_secret_t** psecr
   return SASL_OK;
 }
 
-namespace {
-// Retrieve the GSSAPI error description for an error code and type.
-string gss_error_description(OM_uint32 code, int type) {
-  string description;
-  OM_uint32 message_context = 0;
-
-  do {
-    if (!description.empty()) {
-      description.append(": ");
-    }
-    OM_uint32 minor = 0;
-    gss_buffer_desc buf;
-    gss_display_status(&minor, code, type, GSS_C_NULL_OID, &message_context, &buf);
-    description.append(static_cast<const char*>(buf.value), buf.length);
-    gss_release_buffer(&minor, &buf);
-  } while (message_context != 0);
-
-  return description;
-}
-
-// Transforms a GSSAPI major and minor error code into a Kudu Status.
-Status check_gss_error(OM_uint32 major, OM_uint32 minor) {
-    if (GSS_ERROR(major)) {
-      return Status::NotAuthorized(gss_error_description(major, GSS_C_GSS_CODE),
-                                   gss_error_description(minor, GSS_C_MECH_CODE));
-    }
-    return Status::OK();
-}
-} // anonymous namespace
-
 Status ClientNegotiation::CheckGSSAPI() {
   OM_uint32 major, minor;
   gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
@@ -829,7 +807,7 @@ Status ClientNegotiation::CheckGSSAPI() {
                            &cred,
                            nullptr,
                            nullptr);
-  Status s = check_gss_error(major, minor);
+  Status s = gssapi::MajorMinorToStatus(major, minor);
 
   // Inspect the Kerberos credential to determine if it is expired. The lifetime
   // returned from gss_acquire_cred in the RHEL 6 version of krb5 is always 0,
@@ -838,7 +816,7 @@ Status ClientNegotiation::CheckGSSAPI() {
   OM_uint32 lifetime;
   if (s.ok()) {
     major = gss_inquire_cred(&minor, cred, nullptr, &lifetime, nullptr, nullptr);
-    s = check_gss_error(major, minor);
+    s = gssapi::MajorMinorToStatus(major, minor);
   }
 
   // Release the credential even if gss_inquire_cred fails.
@@ -853,3 +831,7 @@ Status ClientNegotiation::CheckGSSAPI() {
 
 } // namespace rpc
 } // namespace kudu
+
+#if defined(__APPLE__)
+#pragma GCC diagnostic pop
+#endif // #if defined(__APPLE__)
diff --git a/be/src/kudu/rpc/connection.cc b/be/src/kudu/rpc/connection.cc
index 2b79464..0b78d46 100644
--- a/be/src/kudu/rpc/connection.cc
+++ b/be/src/kudu/rpc/connection.cc
@@ -26,13 +26,13 @@
 #include <memory>
 #include <set>
 #include <string>
-#include <type_traits>
 
 #include <boost/intrusive/detail/list_iterator.hpp>
 #include <boost/intrusive/list.hpp>
 #include <ev.h>
 #include <glog/logging.h>
 
+#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/map-util.h"
 #include "kudu/gutil/strings/human_readable.h"
 #include "kudu/gutil/strings/substitute.h"
@@ -329,7 +329,7 @@ void Connection::Shutdown(const Status &status,
   }
 }
 
-void Connection::QueueOutbound(gscoped_ptr<OutboundTransfer> transfer) {
+void Connection::QueueOutbound(unique_ptr<OutboundTransfer> transfer) {
   DCHECK(reactor_thread_->IsCurrentThread());
 
   if (!shutdown_status_.ok()) {
@@ -376,7 +376,10 @@ void Connection::CallAwaitingResponse::HandleTimeout(ev::timer &watcher, int rev
 
 void Connection::HandleOutboundCallTimeout(CallAwaitingResponse *car) {
   DCHECK(reactor_thread_->IsCurrentThread());
-  DCHECK(car->call);
+  if (!car->call) {
+    // The RPC may have been cancelled before the timeout was hit.
+    return;
+  }
   // The timeout timer is stopped by the car destructor exiting Connection::HandleCallResponse()
   DCHECK(!car->call->IsFinished());
 
@@ -532,7 +535,7 @@ void Connection::QueueOutboundCall(shared_ptr<OutboundCall> call) {
 
   TransferCallbacks *cb = new CallTransferCallbacks(std::move(call), this);
   awaiting_response_[call_id] = car.release();
-  QueueOutbound(gscoped_ptr<OutboundTransfer>(
+  QueueOutbound(unique_ptr<OutboundTransfer>(
       OutboundTransfer::CreateForCallRequest(call_id, tmp_slices, n_slices, cb)));
 }
 
@@ -541,7 +544,7 @@ void Connection::QueueOutboundCall(shared_ptr<OutboundCall> call) {
 // been responded to, we can free up all of the associated memory.
 struct ResponseTransferCallbacks : public TransferCallbacks {
  public:
-  ResponseTransferCallbacks(gscoped_ptr<InboundCall> call,
+  ResponseTransferCallbacks(unique_ptr<InboundCall> call,
                             Connection *conn) :
     call_(std::move(call)),
     conn_(conn)
@@ -565,14 +568,14 @@ struct ResponseTransferCallbacks : public TransferCallbacks {
   }
 
  private:
-  gscoped_ptr<InboundCall> call_;
+  unique_ptr<InboundCall> call_;
   Connection *conn_;
 };
 
 // Reactor task which puts a transfer on the outbound transfer queue.
 class QueueTransferTask : public ReactorTask {
  public:
-  QueueTransferTask(gscoped_ptr<OutboundTransfer> transfer,
+  QueueTransferTask(unique_ptr<OutboundTransfer> transfer,
                     Connection *conn)
     : transfer_(std::move(transfer)),
       conn_(conn)
@@ -589,11 +592,11 @@ class QueueTransferTask : public ReactorTask {
   }
 
  private:
-  gscoped_ptr<OutboundTransfer> transfer_;
+  unique_ptr<OutboundTransfer> transfer_;
   Connection *conn_;
 };
 
-void Connection::QueueResponseForCall(gscoped_ptr<InboundCall> call) {
+void Connection::QueueResponseForCall(unique_ptr<InboundCall> call) {
   // This is usually called by the IPC worker thread when the response
   // is set, but in some circumstances may also be called by the
   // reactor thread (e.g. if the service has shut down)
@@ -611,7 +614,7 @@ void Connection::QueueResponseForCall(gscoped_ptr<InboundCall> call) {
   // After the response is sent, can delete the InboundCall object.
   // We set a dummy call ID and required feature set, since these are not needed
   // when sending responses.
-  gscoped_ptr<OutboundTransfer> t(
+  unique_ptr<OutboundTransfer> t(
       OutboundTransfer::CreateForCallResponse(tmp_slices, n_slices, cb));
 
   QueueTransferTask *task = new QueueTransferTask(std::move(t), this);
@@ -682,10 +685,10 @@ void Connection::ReadHandler(ev::io &watcher, int revents) {
   }
 }
 
-void Connection::HandleIncomingCall(gscoped_ptr<InboundTransfer> transfer) {
+void Connection::HandleIncomingCall(unique_ptr<InboundTransfer> transfer) {
   DCHECK(reactor_thread_->IsCurrentThread());
 
-  gscoped_ptr<InboundCall> call(new InboundCall(this));
+  unique_ptr<InboundCall> call(new InboundCall(this));
   Status s = call->ParseFrom(std::move(transfer));
   if (!s.ok()) {
     LOG(WARNING) << ToString() << ": received bad data: " << s.ToString();
@@ -706,9 +709,9 @@ void Connection::HandleIncomingCall(gscoped_ptr<InboundTransfer> transfer) {
   reactor_thread_->reactor()->messenger()->QueueInboundCall(std::move(call));
 }
 
-void Connection::HandleCallResponse(gscoped_ptr<InboundTransfer> transfer) {
+void Connection::HandleCallResponse(unique_ptr<InboundTransfer> transfer) {
   DCHECK(reactor_thread_->IsCurrentThread());
-  gscoped_ptr<CallResponse> resp(new CallResponse);
+  unique_ptr<CallResponse> resp(new CallResponse);
   CHECK_OK(resp->ParseFrom(std::move(transfer)));
 
   CallAwaitingResponse *car_ptr =
diff --git a/be/src/kudu/rpc/connection.h b/be/src/kudu/rpc/connection.h
index 8f80b5a..ab3b029 100644
--- a/be/src/kudu/rpc/connection.h
+++ b/be/src/kudu/rpc/connection.h
@@ -14,9 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_RPC_CONNECTION_H
-#define KUDU_RPC_CONNECTION_H
+#pragma once
 
 #include <cstddef>
 #include <cstdint>
@@ -32,7 +30,6 @@
 #include <ev++.h>
 #include <glog/logging.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/port.h"
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/rpc/connection_id.h"
@@ -135,7 +132,7 @@ class Connection : public RefCountedThreadSafe<Connection> {
   // Queue a call response back to the client on the server side.
   //
   // This may be called from a non-reactor thread.
-  void QueueResponseForCall(gscoped_ptr<InboundCall> call);
+  void QueueResponseForCall(std::unique_ptr<InboundCall> call);
 
   // Cancel an outbound call by removing any reference to it by CallAwaitingResponse
   // in 'awaiting_responses_'.
@@ -286,12 +283,12 @@ class Connection : public RefCountedThreadSafe<Connection> {
 
   // An incoming packet has completed transferring on the server side.
   // This parses the call and delivers it into the call queue.
-  void HandleIncomingCall(gscoped_ptr<InboundTransfer> transfer);
+  void HandleIncomingCall(std::unique_ptr<InboundTransfer> transfer);
 
   // An incoming packet has completed on the client side. This parses the
   // call response, looks up the CallAwaitingResponse, and calls the
   // client callback.
-  void HandleCallResponse(gscoped_ptr<InboundTransfer> transfer);
+  void HandleCallResponse(std::unique_ptr<InboundTransfer> transfer);
 
   // The given CallAwaitingResponse has elapsed its user-defined timeout.
   // Set it to Failed.
@@ -300,7 +297,7 @@ class Connection : public RefCountedThreadSafe<Connection> {
   // Queue a transfer for sending on this connection.
   // We will take ownership of the transfer.
   // This must be called from the reactor thread.
-  void QueueOutbound(gscoped_ptr<OutboundTransfer> transfer);
+  void QueueOutbound(std::unique_ptr<OutboundTransfer> transfer);
 
   // Internal test function for injecting cancellation request when 'call'
   // reaches state specified in 'FLAGS_rpc_inject_cancellation_state'.
@@ -331,7 +328,7 @@ class Connection : public RefCountedThreadSafe<Connection> {
   MonoTime last_activity_time_;
 
   // the inbound transfer, if any
-  gscoped_ptr<InboundTransfer> inbound_;
+  std::unique_ptr<InboundTransfer> inbound_;
 
   // notifies us when our socket is writable.
   ev::io write_io_;
@@ -397,5 +394,3 @@ class Connection : public RefCountedThreadSafe<Connection> {
 
 } // namespace rpc
 } // namespace kudu
-
-#endif
diff --git a/be/src/kudu/rpc/constants.h b/be/src/kudu/rpc/constants.h
index a3c7c67..184fc7b 100644
--- a/be/src/kudu/rpc/constants.h
+++ b/be/src/kudu/rpc/constants.h
@@ -14,9 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_RPC_RPC_CONSTANTS_H
-#define KUDU_RPC_RPC_CONSTANTS_H
+#pragma once
 
 #include <cstdint>
 #include <set>
@@ -56,5 +54,3 @@ extern std::set<RpcFeatureFlag> kSupportedClientRpcFeatureFlags;
 
 } // namespace rpc
 } // namespace kudu
-
-#endif // KUDU_RPC_RPC_CONSTANTS_H
diff --git a/be/src/kudu/rpc/inbound_call.cc b/be/src/kudu/rpc/inbound_call.cc
index 655c453..7479020 100644
--- a/be/src/kudu/rpc/inbound_call.cc
+++ b/be/src/kudu/rpc/inbound_call.cc
@@ -66,7 +66,7 @@ InboundCall::InboundCall(Connection* conn)
 
 InboundCall::~InboundCall() {}
 
-Status InboundCall::ParseFrom(gscoped_ptr<InboundTransfer> transfer) {
+Status InboundCall::ParseFrom(unique_ptr<InboundTransfer> transfer) {
   TRACE_EVENT_FLOW_BEGIN0("rpc", "InboundCall", this);
   TRACE_EVENT0("rpc", "InboundCall::ParseFrom");
   RETURN_NOT_OK(serialization::ParseMessage(transfer->data(), &header_, &serialized_request_));
@@ -162,7 +162,7 @@ void InboundCall::Respond(const MessageLite& response,
   TRACE_TO(trace_, "Queueing $0 response", is_success ? "success" : "failure");
   RecordHandlingCompleted();
   conn_->rpcz_store()->AddCall(this);
-  conn_->QueueResponseForCall(gscoped_ptr<InboundCall>(this));
+  conn_->QueueResponseForCall(unique_ptr<InboundCall>(this));
 }
 
 void InboundCall::SerializeResponseBuffer(const MessageLite& response,
@@ -302,7 +302,7 @@ void InboundCall::RecordHandlingCompleted() {
 
   if (method_info_) {
     method_info_->handler_latency_histogram->Increment(
-        (timing_.time_completed - timing_.time_handled).ToMicroseconds());
+        (timing_.ProcessingDuration()).ToMicroseconds());
   }
 }
 
@@ -311,9 +311,15 @@ bool InboundCall::ClientTimedOut() const {
 }
 
 MonoTime InboundCall::GetTimeReceived() const {
+  DCHECK(timing_.time_received.Initialized());
   return timing_.time_received;
 }
 
+MonoTime InboundCall::GetTimeHandled() const {
+  DCHECK(timing_.time_handled.Initialized());
+  return timing_.time_handled;
+}
+
 vector<uint32_t> InboundCall::GetRequiredFeatures() const {
   vector<uint32_t> features;
   for (uint32_t feature : header_.required_feature_flags()) {
diff --git a/be/src/kudu/rpc/inbound_call.h b/be/src/kudu/rpc/inbound_call.h
index 07c57dc..892b893 100644
--- a/be/src/kudu/rpc/inbound_call.h
+++ b/be/src/kudu/rpc/inbound_call.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_INBOUND_CALL_H
-#define KUDU_RPC_INBOUND_CALL_H
+#pragma once
 
 #include <cstddef>
 #include <cstdint>
@@ -27,7 +26,6 @@
 
 #include <glog/logging.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/macros.h"
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/rpc/remote_method.h"
@@ -67,6 +65,14 @@ struct InboundCallTiming {
   MonoDelta TotalDuration() const {
     return time_completed - time_received;
   }
+
+  MonoDelta ProcessingDuration() const {
+    return time_completed - time_handled;
+  }
+
+  MonoDelta QueueDuration() const {
+    return time_handled - time_received;
+  }
 };
 
 // Inbound call on server
@@ -81,7 +87,7 @@ class InboundCall {
   // 'serialized_request_' member variables. The actual call parameter is
   // not deserialized, as this may be CPU-expensive, and this is called
   // from the reactor thread.
-  Status ParseFrom(gscoped_ptr<InboundTransfer> transfer);
+  Status ParseFrom(std::unique_ptr<InboundTransfer> transfer);
 
   // Return the serialized request parameter protobuf.
   const Slice& serialized_request() const {
@@ -192,6 +198,9 @@ class InboundCall {
   // Return the time when this call was received.
   MonoTime GetTimeReceived() const;
 
+  // Return the time when this call was handled.
+  MonoTime GetTimeHandled() const;
+
   // Returns the set of application-specific feature flags required to service
   // the RPC.
   std::vector<uint32_t> GetRequiredFeatures() const;
@@ -240,7 +249,7 @@ class InboundCall {
   // The transfer that produced the call.
   // This is kept around because it retains the memory referred to
   // by 'serialized_request_' above.
-  gscoped_ptr<InboundTransfer> transfer_;
+  std::unique_ptr<InboundTransfer> transfer_;
 
   // The buffers for serialized response. Set by SerializeResponseBuffer().
   faststring response_hdr_buf_;
@@ -282,5 +291,3 @@ class InboundCall {
 
 } // namespace rpc
 } // namespace kudu
-
-#endif
diff --git a/be/src/kudu/rpc/messenger.cc b/be/src/kudu/rpc/messenger.cc
index 4129172..48af449 100644
--- a/be/src/kudu/rpc/messenger.cc
+++ b/be/src/kudu/rpc/messenger.cc
@@ -22,12 +22,10 @@
 #include <mutex>
 #include <ostream>
 #include <string>
-#include <type_traits>
 #include <utility>
 
 #include <glog/logging.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/map-util.h"
 #include "kudu/gutil/port.h"
 #include "kudu/gutil/stl_util.h"
@@ -51,7 +49,6 @@
 #include "kudu/util/metrics.h"
 #include "kudu/util/monotime.h"
 #include "kudu/util/net/socket.h"
-#include "kudu/util/scoped_cleanup.h"
 #include "kudu/util/status.h"
 #include "kudu/util/thread_restrictions.h"
 #include "kudu/util/threadpool.h"
@@ -59,6 +56,7 @@
 using std::string;
 using std::shared_ptr;
 using std::make_shared;
+using std::unique_ptr;
 using strings::Substitute;
 
 namespace boost {
@@ -184,15 +182,15 @@ MessengerBuilder& MessengerBuilder::set_reuseport() {
   return *this;
 }
 
-Status MessengerBuilder::Build(shared_ptr<Messenger> *msgr) {
+Status MessengerBuilder::Build(shared_ptr<Messenger>* msgr) {
   // Initialize SASL library before we start making requests
   RETURN_NOT_OK(SaslInit(!keytab_file_.empty()));
 
-  Messenger* new_msgr(new Messenger(*this));
-
-  auto cleanup = MakeScopedCleanup([&] () {
-      new_msgr->AllExternalReferencesDropped();
-  });
+  // See docs on Messenger::retain_self_ for info about this odd hack.
+  //
+  // Note: can't use make_shared() as it doesn't support custom deleters.
+  shared_ptr<Messenger> new_msgr(new Messenger(*this),
+                                 std::mem_fn(&Messenger::AllExternalReferencesDropped));
 
   RETURN_NOT_OK(ParseTriState("--rpc_authentication",
                               rpc_authentication_,
@@ -233,9 +231,7 @@ Status MessengerBuilder::Build(shared_ptr<Messenger> *msgr) {
     }
   }
 
-  // See docs on Messenger::retain_self_ for info about this odd hack.
-  cleanup.cancel();
-  *msgr = shared_ptr<Messenger>(new_msgr, std::mem_fun(&Messenger::AllExternalReferencesDropped));
+  *msgr = std::move(new_msgr);
   return Status::OK();
 }
 
@@ -371,7 +367,13 @@ void Messenger::QueueOutboundCall(const shared_ptr<OutboundCall> &call) {
   reactor->QueueOutboundCall(call);
 }
 
-void Messenger::QueueInboundCall(gscoped_ptr<InboundCall> call) {
+void Messenger::QueueInboundCall(unique_ptr<InboundCall> call) {
+  // This lock acquisition spans the entirety of the function to avoid having to
+  // take a ref on the RpcService. In doing so, we guarantee that the service
+  // isn't shut down here, which would be problematic because shutdown is a
+  // blocking operation and QueueInboundCall is called by the reactor thread.
+  //
+  // See KUDU-2946 for more details.
   shared_lock<rw_spinlock> guard(lock_.get_lock());
   scoped_refptr<RpcService>* service = FindOrNull(rpc_services_,
                                                   call->remote_method().service_name());
@@ -427,7 +429,6 @@ Messenger::Messenger(const MessengerBuilder &bld)
 }
 
 Messenger::~Messenger() {
-  std::lock_guard<percpu_rwlock> guard(lock_);
   CHECK(closing_) << "Should have already shut down";
   STLDeleteElements(&reactors_);
 }
@@ -451,7 +452,6 @@ Status Messenger::Init() {
 
 Status Messenger::DumpConnections(const DumpConnectionsRequestPB& req,
                                   DumpConnectionsResponsePB* resp) {
-  shared_lock<rw_spinlock> guard(lock_.get_lock());
   for (Reactor* reactor : reactors_) {
     RETURN_NOT_OK(reactor->DumpConnections(req, resp));
   }
diff --git a/be/src/kudu/rpc/messenger.h b/be/src/kudu/rpc/messenger.h
index 56b087c..6d95615 100644
--- a/be/src/kudu/rpc/messenger.h
+++ b/be/src/kudu/rpc/messenger.h
@@ -14,23 +14,23 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_MESSENGER_H
-#define KUDU_RPC_MESSENGER_H
+#pragma once
 
 #include <cstdint>
 #include <memory>
 #include <mutex>
 #include <string>
 #include <unordered_map>
+#include <utility>
 #include <vector>
 
 #include <boost/optional/optional.hpp>
 #include <gtest/gtest_prod.h>
 
 #include "kudu/gutil/macros.h"
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/rpc/connection.h"
+#include "kudu/rpc/rpc_service.h"
 #include "kudu/security/security_flags.h"
 #include "kudu/security/token.pb.h"
 #include "kudu/util/locks.h"
@@ -66,7 +66,6 @@ class InboundCall;
 class Messenger;
 class OutboundCall;
 class Reactor;
-class RpcService;
 class RpczStore;
 
 struct AcceptorPoolInfo {
@@ -166,7 +165,7 @@ class MessengerBuilder {
   // Configure the messenger to set the SO_REUSEPORT socket option.
   MessengerBuilder& set_reuseport();
 
-  Status Build(std::shared_ptr<Messenger> *msgr);
+  Status Build(std::shared_ptr<Messenger>* msgr);
 
  private:
   const std::string name_;
@@ -257,7 +256,7 @@ class Messenger {
   void QueueOutboundCall(const std::shared_ptr<OutboundCall> &call);
 
   // Enqueue a call for processing on the server.
-  void QueueInboundCall(gscoped_ptr<InboundCall> call);
+  void QueueInboundCall(std::unique_ptr<InboundCall> call);
 
   // Queue a cancellation for the given outbound call.
   void QueueCancellation(const std::shared_ptr<OutboundCall> &call);
@@ -379,8 +378,8 @@ class Messenger {
 
   // Separate client and server negotiation pools to avoid possibility of distributed
   // deadlock. See KUDU-2041.
-  gscoped_ptr<ThreadPool> client_negotiation_pool_;
-  gscoped_ptr<ThreadPool> server_negotiation_pool_;
+  std::unique_ptr<ThreadPool> client_negotiation_pool_;
+  std::unique_ptr<ThreadPool> server_negotiation_pool_;
 
   std::unique_ptr<security::TlsContext> tls_context_;
 
@@ -458,4 +457,3 @@ class Messenger {
 } // namespace rpc
 } // namespace kudu
 
-#endif
diff --git a/be/src/kudu/rpc/mt-rpc-test.cc b/be/src/kudu/rpc/mt-rpc-test.cc
index 7427850..0245ce6 100644
--- a/be/src/kudu/rpc/mt-rpc-test.cc
+++ b/be/src/kudu/rpc/mt-rpc-test.cc
@@ -15,26 +15,23 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#include "kudu/rpc/rpc-test-base.h"
-
 #include <cstddef>
 #include <memory>
 #include <ostream>
 #include <string>
-#include <type_traits>
 #include <utility>
 #include <vector>
 
 #include <glog/logging.h>
 #include <gtest/gtest.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/port.h"
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/rpc/acceptor_pool.h"
 #include "kudu/rpc/messenger.h"
 #include "kudu/rpc/proxy.h"
+#include "kudu/rpc/rpc-test-base.h"
 #include "kudu/rpc/rpc_service.h"
 #include "kudu/rpc/service_if.h"
 #include "kudu/rpc/service_pool.h"
@@ -54,6 +51,7 @@ METRIC_DECLARE_counter(rpcs_queue_overflow);
 
 using std::string;
 using std::shared_ptr;
+using std::unique_ptr;
 using std::vector;
 using strings::Substitute;
 
@@ -178,7 +176,7 @@ TEST_F(MultiThreadedRpcTest, TestShutdownClientWhileCallsPending) {
 // This bogus service pool leaves the service queue full.
 class BogusServicePool : public ServicePool {
  public:
-  BogusServicePool(gscoped_ptr<ServiceIf> service,
+  BogusServicePool(unique_ptr<ServiceIf> service,
                    const scoped_refptr<MetricEntity>& metric_entity,
                    size_t service_queue_length)
     : ServicePool(std::move(service), metric_entity, service_queue_length) {
@@ -195,7 +193,7 @@ void IncrementBackpressureOrShutdown(const Status* status, int* backpressure, in
     ++(*backpressure);
   } else if (msg.find("shutting down") != string::npos) {
     ++(*shutdown);
-  } else if (msg.find("got EOF from remote") != string::npos) {
+  } else if (msg.find("recv got EOF from") != string::npos) {
     ++(*shutdown);
   } else {
     FAIL() << "Unexpected status message: " << msg;
@@ -216,7 +214,7 @@ TEST_F(MultiThreadedRpcTest, TestBlowOutServiceQueue) {
   ASSERT_OK(pool->Start(kMaxConcurrency));
   Sockaddr server_addr = pool->bind_address();
 
-  gscoped_ptr<ServiceIf> service(new GenericCalculatorService());
+  unique_ptr<ServiceIf> service(new GenericCalculatorService());
   service_name_ = service->service_name();
   service_pool_ = new BogusServicePool(std::move(service),
                                       server_messenger_->metric_entity(),
diff --git a/be/src/kudu/rpc/negotiation-test.cc b/be/src/kudu/rpc/negotiation-test.cc
index 976f590..bebc5e9 100644
--- a/be/src/kudu/rpc/negotiation-test.cc
+++ b/be/src/kudu/rpc/negotiation-test.cc
@@ -80,6 +80,13 @@
 #define KRB5_VERSION_LE_1_10
 #endif
 
+#if defined(__APPLE__)
+// Almost all functions in the SASL API are marked as deprecated
+// since macOS 10.11.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif // #if defined(__APPLE__)
+
 DEFINE_bool(is_test_child, false,
             "Used by tests which require clean processes. "
             "See TestDisableInit.");
@@ -199,7 +206,9 @@ TEST_P(TestNegotiation, TestNegotiation) {
   FLAGS_rpc_encrypt_loopback_connections = desc.rpc_encrypt_loopback;
 
   // Generate an optional client token and server token verifier.
-  TokenSigner token_signer(60, 20, std::make_shared<TokenVerifier>());
+  // Note: the authz token validity period doesn't matter because we're only
+  // concerned with authenticating the connection.
+  TokenSigner token_signer(60, 0, 20, std::make_shared<TokenVerifier>());
   {
     unique_ptr<TokenSigningPrivateKey> key;
     ASSERT_OK(token_signer.CheckNeedKey(&key));
@@ -1157,10 +1166,15 @@ TEST_F(TestNegotiation, TestPreflight) {
   // Try with an inaccessible keytab.
   CHECK_ERR(chmod(kt_path.c_str(), 0000));
   s = ServerNegotiation::PreflightCheckGSSAPI("kudu");
-  ASSERT_FALSE(s.ok());
+  if (geteuid() == 0) {
+    // The super-user can acess the 'inaccessible' keytab file anyway.
+    ASSERT_TRUE(s.ok()) << s.ToString();
+  } else {
+    ASSERT_FALSE(s.ok()) << s.ToString();
 #ifndef KRB5_VERSION_LE_1_10
-  ASSERT_STR_MATCHES(s.ToString(), "error accessing keytab: Permission denied");
+    ASSERT_STR_MATCHES(s.ToString(), "error accessing keytab: Permission denied");
 #endif
+  }
   CHECK_ERR(unlink(kt_path.c_str()));
 
   // Try with a keytab that has the wrong credentials.
@@ -1344,3 +1358,7 @@ TEST_F(TestDisableInit, TestMultipleSaslInit_NoMutexImpl) {
 
 } // namespace rpc
 } // namespace kudu
+
+#if defined(__APPLE__)
+#pragma GCC diagnostic pop
+#endif // #if defined(__APPLE__)
diff --git a/be/src/kudu/rpc/negotiation.h b/be/src/kudu/rpc/negotiation.h
index b25ed0e..9f06c64 100644
--- a/be/src/kudu/rpc/negotiation.h
+++ b/be/src/kudu/rpc/negotiation.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_NEGOTIATION_H
-#define KUDU_RPC_NEGOTIATION_H
+#pragma once
 
 #include <iosfwd>
 
@@ -55,4 +54,3 @@ class Negotiation {
 
 } // namespace rpc
 } // namespace kudu
-#endif // KUDU_RPC_NEGOTIATION_H
diff --git a/be/src/kudu/rpc/outbound_call.cc b/be/src/kudu/rpc/outbound_call.cc
index 17761f5..3fe7641 100644
--- a/be/src/kudu/rpc/outbound_call.cc
+++ b/be/src/kudu/rpc/outbound_call.cc
@@ -15,11 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include "kudu/rpc/outbound_call.h"
+
 #include <cstdint>
 #include <memory>
 #include <mutex>
 #include <string>
-#include <type_traits>
 #include <unordered_set>
 #include <utility>
 #include <vector>
@@ -34,7 +35,6 @@
 #include "kudu/gutil/sysinfo.h"
 #include "kudu/gutil/walltime.h"
 #include "kudu/rpc/constants.h"
-#include "kudu/rpc/outbound_call.h"
 #include "kudu/rpc/rpc_controller.h"
 #include "kudu/rpc/rpc_introspection.pb.h"
 #include "kudu/rpc/rpc_sidecar.h"
@@ -289,7 +289,7 @@ void OutboundCall::CallCallback() {
   }
 }
 
-void OutboundCall::SetResponse(gscoped_ptr<CallResponse> resp) {
+void OutboundCall::SetResponse(unique_ptr<CallResponse> resp) {
   call_response_ = std::move(resp);
   Slice r(call_response_->serialized_response());
 
@@ -508,7 +508,7 @@ Status CallResponse::GetSidecar(int idx, Slice* sidecar) const {
   return Status::OK();
 }
 
-Status CallResponse::ParseFrom(gscoped_ptr<InboundTransfer> transfer) {
+Status CallResponse::ParseFrom(unique_ptr<InboundTransfer> transfer) {
   CHECK(!parsed_);
   RETURN_NOT_OK(serialization::ParseMessage(transfer->data(), &header_,
                                             &serialized_response_));
diff --git a/be/src/kudu/rpc/outbound_call.h b/be/src/kudu/rpc/outbound_call.h
index c48e496..7cd5f01 100644
--- a/be/src/kudu/rpc/outbound_call.h
+++ b/be/src/kudu/rpc/outbound_call.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_CLIENT_CALL_H
-#define KUDU_RPC_CLIENT_CALL_H
+#pragma once
 
 #include <cstddef>
 #include <cstdint>
@@ -29,7 +28,6 @@
 #include <glog/logging.h>
 #include <gtest/gtest_prod.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/macros.h"
 #include "kudu/rpc/connection_id.h"
 #include "kudu/rpc/constants.h"
@@ -146,7 +144,7 @@ class OutboundCall {
   bool IsFinished() const;
 
   // Fill in the call response.
-  void SetResponse(gscoped_ptr<CallResponse> resp);
+  void SetResponse(std::unique_ptr<CallResponse> resp);
 
   const std::set<RpcFeatureFlag>& required_rpc_features() const {
     return required_rpc_features_;
@@ -268,7 +266,7 @@ class OutboundCall {
 
   // Once a response has been received for this call, contains that response.
   // Otherwise NULL.
-  gscoped_ptr<CallResponse> call_response_;
+  std::unique_ptr<CallResponse> call_response_;
 
   // All sidecars to be sent with this call.
   std::vector<std::unique_ptr<RpcSidecar>> sidecars_;
@@ -297,7 +295,7 @@ class CallResponse {
 
   // Parse the response received from a call. This must be called before any
   // other methods on this object.
-  Status ParseFrom(gscoped_ptr<InboundTransfer> transfer);
+  Status ParseFrom(std::unique_ptr<InboundTransfer> transfer);
 
   // Return true if the call succeeded.
   bool is_success() const {
@@ -337,12 +335,10 @@ class CallResponse {
 
   // The incoming transfer data - retained because serialized_response_
   // and sidecar_slices_ refer into its data.
-  gscoped_ptr<InboundTransfer> transfer_;
+  std::unique_ptr<InboundTransfer> transfer_;
 
   DISALLOW_COPY_AND_ASSIGN(CallResponse);
 };
 
 } // namespace rpc
 } // namespace kudu
-
-#endif
diff --git a/be/src/kudu/rpc/protoc-gen-krpc.cc b/be/src/kudu/rpc/protoc-gen-krpc.cc
index cc25f67..840e549 100644
--- a/be/src/kudu/rpc/protoc-gen-krpc.cc
+++ b/be/src/kudu/rpc/protoc-gen-krpc.cc
@@ -20,11 +20,11 @@
 // protoc --plugin=protoc-gen-krpc --krpc_out . --proto_path . <file>.proto
 ////////////////////////////////////////////////////////////////////////////////
 
-#include <cstddef>
 #include <map>
 #include <memory>
 #include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <boost/optional/optional.hpp>
@@ -36,8 +36,6 @@
 #include <google/protobuf/io/printer.h>
 #include <google/protobuf/io/zero_copy_stream.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
-#include "kudu/gutil/port.h"
 #include "kudu/gutil/strings/join.h"
 #include "kudu/gutil/strings/numbers.h"
 #include "kudu/gutil/strings/split.h"
@@ -46,8 +44,6 @@
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/gutil/strings/util.h"
 #include "kudu/rpc/rpc_header.pb.h"
-#include "kudu/util/status.h"
-#include "kudu/util/string_case.h"
 
 using boost::optional;
 using google::protobuf::FileDescriptor;
@@ -55,10 +51,12 @@ using google::protobuf::io::Printer;
 using google::protobuf::MethodDescriptor;
 using google::protobuf::ServiceDescriptor;
 using std::map;
-using std::shared_ptr;
 using std::set;
 using std::string;
+using std::unique_ptr;
 using std::vector;
+using strings::Split;
+using strings::Substitute;
 
 namespace kudu {
 namespace rpc {
@@ -84,93 +82,65 @@ optional<string> GetAuthzMethod(const MethodDescriptor& method) {
 class Substituter {
  public:
   virtual ~Substituter() {}
-  virtual void InitSubstitutionMap(map<string, string> *map) const = 0;
+  virtual void InitSubstitutionMap(map<string, string>* map) const = 0;
 };
 
 // NameInfo contains information about the output names.
 class FileSubstitutions : public Substituter {
  public:
-  static const std::string kProtoExtension;
+  bool Init(const FileDescriptor* file, string* error) {
+    static const char* kProtoExtension = ".proto";
 
-  Status Init(const FileDescriptor *file) {
+    // If path = /foo/bar/baz_stuff.proto, path_no_extension = /foo/bar/baz_stuff
     const string& path = file->name();
-    map_["path"] = path;
-
-    // Initialize path_
-    // If path = /foo/bar/baz_stuff.proto, path_ = /foo/bar/baz_stuff
     if (!TryStripSuffixString(path, kProtoExtension, &path_no_extension_)) {
-      return Status::InvalidArgument("file name " + path +
-                                     " did not end in " + kProtoExtension);
+      *error = Substitute("name_info.Init failed: file name $0 did not end in $1",
+                          path, kProtoExtension);
+      return false;
     }
+    map_["path"] = path;
     map_["path_no_extension"] = path_no_extension_;
 
-    // If path = /foo/bar/baz_stuff.proto, base_ = baz_stuff
-    string base;
-    GetBaseName(path_no_extension_, &base);
-    map_["base"] = base;
-
-    // If path = /foo/bar/baz_stuff.proto, camel_case_ = BazStuff
-    string camel_case;
-    SnakeToCamelCase(base, &camel_case);
-    map_["camel_case"] = camel_case;
-
-    // If path = /foo/bar/baz_stuff.proto, upper_case_ = BAZ_STUFF
-    string upper_case;
-    ToUpperCase(base, &upper_case);
-    map_["upper_case"] = upper_case;
-
     map_["open_namespace"] = GenerateOpenNamespace(file->package());
     map_["close_namespace"] = GenerateCloseNamespace(file->package());
 
-    return Status::OK();
+    return true;
   }
 
-  virtual void InitSubstitutionMap(map<string, string> *map) const OVERRIDE {
-    typedef std::map<string, string>::value_type kv_pair;
-    for (const kv_pair &pair : map_) {
+  void InitSubstitutionMap(map<string, string>* map) const override {
+    for (const auto& pair : map_) {
       (*map)[pair.first] = pair.second;
     }
   }
 
-  std::string service_header() const {
+  string service_header() const {
     return path_no_extension_ + ".service.h";
   }
 
-  std::string service() const {
+  string service() const {
     return path_no_extension_ + ".service.cc";
   }
 
-  std::string proxy_header() const {
+  string proxy_header() const {
     return path_no_extension_ + ".proxy.h";
   }
 
-  std::string proxy() const {
+  string proxy() const {
     return path_no_extension_ + ".proxy.cc";
   }
 
  private:
-  // Extract the last filename component.
-  static void GetBaseName(const string &path,
-                          string *base) {
-    size_t last_slash = path.find_last_of('/');
-    if (last_slash != string::npos) {
-      *base = path.substr(last_slash + 1);
-    } else {
-      *base = path;
-    }
-  }
-
-  static string GenerateOpenNamespace(const string &str) {
-    vector<string> components = strings::Split(str, ".");
+  static string GenerateOpenNamespace(const string& str) {
+    vector<string> components = Split(str, ".");
     string out;
-    for (const string &c : components) {
+    for (const auto& c : components) {
       out.append("namespace ").append(c).append(" {\n");
     }
     return out;
   }
 
-  static string GenerateCloseNamespace(const string &str) {
-    vector<string> components = strings::Split(str, ".");
+  static string GenerateCloseNamespace(const string& str) {
+    vector<string> components = Split(str, ".");
     string out;
     for (auto c = components.crbegin(); c != components.crend(); c++) {
       out.append("} // namespace ").append(*c).append("\n");
@@ -178,20 +148,17 @@ class FileSubstitutions : public Substituter {
     return out;
   }
 
-  std::string path_no_extension_;
+  string path_no_extension_;
   map<string, string> map_;
 };
 
-const std::string FileSubstitutions::kProtoExtension(".proto");
-
 class MethodSubstitutions : public Substituter {
  public:
-  explicit MethodSubstitutions(const MethodDescriptor *method)
+  explicit MethodSubstitutions(const MethodDescriptor* method)
     : method_(method) {
   }
 
-  virtual void InitSubstitutionMap(map<string, string> *map) const OVERRIDE {
-
+  void InitSubstitutionMap(map<string, string>* map) const override {
     (*map)["rpc_name"] = method_->name();
     (*map)["rpc_full_name"] = method_->full_name();
     (*map)["rpc_full_name_plainchars"] =
@@ -204,7 +171,7 @@ class MethodSubstitutions : public Substituter {
         ReplaceNamespaceDelimiters(
             StripNamespaceIfPossible(method_->service()->full_name(),
                                      method_->output_type()->full_name()));
-    (*map)["metric_enum_key"] = strings::Substitute("kMetricIndex$0", method_->name());
+    (*map)["metric_enum_key"] = Substitute("kMetricIndex$0", method_->name());
     bool track_result = static_cast<bool>(method_->options().GetExtension(track_rpc_result));
     (*map)["track_result"] = track_result ? " true" : "false";
     (*map)["authz_method"] = GetAuthzMethod(*method_).get_value_or("AuthorizeAllowAll");
@@ -213,8 +180,8 @@ class MethodSubstitutions : public Substituter {
   // Strips the package from method arguments if they are in the same package as
   // the service, otherwise leaves them so that we can have fully qualified
   // namespaces for method arguments.
-  static std::string StripNamespaceIfPossible(const std::string& service_full_name,
-                                              const std::string& arg_full_name) {
+  static string StripNamespaceIfPossible(const string& service_full_name,
+                                         const string& arg_full_name) {
     StringPiece service_package(service_full_name);
     if (!service_package.contains(".")) {
       return arg_full_name;
@@ -231,21 +198,21 @@ class MethodSubstitutions : public Substituter {
     return argfqn.ToString();
   }
 
-  static std::string ReplaceNamespaceDelimiters(const std::string& arg_full_name) {
+  static string ReplaceNamespaceDelimiters(const string& arg_full_name) {
     return JoinStrings(strings::Split(arg_full_name, "."), "::");
   }
 
  private:
-  const MethodDescriptor *method_;
+  const MethodDescriptor* method_;
 };
 
 class ServiceSubstitutions : public Substituter {
  public:
-  explicit ServiceSubstitutions(const ServiceDescriptor *service)
+  explicit ServiceSubstitutions(const ServiceDescriptor* service)
     : service_(service)
   {}
 
-  virtual void InitSubstitutionMap(map<string, string> *map) const OVERRIDE {
+  void InitSubstitutionMap(map<string, string>* map) const override {
     (*map)["service_name"] = service_->name();
     (*map)["full_service_name"] = service_->full_name();
     (*map)["service_method_count"] = SimpleItoa(service_->method_count());
@@ -255,23 +222,23 @@ class ServiceSubstitutions : public Substituter {
   }
 
  private:
-  const ServiceDescriptor *service_;
+  const ServiceDescriptor* service_;
 };
 
 
 class SubstitutionContext {
  public:
-  // Takes ownership of the substituter
-  void Push(const Substituter *sub) {
-    subs_.push_back(shared_ptr<const Substituter>(sub));
+  // Takes ownership of the substituter.
+  void Push(unique_ptr<const Substituter> sub) {
+    subs_.emplace_back(std::move(sub));
   }
 
-  void PushMethod(const MethodDescriptor *method) {
-    Push(new MethodSubstitutions(method));
+  void PushMethod(const MethodDescriptor* method) {
+    Push(unique_ptr<const Substituter>(new MethodSubstitutions(method)));
   }
 
-  void PushService(const ServiceDescriptor *service) {
-    Push(new ServiceSubstitutions(service));
+  void PushService(const ServiceDescriptor* service) {
+    Push(unique_ptr<const Substituter>(new ServiceSubstitutions(service)));
   }
 
   void Pop() {
@@ -279,14 +246,14 @@ class SubstitutionContext {
     subs_.pop_back();
   }
 
-  void InitSubstitutionMap(map<string, string> *subs) const {
-    for (const shared_ptr<const Substituter> &sub : subs_) {
+  void InitSubstitutionMap(map<string, string>* subs) const {
+    for (const auto& sub : subs_) {
       sub->InitSubstitutionMap(subs);
     }
   }
 
  private:
-  vector<shared_ptr<const Substituter> > subs_;
+  vector<unique_ptr<const Substituter>> subs_;
 };
 
 
@@ -297,37 +264,39 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
 
   ~CodeGenerator() { }
 
-  bool Generate(const google::protobuf::FileDescriptor *file,
-        const std::string &/* parameter */,
-        google::protobuf::compiler::GeneratorContext *gen_context,
-        std::string *error) const OVERRIDE {
-    auto name_info = new FileSubstitutions();
-    Status ret = name_info->Init(file);
-    if (!ret.ok()) {
-      *error = "name_info.Init failed: " + ret.ToString();
+  bool Generate(const google::protobuf::FileDescriptor* file,
+        const string&/* parameter */,
+        google::protobuf::compiler::GeneratorContext* gen_context,
+        string* error) const override {
+    unique_ptr<FileSubstitutions> name_info(new FileSubstitutions());
+    bool ret = name_info->Init(file, error);
+    if (!ret) {
       return false;
     }
 
+    // 'subs' takes ownership but we need to keep using it.
+    const FileSubstitutions* name_info_ptr = name_info.get();
+
     SubstitutionContext subs;
-    subs.Push(name_info);
+    subs.Push(std::move(name_info));
 
-    gscoped_ptr<google::protobuf::io::ZeroCopyOutputStream> ih_output(
-        gen_context->Open(name_info->service_header()));
+    unique_ptr<google::protobuf::io::ZeroCopyOutputStream> ih_output(
+        gen_context->Open(name_info_ptr->service_header()));
     Printer ih_printer(ih_output.get(), '$');
     GenerateServiceIfHeader(&ih_printer, &subs, file);
 
-    gscoped_ptr<google::protobuf::io::ZeroCopyOutputStream> i_output(
-        gen_context->Open(name_info->service()));
+    unique_ptr<google::protobuf::io::ZeroCopyOutputStream> i_output(
+        gen_context->Open(name_info_ptr->service()));
     Printer i_printer(i_output.get(), '$');
     GenerateServiceIf(&i_printer, &subs, file);
 
-    gscoped_ptr<google::protobuf::io::ZeroCopyOutputStream> ph_output(
-        gen_context->Open(name_info->proxy_header()));
+    unique_ptr<google::protobuf::io::ZeroCopyOutputStream> ph_output(
+        gen_context->Open(name_info_ptr->proxy_header()));
     Printer ph_printer(ph_output.get(), '$');
     GenerateProxyHeader(&ph_printer, &subs, file);
 
-    gscoped_ptr<google::protobuf::io::ZeroCopyOutputStream> p_output(
-        gen_context->Open(name_info->proxy()));
+    unique_ptr<google::protobuf::io::ZeroCopyOutputStream> p_output(
+        gen_context->Open(name_info_ptr->proxy()));
     Printer p_printer(p_output.get(), '$');
     GenerateProxy(&p_printer, &subs, file);
 
@@ -335,22 +304,21 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
   }
 
  private:
-  void Print(Printer *printer,
-             const SubstitutionContext &sub,
-             const char *text) const {
+  static void Print(Printer* printer,
+                    const SubstitutionContext& sub,
+                    const char* text) {
     map<string, string> subs;
     sub.InitSubstitutionMap(&subs);
     printer->Print(subs, text);
   }
 
-  void GenerateServiceIfHeader(Printer *printer,
-                               SubstitutionContext *subs,
-                               const FileDescriptor *file) const {
+  static void GenerateServiceIfHeader(Printer* printer,
+                                      SubstitutionContext* subs,
+                                      const FileDescriptor* file) {
     Print(printer, *subs,
       "// THIS FILE IS AUTOGENERATED FROM $path$\n"
       "\n"
-      "#ifndef KUDU_RPC_$upper_case$_SERVICE_IF_DOT_H\n"
-      "#define KUDU_RPC_$upper_case$_SERVICE_IF_DOT_H\n"
+      "#pragma once\n"
       "\n"
       "#include <string>\n"
       "\n"
@@ -376,7 +344,7 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
 
     for (int service_idx = 0; service_idx < file->service_count();
          ++service_idx) {
-      const ServiceDescriptor *service = file->service(service_idx);
+      const ServiceDescriptor* service = file->service(service_idx);
       subs->PushService(service);
 
       Print(printer, *subs,
@@ -400,7 +368,7 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
         "  virtual void $rpc_name$(const class $request$ *req,\n"
         "      class $response$ *resp, ::kudu::rpc::RpcContext *context) = 0;\n"
         );
-        subs->Pop();
+        subs->Pop(); // method
         if (auto m = GetAuthzMethod(*method)) {
           authz_methods.insert(m.get());
         }
@@ -428,13 +396,13 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
 
     Print(printer, *subs,
       "\n"
-      "$close_namespace$\n"
-      "#endif\n");
+      "$close_namespace$"
+      "\n");
   }
 
-  void GenerateServiceIf(Printer *printer,
-                         SubstitutionContext *subs,
-                         const FileDescriptor *file) const {
+  static void GenerateServiceIf(Printer* printer,
+                                SubstitutionContext* subs,
+                                const FileDescriptor* file) {
     Print(printer, *subs,
       "// THIS FILE IS AUTOGENERATED FROM $path$\n"
       "\n"
@@ -456,24 +424,25 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
     // Define metric prototypes for each method in the service.
     for (int service_idx = 0; service_idx < file->service_count();
         ++service_idx) {
-      const ServiceDescriptor *service = file->service(service_idx);
+      const ServiceDescriptor* service = file->service(service_idx);
       subs->PushService(service);
 
       for (int method_idx = 0; method_idx < service->method_count();
           ++method_idx) {
-        const MethodDescriptor *method = service->method(method_idx);
+        const MethodDescriptor* method = service->method(method_idx);
         subs->PushMethod(method);
         Print(printer, *subs,
           "METRIC_DEFINE_histogram(server, handler_latency_$rpc_full_name_plainchars$,\n"
           "  \"$rpc_full_name$ RPC Time\",\n"
           "  kudu::MetricUnit::kMicroseconds,\n"
           "  \"Microseconds spent handling $rpc_full_name$() RPC requests\",\n"
+          "  kudu::MetricLevel::kInfo,\n"
           "  60000000LU, 2);\n"
           "\n");
-        subs->Pop();
+        subs->Pop(); // method
       }
 
-      subs->Pop();
+      subs->Pop(); // service
     }
 
     Print(printer, *subs,
@@ -489,7 +458,7 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
 
     for (int service_idx = 0; service_idx < file->service_count();
          ++service_idx) {
-      const ServiceDescriptor *service = file->service(service_idx);
+      const ServiceDescriptor* service = file->service(service_idx);
       subs->PushService(service);
 
       Print(printer, *subs,
@@ -499,7 +468,7 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
       );
       for (int method_idx = 0; method_idx < service->method_count();
            ++method_idx) {
-        const MethodDescriptor *method = service->method(method_idx);
+        const MethodDescriptor* method = service->method(method_idx);
         subs->PushMethod(method);
 
         Print(printer, *subs,
@@ -523,7 +492,7 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
               "    };\n"
               "    methods_by_name_[\"$rpc_name$\"] = std::move(mi);\n"
               "  }\n");
-        subs->Pop();
+        subs->Pop(); // method
       }
 
       Print(printer, *subs,
@@ -541,22 +510,23 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
         "\n"
       );
 
-      subs->Pop();
+      subs->Pop(); // service
     }
 
     Print(printer, *subs,
+      "\n"
       "$close_namespace$"
+      "\n"
       );
   }
 
-  void GenerateProxyHeader(Printer *printer,
-                           SubstitutionContext *subs,
-                           const FileDescriptor *file) const {
+  static void GenerateProxyHeader(Printer* printer,
+                                  SubstitutionContext* subs,
+                                  const FileDescriptor* file) {
     Print(printer, *subs,
       "// THIS FILE IS AUTOGENERATED FROM $path$\n"
       "\n"
-      "#ifndef KUDU_RPC_$upper_case$_PROXY_DOT_H\n"
-      "#define KUDU_RPC_$upper_case$_PROXY_DOT_H\n"
+      "#pragma once\n"
       "\n"
       "#include <memory>\n"
       "#include <string>\n"
@@ -578,7 +548,7 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
 
     for (int service_idx = 0; service_idx < file->service_count();
          ++service_idx) {
-      const ServiceDescriptor *service = file->service(service_idx);
+      const ServiceDescriptor* service = file->service(service_idx);
       subs->PushService(service);
 
       Print(printer, *subs,
@@ -593,7 +563,7 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
 
       for (int method_idx = 0; method_idx < service->method_count();
            ++method_idx) {
-        const MethodDescriptor *method = service->method(method_idx);
+        const MethodDescriptor* method = service->method(method_idx);
         subs->PushMethod(method);
 
         Print(printer, *subs,
@@ -606,23 +576,22 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
         "                       ::kudu::rpc::RpcController *controller,\n"
         "                       const ::kudu::rpc::ResponseCallback &callback);\n"
         );
-        subs->Pop();
+        subs->Pop(); // method
       }
       Print(printer, *subs,
       "};\n");
-      subs->Pop();
+      subs->Pop(); // service
     }
     Print(printer, *subs,
       "\n"
       "$close_namespace$"
       "\n"
-      "#endif\n"
-      );
+    );
   }
 
-  void GenerateProxy(Printer *printer,
-                     SubstitutionContext *subs,
-                     const FileDescriptor *file) const {
+  static void GenerateProxy(Printer* printer,
+                            SubstitutionContext* subs,
+                            const FileDescriptor* file) {
     Print(printer, *subs,
       "// THIS FILE IS AUTOGENERATED FROM $path$\n"
       "\n"
@@ -644,7 +613,7 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
 
     for (int service_idx = 0; service_idx < file->service_count();
          ++service_idx) {
-      const ServiceDescriptor *service = file->service(service_idx);
+      const ServiceDescriptor* service = file->service(service_idx);
       subs->PushService(service);
       Print(printer, *subs,
         "$service_name$Proxy::$service_name$Proxy(\n"
@@ -659,7 +628,7 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
         "\n");
       for (int method_idx = 0; method_idx < service->method_count();
            ++method_idx) {
-        const MethodDescriptor *method = service->method(method_idx);
+        const MethodDescriptor* method = service->method(method_idx);
         subs->PushMethod(method);
         Print(printer, *subs,
         "::kudu::Status $service_name$Proxy::$rpc_name$(const $request$ &req, $response$ *resp,\n"
@@ -673,19 +642,21 @@ class CodeGenerator : public ::google::protobuf::compiler::CodeGenerator {
         "  AsyncRequest(\"$rpc_name$\", req, resp, controller, callback);\n"
         "}\n"
         "\n");
-        subs->Pop();
+        subs->Pop(); // method
       }
 
-      subs->Pop();
+      subs->Pop(); // service
     }
     Print(printer, *subs,
-      "$close_namespace$");
+      "\n"
+      "$close_namespace$"
+      "\n");
   }
 };
 } // namespace rpc
 } // namespace kudu
 
-int main(int argc, char *argv[]) {
+int main(int argc, char* argv[]) {
   kudu::rpc::CodeGenerator generator;
   return google::protobuf::compiler::PluginMain(argc, argv, &generator);
 }
diff --git a/be/src/kudu/rpc/proxy.h b/be/src/kudu/rpc/proxy.h
index 641c514..ccf5f18 100644
--- a/be/src/kudu/rpc/proxy.h
+++ b/be/src/kudu/rpc/proxy.h
@@ -14,9 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_RPC_PROXY_H
-#define KUDU_RPC_PROXY_H
+#pragma once
 
 #include <memory>
 #include <string>
@@ -133,5 +131,3 @@ class Proxy {
 
 } // namespace rpc
 } // namespace kudu
-
-#endif
diff --git a/be/src/kudu/rpc/reactor.cc b/be/src/kudu/rpc/reactor.cc
index a3e56f7..4ed2f5b 100644
--- a/be/src/kudu/rpc/reactor.cc
+++ b/be/src/kudu/rpc/reactor.cc
@@ -102,7 +102,9 @@ METRIC_DEFINE_histogram(server, reactor_load_percent,
                         "The percentage of time that the reactor is busy "
                         "(not blocked awaiting network activity). If this metric "
                         "shows significant samples nears 100%, increasing the "
-                        "number of reactors may be beneficial.", 100, 2);
+                        "number of reactors may be beneficial.",
+                        kudu::MetricLevel::kInfo,
+                        100, 2);
 
 METRIC_DEFINE_histogram(server, reactor_active_latency_us,
                         "Reactor Thread Active Latency",
@@ -111,6 +113,7 @@ METRIC_DEFINE_histogram(server, reactor_active_latency_us,
                         "The reactor thread is responsible for all network I/O and "
                         "therefore outliers in this latency histogram directly contribute "
                         "to the latency of both inbound and outbound RPCs.",
+                        kudu::MetricLevel::kInfo,
                         1000000, 2);
 
 namespace kudu {
diff --git a/be/src/kudu/rpc/reactor.h b/be/src/kudu/rpc/reactor.h
index 4ab7ef6..1767357 100644
--- a/be/src/kudu/rpc/reactor.h
+++ b/be/src/kudu/rpc/reactor.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_REACTOR_H
-#define KUDU_RPC_REACTOR_H
+#pragma once
 
 #include <cstdint>
 #include <list>
@@ -428,5 +427,3 @@ class Reactor {
 
 } // namespace rpc
 } // namespace kudu
-
-#endif
diff --git a/be/src/kudu/rpc/remote_method.h b/be/src/kudu/rpc/remote_method.h
index a0a35bb..b5e42ae 100644
--- a/be/src/kudu/rpc/remote_method.h
+++ b/be/src/kudu/rpc/remote_method.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_REMOTE_METHOD_H_
-#define KUDU_RPC_REMOTE_METHOD_H_
+#pragma once
 
 #include <string>
 
@@ -47,5 +46,3 @@ class RemoteMethod {
 
 } // namespace rpc
 } // namespace kudu
-
-#endif // KUDU_RPC_REMOTE_METHOD_H_
diff --git a/be/src/kudu/rpc/response_callback.h b/be/src/kudu/rpc/response_callback.h
index 8c4fc03..5a70a79 100644
--- a/be/src/kudu/rpc/response_callback.h
+++ b/be/src/kudu/rpc/response_callback.h
@@ -14,9 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_RPC_RESPONSE_CALLBACK_H
-#define KUDU_RPC_RESPONSE_CALLBACK_H
+#pragma once
 
 #include <boost/function.hpp>
 
@@ -25,7 +23,5 @@ namespace rpc {
 
 typedef boost::function<void()> ResponseCallback;
 
-}
-}
-
-#endif
+} // namespace rpc
+} // namespace kudu
diff --git a/be/src/kudu/rpc/result_tracker.cc b/be/src/kudu/rpc/result_tracker.cc
index d26ff87..f8f5c46 100644
--- a/be/src/kudu/rpc/result_tracker.cc
+++ b/be/src/kudu/rpc/result_tracker.cc
@@ -556,8 +556,7 @@ void ResultTracker::ClientState::GCCompletionRecords(
 }
 
 string ResultTracker::ClientState::ToString() const {
-  auto since_last_heard =
-      MonoTime::Now().GetDeltaSince(last_heard_from);
+  auto since_last_heard = MonoTime::Now() - last_heard_from;
   string result = Substitute("Client State[Last heard from: $0s ago, "
                              "$1 CompletionRecords:",
                              since_last_heard.ToString(),
diff --git a/be/src/kudu/rpc/retriable_rpc.h b/be/src/kudu/rpc/retriable_rpc.h
index ba84689..9a0b8f5 100644
--- a/be/src/kudu/rpc/retriable_rpc.h
+++ b/be/src/kudu/rpc/retriable_rpc.h
@@ -21,10 +21,11 @@
 
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/gutil/strings/substitute.h"
+#include "kudu/rpc/messenger.h"
 #include "kudu/rpc/request_tracker.h"
+#include "kudu/rpc/response_callback.h"
 #include "kudu/rpc/rpc.h"
 #include "kudu/rpc/rpc_header.pb.h"
-#include "kudu/rpc/messenger.h"
 #include "kudu/util/monotime.h"
 
 namespace kudu {
@@ -34,28 +35,31 @@ namespace internal {
 typedef rpc::RequestTracker::SequenceNumber SequenceNumber;
 }
 
-// A base class for retriable RPCs that handles replica picking and retry logic.
+// A base class for retriable RPCs to servers that handles replica picking and
+// retry logic.
 //
-// The 'Server' template parameter refers to the type of the server that will be looked up
-// and passed to the derived classes on Try(). For instance in the case of WriteRpc it's
-// RemoteTabletServer.
+// The 'Server' template parameter refers to the type of the server that will
+// be looked up and passed to the derived classes on Try(). For instance in the
+// case of WriteRpc it's RemoteTabletServer.
 //
-// TODO(unknown): merge RpcRetrier into this class? Can't be done right now as the retrier is used
-// independently elsewhere, but likely possible when all replicated RPCs have a ReplicaPicker.
+// Note: unlike the vanilla RpcRetrier, that provides the mechanism with which
+// to retry, RetriableRpc provides the interfaces to perform more complex
+// handling of error responses, also taking into account server locations, etc.
 //
 // TODO(unknown): allow to target replicas other than the leader, if needed.
 //
-// TODO(unknown): once we have retry handling on all the RPCs merge this with rpc::Rpc.
+// TODO(awong): there might be room to merge part of this with retry logic in
+// AsyncLeaderMasterRpc; namely, the generic error-handling in the RPC layer.
 template <class Server, class RequestPB, class ResponsePB>
 class RetriableRpc : public Rpc {
  public:
-  RetriableRpc(const scoped_refptr<ServerPicker<Server>>& server_picker,
-               const scoped_refptr<RequestTracker>& request_tracker,
+  RetriableRpc(scoped_refptr<ServerPicker<Server>> server_picker,
+               scoped_refptr<RequestTracker> request_tracker,
                const MonoTime& deadline,
                std::shared_ptr<Messenger> messenger)
-      : Rpc(deadline, std::move(messenger)),
-        server_picker_(server_picker),
-        request_tracker_(request_tracker),
+      : Rpc(deadline, std::move(messenger), BackoffType::LINEAR),
+        server_picker_(std::move(server_picker)),
+        request_tracker_(std::move(request_tracker)),
         sequence_number_(RequestTracker::kNoSeqNo),
         num_attempts_(0) {}
 
@@ -68,11 +72,6 @@ class RetriableRpc : public Rpc {
   // Try() to actually send the request.
   void SendRpc() override;
 
-  // The callback to call upon retrieving (of failing to retrieve) a new authn
-  // token. This is the callback that subclasses should call in their custom
-  // implementation of the GetNewAuthnTokenAndRetry() method.
-  void GetNewAuthnTokenAndRetryCb(const Status& status);
-
  protected:
   // Subclasses implement this method to actually try the RPC.
   // The server been looked up and is ready to be used.
@@ -87,15 +86,30 @@ class RetriableRpc : public Rpc {
   // After this is called the RPC will be no longer retried.
   virtual void Finish(const Status& status) = 0;
 
-  // Returns 'true' if the RPC is to scheduled for retry with a new authn token,
-  // 'false' otherwise. For RPCs performed in the context of providing token
-  // for authentication it's necessary to implement this method. The default
-  // implementation returns 'false' meaning the calls returning
+  // Returns 'true' if the RPC is scheduled for retry with a new authn
+  // token, 'false' otherwise. For RPCs performed in the context of providing
+  // token for authentication it's necessary to implement this method. The
+  // default implementation returns 'false' meaning the calls returning
   // INVALID_AUTHENTICATION_TOKEN RPC status are not retried.
   virtual bool GetNewAuthnTokenAndRetry() {
     return false;
   }
 
+  // Similar to GetNewAuthnTokenAndRetry() but applied for authz tokens. The
+  // default implementation returns 'false', meaning the calls returning
+  // INVALID_AUTHORIZATION_TOKEN RPC status are not retried.
+  virtual bool GetNewAuthzTokenAndRetry() {
+    return false;
+  }
+
+  // The callback to call upon retrieving (or failing to retrieve) a new authn
+  // token. This is the callback that subclasses should call in their custom
+  // implementation of the GetNewAuthnTokenAndRetry() method.
+  virtual void GotNewAuthnTokenRetryCb(const Status& status);
+
+  // Like GotNewAuthnTokenRetryCb() but for authz tokens.
+  virtual void GotNewAuthzTokenRetryCb(const Status& status);
+
   // Request body.
   RequestPB req_;
 
@@ -105,6 +119,10 @@ class RetriableRpc : public Rpc {
  private:
   friend class CalculatorServiceRpc;
 
+  // The callback to call upon retrieving (or failing to retrieve) a new token.
+  // 'token_type' is used for logging.
+  void GotNewTokenRetryCb(const Status& status, const char* token_type);
+
   // Decides whether to retry the RPC, based on the result of AnalyzeResponse()
   // and retries if that is the case.
   // Returns true if the RPC was retried or false otherwise.
@@ -147,20 +165,36 @@ void RetriableRpc<Server, RequestPB, ResponsePB>::SendRpc()  {
 }
 
 template <class Server, class RequestPB, class ResponsePB>
-void RetriableRpc<Server, RequestPB, ResponsePB>::GetNewAuthnTokenAndRetryCb(
-    const Status& status) {
+void RetriableRpc<Server, RequestPB, ResponsePB>::GotNewTokenRetryCb(
+    const Status& status, const char* token_type) {
   if (status.ok()) {
-    // Perform the RPC call with the newly fetched authn token.
+    // Perform the RPC call with the newly-fetched token.
     mutable_retrier()->mutable_controller()->Reset();
     SendRpc();
+  } else if (status.IsNotSupported()) {
+    // If the token retrieval isn't supported by the cluster, don't retry.
+    FinishInternal();
+    Finish(status);
   } else {
     // Back to the retry sequence, hoping for better conditions after some time.
-    VLOG(1) << "Failed to get new authn token: " << status.ToString();
+    VLOG(1) << strings::Substitute("Failed to get new $0 token: $1", token_type, status.ToString());
     mutable_retrier()->DelayedRetry(this, status);
   }
 }
 
 template <class Server, class RequestPB, class ResponsePB>
+void RetriableRpc<Server, RequestPB, ResponsePB>::GotNewAuthnTokenRetryCb(
+    const Status& status) {
+  GotNewTokenRetryCb(status, "authn");
+}
+
+template <class Server, class RequestPB, class ResponsePB>
+void RetriableRpc<Server, RequestPB, ResponsePB>::GotNewAuthzTokenRetryCb(
+    const Status& status) {
+  GotNewTokenRetryCb(status, "authz");
+}
+
+template <class Server, class RequestPB, class ResponsePB>
 bool RetriableRpc<Server, RequestPB, ResponsePB>::RetryIfNeeded(
     const RetriableRpcStatus& result, Server* server) {
   // Handle the cases where we retry.
@@ -203,13 +237,13 @@ bool RetriableRpc<Server, RequestPB, ResponsePB>::RetryIfNeeded(
     case RetriableRpcStatus::INVALID_AUTHENTICATION_TOKEN: {
       // This is a special case for retry: first it's necessary to get a new
       // authn token and then retry the operation with the new token.
-      if (GetNewAuthnTokenAndRetry()) {
-        // The RPC will be retried.
-        resp_.Clear();
-        return true;
-      }
-      // Do not retry.
-      return false;
+      return GetNewAuthnTokenAndRetry();
+    }
+
+    case RetriableRpcStatus::INVALID_AUTHORIZATION_TOKEN: {
+      // TODO(awong): It'd be nice if the response status that led us to
+      // retrieve a token and retry were propgated to the user.
+      return GetNewAuthzTokenAndRetry();
     }
 
     case RetriableRpcStatus::NON_RETRIABLE_ERROR:
diff --git a/be/src/kudu/rpc/rpc-bench.cc b/be/src/kudu/rpc/rpc-bench.cc
index 0331e8b..a825900 100644
--- a/be/src/kudu/rpc/rpc-bench.cc
+++ b/be/src/kudu/rpc/rpc-bench.cc
@@ -126,15 +126,10 @@ class RpcBench : public RpcTestBase {
     LOG(INFO) << "User CPU per req: " << user_cpu_micros_per_req << "us";
     LOG(INFO) << "Sys CPU per req:  " << sys_cpu_micros_per_req << "us";
     LOG(INFO) << "Ctx Sw. per req:  " << csw_per_req;
-    LOG(INFO) << "Server Reactor load (mean):     "
-              << reactor_load.MeanValue() << "%";
-    LOG(INFO) << "Server Reactor load (95p):      "
-              << reactor_load.ValueAtPercentile(95) << "%";
-    LOG(INFO) << "Server Reactor Latency (mean):  "
-              << reactor_latency.MeanValue() << "us";
-    LOG(INFO) << "Server Reactor Latency (95p):   "
-              << reactor_latency.ValueAtPercentile(95) << "us";
-
+    LOG(INFO) << "Server reactor load histogram";
+    reactor_load.DumpHumanReadable(&LOG(INFO));
+    LOG(INFO) << "Server reactor latency histogram";
+    reactor_latency.DumpHumanReadable(&LOG(INFO));
   }
 
  protected:
diff --git a/be/src/kudu/rpc/rpc-test-base.h b/be/src/kudu/rpc/rpc-test-base.h
index 2fb742e..fcc7452 100644
--- a/be/src/kudu/rpc/rpc-test-base.h
+++ b/be/src/kudu/rpc/rpc-test-base.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_RPC_TEST_BASE_H
-#define KUDU_RPC_RPC_TEST_BASE_H
+#pragma once
 
 #include <algorithm>
 #include <atomic>
@@ -212,7 +211,7 @@ class GenericCalculatorService : public ServiceIf {
 
     LOG(INFO) << "got call: " << pb_util::SecureShortDebugString(req);
     SleepFor(MonoDelta::FromMicroseconds(req.sleep_micros()));
-    MonoDelta duration(MonoTime::Now().GetDeltaSince(incoming->GetTimeReceived()));
+    MonoDelta duration(MonoTime::Now() - incoming->GetTimeReceived());
     CHECK_GE(duration.ToMicroseconds(), req.sleep_micros());
     SleepResponsePB resp;
     incoming->RespondSuccess(resp);
@@ -534,9 +533,10 @@ class RpcTestBase : public KuduTest {
     CHECK_OK(DoTestOutgoingSidecar(p, size1, size2));
   }
 
-  void DoTestExpectTimeout(const Proxy& p,
-                           const MonoDelta& timeout,
-                           bool* is_negotiaton_error = nullptr) {
+  static void DoTestExpectTimeout(const Proxy& p,
+                                  const MonoDelta& timeout,
+                                  bool will_be_cancelled = false,
+                                  bool* is_negotiaton_error = nullptr) {
     SleepRequestPB req;
     SleepResponsePB resp;
     // Sleep for 500ms longer than the call timeout.
@@ -555,13 +555,20 @@ class RpcTestBase : public KuduTest {
     }
 
     int expected_millis = timeout.ToMilliseconds();
-    int elapsed_millis = sw.elapsed().wall_millis();
+    int elapsed_millis = static_cast<int>(sw.elapsed().wall_millis());
 
-    // We shouldn't timeout significantly faster than our configured timeout.
-    EXPECT_GE(elapsed_millis, expected_millis - 10);
+    // We shouldn't timeout significantly faster than our configured timeout, unless the
+    // rpc is cancelled.
+    if (!will_be_cancelled) {
+      EXPECT_GE(elapsed_millis, expected_millis - 10);
+    }
     // And we also shouldn't take the full time that we asked for
     EXPECT_LT(elapsed_millis * 1000, sleep_micros);
-    EXPECT_TRUE(s.IsTimedOut());
+    if (will_be_cancelled) {
+      EXPECT_TRUE(s.IsAborted());
+    } else {
+      EXPECT_TRUE(s.IsTimedOut());
+    }
     LOG(INFO) << "status: " << s.ToString() << ", seconds elapsed: " << sw.elapsed().wall_seconds();
   }
 
@@ -631,7 +638,7 @@ class RpcTestBase : public KuduTest {
     mem_tracker_ = MemTracker::CreateTracker(-1, "result_tracker");
     result_tracker_.reset(new ResultTracker(mem_tracker_));
 
-    gscoped_ptr<ServiceIf> service(new ServiceClass(metric_entity_, result_tracker_));
+    std::unique_ptr<ServiceIf> service(new ServiceClass(metric_entity_, result_tracker_));
     service_name_ = service->service_name();
     scoped_refptr<MetricEntity> metric_entity = server_messenger_->metric_entity();
     service_pool_ = new ServicePool(std::move(service), metric_entity, service_queue_length_);
@@ -658,4 +665,3 @@ class RpcTestBase : public KuduTest {
 
 } // namespace rpc
 } // namespace kudu
-#endif
diff --git a/be/src/kudu/rpc/rpc-test.cc b/be/src/kudu/rpc/rpc-test.cc
index 5f2be73..4258de2 100644
--- a/be/src/kudu/rpc/rpc-test.cc
+++ b/be/src/kudu/rpc/rpc-test.cc
@@ -15,8 +15,6 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#include "kudu/rpc/rpc-test-base.h"
-
 #include <cerrno>
 #include <cstdint>
 #include <cstdlib>
@@ -26,13 +24,11 @@
 #include <ostream>
 #include <set>
 #include <string>
-#include <unistd.h>
 #include <unordered_map>
 #include <vector>
 
 #include <boost/bind.hpp>
 #include <boost/core/ref.hpp>
-#include <boost/function.hpp>
 #include <gflags/gflags_declare.h>
 #include <glog/logging.h>
 #include <gtest/gtest.h>
@@ -49,6 +45,7 @@
 #include "kudu/rpc/outbound_call.h"
 #include "kudu/rpc/proxy.h"
 #include "kudu/rpc/reactor.h"
+#include "kudu/rpc/rpc-test-base.h"
 #include "kudu/rpc/rpc_controller.h"
 #include "kudu/rpc/rpc_introspection.pb.h"
 #include "kudu/rpc/rpc_sidecar.h"
@@ -498,59 +495,77 @@ TEST_P(TestRpc, TestConnectionAlwaysKeepalive) {
 
 // Test that the metrics on a per connection level work accurately.
 TEST_P(TestRpc, TestClientConnectionMetrics) {
-  // Only run one reactor per messenger, so we can grab the metrics from that
-  // one without having to check all.
-  n_server_reactor_threads_ = 1;
-  keepalive_time_ms_ = -1;
-
   // Set up server.
   Sockaddr server_addr;
   bool enable_ssl = GetParam();
   ASSERT_OK(StartTestServer(&server_addr, enable_ssl));
 
-  // Set up client.
+  // Set up client with one reactor so that we can grab the metrics from just
+  // that reactor.
   LOG(INFO) << "Connecting to " << server_addr.ToString();
   shared_ptr<Messenger> client_messenger;
   ASSERT_OK(CreateMessenger("Client", &client_messenger, 1, enable_ssl));
   Proxy p(client_messenger, server_addr, server_addr.host(),
           GenericCalculatorService::static_service_name());
 
-  // Cause the reactor thread to be blocked for 2 seconds.
-  server_messenger_->ScheduleOnReactor(boost::bind(sleep, 2), MonoDelta::FromSeconds(0));
-
-  RpcController controller;
-  DumpRunningRpcsRequestPB dump_req;
-  DumpRunningRpcsResponsePB dump_resp;
-  dump_req.set_include_traces(false);
-
-  // We'll send several calls asynchronously to force RPC queueing on the sender side.
-  int n_calls = 1000;
-  AddRequestPB add_req;
-  add_req.set_x(rand());
-  add_req.set_y(rand());
-  AddResponsePB add_resp;
-
-  vector<unique_ptr<RpcController>> controllers;
-  CountDownLatch latch(n_calls);
-  for (int i = 0; i < n_calls; i++) {
-    controllers.emplace_back(new RpcController());
-    p.AsyncRequest(GenericCalculatorService::kAddMethodName, add_req, &add_resp,
-        controllers.back().get(), boost::bind(&CountDownLatch::CountDown, boost::ref(latch)));
-  }
-
-  // Since we blocked the only reactor thread for sometime, we should see RPCs queued on the
-  // OutboundTransfer queue, unless the main thread is very slow.
-  ASSERT_OK(client_messenger->DumpRunningRpcs(dump_req, &dump_resp));
-  ASSERT_EQ(1, dump_resp.outbound_connections_size());
-  ASSERT_GT(dump_resp.outbound_connections(0).outbound_queue_size(), 0);
+  // Here we queue a bunch of calls to the server and test that the sender's
+  // OutboundTransfer queue is indeed populated with those calls. Unfortunately,
+  // we have no surefire way of controlling the queue directly; a fast client
+  // reactor thread or a slow main thread could cause all of the outbound calls
+  // to be sent before we test the queue size, even though the server can't yet process them.
+  //
+  // So we repeat the entire exercise until we get a non-zero queue size.
+  ASSERT_EVENTUALLY([&]{
+    // We'll send several calls asynchronously to force RPC queueing on the sender side.
+    constexpr int n_calls = 1000;
+    AddRequestPB add_req;
+    add_req.set_x(rand());
+    add_req.set_y(rand());
+    AddResponsePB add_resp;
+
+    // Send the calls.
+    vector<unique_ptr<RpcController>> controllers;
+    CountDownLatch latch(n_calls);
+    for (int i = 0; i < n_calls; i++) {
+      controllers.emplace_back(new RpcController());
+      p.AsyncRequest(GenericCalculatorService::kAddMethodName, add_req, &add_resp,
+                     controllers.back().get(), boost::bind(
+                         &CountDownLatch::CountDown, boost::ref(latch)));
+    }
+    auto cleanup = MakeScopedCleanup([&](){
+      latch.Wait();
+    });
+
+    // Test the OutboundTransfer queue.
+    DumpConnectionsRequestPB dump_req;
+    DumpConnectionsResponsePB dump_resp;
+    dump_req.set_include_traces(false);
+    ASSERT_OK(client_messenger->DumpConnections(dump_req, &dump_resp));
+    ASSERT_EQ(1, dump_resp.outbound_connections_size());
+    const auto& conn = dump_resp.outbound_connections(0);
+    ASSERT_GT(conn.outbound_queue_size(), 0);
+
+#ifdef __linux__
+    // Test that the socket statistics are present. We only assert on those that
+    // we know to be present on all kernel versions.
+    ASSERT_TRUE(conn.has_socket_stats());
+    ASSERT_GT(conn.socket_stats().rtt(), 0);
+    ASSERT_GT(conn.socket_stats().rttvar(), 0);
+    ASSERT_GT(conn.socket_stats().snd_cwnd(), 0);
+    ASSERT_GT(conn.socket_stats().send_bytes_per_sec(), 0);
+    ASSERT_TRUE(conn.socket_stats().has_send_queue_bytes());
+    ASSERT_TRUE(conn.socket_stats().has_receive_queue_bytes());
+#endif
 
-  // Wait for the calls to be marked finished.
-  latch.Wait();
+    // Unblock all of the calls and wait for them to finish.
+    latch.Wait();
+    cleanup.cancel();
 
-  // Verify that all the RPCs have finished.
-  for (const auto& controller : controllers) {
-    ASSERT_TRUE(controller->finished());
-  }
+    // Verify that all the RPCs have finished.
+    for (const auto& controller : controllers) {
+      ASSERT_TRUE(controller->finished());
+    }
+  });
 }
 
 // Test that outbound connections to the same server are reopen upon every RPC
@@ -927,15 +942,15 @@ TEST_P(TestRpc, TestCallTimeout) {
   // Test a very short timeout - we expect this will time out while the
   // call is still trying to connect, or in the send queue. This was triggering ASAN failures
   // before.
-  ASSERT_NO_FATAL_FAILURE(DoTestExpectTimeout(p, MonoDelta::FromNanoseconds(1)));
+  NO_FATALS(DoTestExpectTimeout(p, MonoDelta::FromNanoseconds(1)));
 
   // Test a longer timeout - expect this will time out after we send the request,
   // but shorter than our threshold for two-stage timeout handling.
-  ASSERT_NO_FATAL_FAILURE(DoTestExpectTimeout(p, MonoDelta::FromMilliseconds(200)));
+  NO_FATALS(DoTestExpectTimeout(p, MonoDelta::FromMilliseconds(200)));
 
   // Test a longer timeout - expect this will trigger the "two-stage timeout"
   // code path.
-  ASSERT_NO_FATAL_FAILURE(DoTestExpectTimeout(p, MonoDelta::FromMilliseconds(1500)));
+  NO_FATALS(DoTestExpectTimeout(p, MonoDelta::FromMilliseconds(1500)));
 }
 
 // Inject 500ms delay in negotiation, and send a call with a short timeout, followed by
@@ -955,7 +970,7 @@ TEST_P(TestRpc, TestCallTimeoutDoesntAffectNegotiation) {
           GenericCalculatorService::static_service_name());
 
   FLAGS_rpc_negotiation_inject_delay_ms = 500;
-  ASSERT_NO_FATAL_FAILURE(DoTestExpectTimeout(p, MonoDelta::FromMilliseconds(50)));
+  NO_FATALS(DoTestExpectTimeout(p, MonoDelta::FromMilliseconds(50)));
   ASSERT_OK(DoTestSyncCall(p, GenericCalculatorService::kAddMethodName));
 
   // Only the second call should have been received by the server, because we
@@ -1000,8 +1015,8 @@ TEST_F(TestRpc, TestNegotiationTimeout) {
           GenericCalculatorService::static_service_name());
 
   bool is_negotiation_error = false;
-  ASSERT_NO_FATAL_FAILURE(DoTestExpectTimeout(
-      p, MonoDelta::FromMilliseconds(100), &is_negotiation_error));
+  NO_FATALS(DoTestExpectTimeout(
+      p, MonoDelta::FromMilliseconds(100), false, &is_negotiation_error));
   EXPECT_TRUE(is_negotiation_error);
 
   acceptor_thread->Join();
@@ -1239,7 +1254,7 @@ TEST_P(TestRpc, TestApplicationFeatureFlag) {
 
 TEST_P(TestRpc, TestApplicationFeatureFlagUnsupportedServer) {
   auto savedFlags = kSupportedServerRpcFeatureFlags;
-  auto cleanup = MakeScopedCleanup([&] () { kSupportedServerRpcFeatureFlags = savedFlags; });
+  SCOPED_CLEANUP({ kSupportedServerRpcFeatureFlags = savedFlags; });
   kSupportedServerRpcFeatureFlags = {};
 
   // Set up server.
@@ -1290,6 +1305,7 @@ TEST_P(TestRpc, TestCancellation) {
   Proxy p(client_messenger, server_addr, server_addr.host(),
           GenericCalculatorService::static_service_name());
 
+  int timeout_ms = 10;
   for (int i = OutboundCall::READY; i <= OutboundCall::FINISHED_SUCCESS; ++i) {
     FLAGS_rpc_inject_cancellation_state = i;
     switch (i) {
@@ -1300,6 +1316,7 @@ TEST_P(TestRpc, TestCancellation) {
         ASSERT_TRUE(DoTestOutgoingSidecar(p, 0, 0).IsAborted());
         ASSERT_TRUE(DoTestOutgoingSidecar(p, 123, 456).IsAborted());
         ASSERT_TRUE(DoTestOutgoingSidecar(p, 3000 * 1024, 2000 * 1024).IsAborted());
+        DoTestExpectTimeout(p, MonoDelta::FromMilliseconds(timeout_ms), true);
         break;
       case OutboundCall::NEGOTIATION_TIMED_OUT:
       case OutboundCall::TIMED_OUT:
@@ -1327,6 +1344,8 @@ TEST_P(TestRpc, TestCancellation) {
         break;
     }
   }
+  // Sleep briefly to ensure the timeout tests have a chance for the timeouts to trigger.
+  SleepFor(MonoDelta::FromMilliseconds(timeout_ms * 2));
   client_messenger->Shutdown();
 }
 
diff --git a/be/src/kudu/rpc/rpc.cc b/be/src/kudu/rpc/rpc.cc
index 84ea892..862e804 100644
--- a/be/src/kudu/rpc/rpc.cc
+++ b/be/src/kudu/rpc/rpc.cc
@@ -17,6 +17,8 @@
 
 #include "kudu/rpc/rpc.h"
 
+#include <algorithm>
+#include <cmath>
 #include <cstdlib>
 #include <string>
 
@@ -26,9 +28,7 @@
 
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/rpc/messenger.h"
-#include "kudu/rpc/rpc_header.pb.h"
 
-using std::shared_ptr;
 using std::string;
 using strings::Substitute;
 using strings::SubstituteAndAppend;
@@ -37,42 +37,29 @@ namespace kudu {
 
 namespace rpc {
 
-bool RpcRetrier::HandleResponse(Rpc* rpc, Status* out_status) {
-  DCHECK(rpc);
-  DCHECK(out_status);
-
-  // Always retry TOO_BUSY and UNAVAILABLE errors.
-  const Status controller_status = controller_.status();
-  if (controller_status.IsRemoteError()) {
-    const ErrorStatusPB* err = controller_.error_response();
-    if (err &&
-        err->has_code() &&
-        (err->code() == ErrorStatusPB::ERROR_SERVER_TOO_BUSY ||
-         err->code() == ErrorStatusPB::ERROR_UNAVAILABLE)) {
-      // The UNAVAILABLE code is a broader counterpart of the
-      // SERVER_TOO_BUSY. In both cases it's necessary to retry a bit later.
-      DelayedRetry(rpc, controller_status);
-      return true;
-    }
-  }
-
-  *out_status = controller_status;
-  return false;
+MonoDelta ComputeExponentialBackoff(int num_attempts) {
+  return MonoDelta::FromMilliseconds(
+      (10 + rand() % 10) * static_cast<int>(
+          std::pow(2.0, std::min(8, num_attempts - 1))));
 }
 
 void RpcRetrier::DelayedRetry(Rpc* rpc, const Status& why_status) {
   if (!why_status.ok() && (last_error_.ok() || last_error_.IsTimedOut())) {
     last_error_ = why_status;
   }
-  // Add some jitter to the retry delay.
-  //
   // If the delay causes us to miss our deadline, RetryCb will fail the
   // RPC on our behalf.
-  int num_ms = ++attempt_num_ + ((rand() % 5));
-  messenger_->ScheduleOnReactor(boost::bind(&RpcRetrier::DelayedRetryCb,
-                                            this,
-                                            rpc, _1),
-                                MonoDelta::FromMilliseconds(num_ms));
+  MonoDelta backoff = ComputeBackoff(attempt_num_++);
+  messenger_->ScheduleOnReactor(
+      boost::bind(&RpcRetrier::DelayedRetryCb, this, rpc, _1), backoff);
+}
+
+MonoDelta RpcRetrier::ComputeBackoff(int num_attempts) const {
+  if (backoff_ == BackoffType::LINEAR) {
+    return MonoDelta::FromMilliseconds(num_attempts + ((rand() % 5)));
+  }
+  DCHECK(BackoffType::EXPONENTIAL == backoff_);
+  return ComputeExponentialBackoff(num_attempts);
 }
 
 void RpcRetrier::DelayedRetryCb(Rpc* rpc, const Status& status) {
diff --git a/be/src/kudu/rpc/rpc.h b/be/src/kudu/rpc/rpc.h
index bd195dc..541efa8 100644
--- a/be/src/kudu/rpc/rpc.h
+++ b/be/src/kudu/rpc/rpc.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_RPC_H
-#define KUDU_RPC_RPC_H
+#pragma once
 
 #include <memory>
 #include <string>
@@ -35,9 +34,13 @@ namespace rpc {
 class Messenger;
 class Rpc;
 
+// Exponential backoff with jitter anchored between 10ms and 20ms, and an upper
+// bound between 2.5s and 5s.
+MonoDelta ComputeExponentialBackoff(int num_attempts);
+
 // Result status of a retriable Rpc.
 //
-// TODO Consider merging this with ScanRpcStatus.
+// TODO(todd): Consider merging this with ScanRpcStatus.
 struct RetriableRpcStatus {
   enum Result {
     // There was no error, i.e. the Rpc was successful.
@@ -66,9 +69,12 @@ struct RetriableRpcStatus {
     RESOURCE_NOT_FOUND,
 
     // The authentication token supplied with the operation was found invalid
-    // by the server. Most likely, the token has expired. If so, get a new token
+    // by the server. The token has likely expired. If so, get a new token
     // using client credentials and retry the operation with it.
     INVALID_AUTHENTICATION_TOKEN,
+
+    // Similar to INVALID_AUTHENTICATION_TOKEN, but for authorization tokens.
+    INVALID_AUTHORIZATION_TOKEN,
   };
 
   Result result;
@@ -104,29 +110,36 @@ class ServerPicker : public RefCountedThreadSafe<ServerPicker<Server>> {
   virtual void MarkResourceNotFound(Server *replica) = 0;
 };
 
-// Provides utilities for retrying failed RPCs.
-//
-// All RPCs should use HandleResponse() to retry certain generic errors.
+// Backoff strategy to use when retrying RPCs.
+enum class BackoffType {
+  // Backoff a small amount of jitter before retrying, roughly linear with the
+  // number of RPC attempts.
+  LINEAR,
+
+  // Backoff by a bounded amount of time that is otherwise exponential with the
+  // number of RPC attempts.
+  EXPONENTIAL,
+};
+
+// Provides utilities for retrying failed RPCs. The default implementation adds
+// a small amount of jitter before retrying, roughly linear with the number of
+// RPC attempts.
 class RpcRetrier {
  public:
-  RpcRetrier(MonoTime deadline, std::shared_ptr<rpc::Messenger> messenger)
+  RpcRetrier(MonoTime deadline, std::shared_ptr<rpc::Messenger> messenger,
+             BackoffType backoff)
       : attempt_num_(1),
         deadline_(deadline),
-        messenger_(std::move(messenger)) {
+        messenger_(std::move(messenger)),
+        backoff_(backoff) {
     if (deadline_.Initialized()) {
       controller_.set_deadline(deadline_);
     }
     controller_.Reset();
   }
 
-  // Tries to handle a failed RPC.
-  //
-  // If it was handled (e.g. scheduled for retry in the future), returns
-  // true. In this case, callers should ensure that 'rpc' remains alive.
-  //
-  // Otherwise, returns false and writes the controller status to
-  // 'out_status'.
-  bool HandleResponse(Rpc* rpc, Status* out_status);
+  // Computes an appropriate backoff time for the given attempt.
+  MonoDelta ComputeBackoff(int num_attempts) const;
 
   // Retries an RPC at some point in the near future. If 'why_status' is not OK,
   // records it as the most recent error causing the RPC to retry. This is
@@ -166,24 +179,27 @@ class RpcRetrier {
   // Messenger to use when sending the RPC.
   std::shared_ptr<Messenger> messenger_;
 
-  // RPC controller to use when sending the RPC.
-  RpcController controller_;
-
   // In case any retries have already happened, remembers the last error.
   // Errors from the server take precedence over timeout errors.
   Status last_error_;
 
+  // RPC controller to use when sending the RPC.
+  RpcController controller_;
+
+  // The type of backoff this retrier should employ.
+  const BackoffType backoff_;
+
   DISALLOW_COPY_AND_ASSIGN(RpcRetrier);
 };
 
-// An in-flight remote procedure call to some server.
+// Encapsulates an in-flight remote procedure call to some server, employing
+// the provided backoff type to retry when necessary.
 class Rpc {
  public:
   Rpc(const MonoTime& deadline,
-      std::shared_ptr<rpc::Messenger> messenger)
-      : retrier_(deadline, std::move(messenger)) {
-  }
-
+      std::shared_ptr<rpc::Messenger> messenger,
+      BackoffType backoff)
+      : retrier_(deadline, std::move(messenger), backoff) {}
   virtual ~Rpc() {}
 
   // Asynchronously sends the RPC to the remote end.
@@ -194,19 +210,20 @@ class Rpc {
   // Returns a string representation of the RPC.
   virtual std::string ToString() const = 0;
 
-  // Returns the number of times this RPC has been sent. Will always be at
+  // Returns the number of times this RPC has been sent. Should always be at
   // least one.
   int num_attempts() const { return retrier().attempt_num(); }
 
  protected:
+  // Used to retry some failed RPCs.
   const RpcRetrier& retrier() const { return retrier_; }
   RpcRetrier* mutable_retrier() { return &retrier_; }
 
  private:
   friend class RpcRetrier;
 
-  // Callback for SendRpc(). If 'status' is not OK, something failed
-  // before the RPC was sent.
+  // Callback for SendRpc(). If 'status' is not OK, something failed before the
+  // RPC was sent.
   virtual void SendRpcCb(const Status& status) = 0;
 
   // Used to retry some failed RPCs.
@@ -218,4 +235,3 @@ class Rpc {
 } // namespace rpc
 } // namespace kudu
 
-#endif // KUDU_RPC_RPC_H
diff --git a/be/src/kudu/rpc/rpc_context.cc b/be/src/kudu/rpc/rpc_context.cc
index 97da445..a65c137 100644
--- a/be/src/kudu/rpc/rpc_context.cc
+++ b/be/src/kudu/rpc/rpc_context.cc
@@ -48,12 +48,10 @@ namespace rpc {
 
 RpcContext::RpcContext(InboundCall *call,
                        const google::protobuf::Message *request_pb,
-                       google::protobuf::Message *response_pb,
-                       scoped_refptr<ResultTracker> result_tracker)
+                       google::protobuf::Message *response_pb)
   : call_(CHECK_NOTNULL(call)),
     request_pb_(request_pb),
-    response_pb_(response_pb),
-    result_tracker_(std::move(result_tracker)) {
+    response_pb_(response_pb) {
   VLOG(4) << call_->remote_method().service_name() << ": Received RPC request for "
           << call_->ToString() << ":" << std::endl << SecureDebugString(*request_pb_);
   TRACE_EVENT_ASYNC_BEGIN2("rpc_call", "RPC", this,
@@ -64,6 +62,11 @@ RpcContext::RpcContext(InboundCall *call,
 RpcContext::~RpcContext() {
 }
 
+void RpcContext::SetResultTracker(scoped_refptr<ResultTracker> result_tracker) {
+  DCHECK(!result_tracker_);
+  result_tracker_ = std::move(result_tracker);
+}
+
 void RpcContext::RespondSuccess() {
   if (AreResultsTracked()) {
     result_tracker_->RecordCompletionAndRespond(call_->header().request_id(),
@@ -148,7 +151,7 @@ Status RpcContext::AddOutboundSidecar(unique_ptr<RpcSidecar> car, int* idx) {
   return call_->AddOutboundSidecar(std::move(car), idx);
 }
 
-Status RpcContext::GetInboundSidecar(int idx, Slice* slice) {
+Status RpcContext::GetInboundSidecar(int idx, Slice* slice) const {
   return call_->GetInboundSidecar(idx, slice);
 }
 
@@ -189,7 +192,11 @@ MonoTime RpcContext::GetTimeReceived() const {
   return call_->GetTimeReceived();
 }
 
-Trace* RpcContext::trace() {
+MonoTime RpcContext::GetTimeHandled() const {
+  return call_->GetTimeHandled();
+}
+
+Trace* RpcContext::trace() const {
   return call_->trace();
 }
 
diff --git a/be/src/kudu/rpc/rpc_context.h b/be/src/kudu/rpc/rpc_context.h
index c729d5e..b1466ce 100644
--- a/be/src/kudu/rpc/rpc_context.h
+++ b/be/src/kudu/rpc/rpc_context.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_RPC_CONTEXT_H
-#define KUDU_RPC_RPC_CONTEXT_H
+#pragma once
 
 #include <memory>
 #include <stddef.h>
@@ -23,7 +22,6 @@
 
 #include <glog/logging.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/rpc/rpc_header.pb.h"
 #include "kudu/util/monotime.h"
@@ -69,13 +67,19 @@ class RpcContext {
   // and is not a public API.
   RpcContext(InboundCall *call,
              const google::protobuf::Message *request_pb,
-             google::protobuf::Message *response_pb,
-             scoped_refptr<ResultTracker> result_tracker);
+             google::protobuf::Message *response_pb);
 
   ~RpcContext();
 
+  // Initialize a result tracker for the RPC.
+  //
+  // This is delayed until after the constructor in order to allow for RPCs to
+  // be validated and used prior to initializing the tracking (primarily for
+  // authorization).
+  void SetResultTracker(scoped_refptr<ResultTracker> result_tracker);
+
   // Return the trace buffer for this call.
-  Trace* trace();
+  Trace* trace() const;
 
   // Send a response to the call. The service may call this method
   // before or after returning from the original handler method,
@@ -163,7 +167,7 @@ class RpcContext {
 
   // Fills 'sidecar' with a sidecar sent by the client. Returns an error if 'idx' is out
   // of bounds.
-  Status GetInboundSidecar(int idx, Slice* slice);
+  Status GetInboundSidecar(int idx, Slice* slice) const;
 
   // Return the identity of remote user who made this call.
   const RemoteUser& remote_user() const;
@@ -207,6 +211,9 @@ class RpcContext {
   // Return the time when the inbound call was received.
   MonoTime GetTimeReceived() const;
 
+  // Return the time when the inbound call was handled.
+  MonoTime GetTimeHandled() const;
+
   // Whether the results of this RPC are tracked with a ResultTracker.
   // If this returns true, both result_tracker() and request_id() should return non-null results.
   bool AreResultsTracked() const { return result_tracker_.get() != nullptr; }
@@ -235,11 +242,10 @@ class RpcContext {
  private:
   friend class ResultTracker;
   InboundCall* const call_;
-  const gscoped_ptr<const google::protobuf::Message> request_pb_;
-  const gscoped_ptr<google::protobuf::Message> response_pb_;
+  const std::unique_ptr<const google::protobuf::Message> request_pb_;
+  const std::unique_ptr<google::protobuf::Message> response_pb_;
   scoped_refptr<ResultTracker> result_tracker_;
 };
 
 } // namespace rpc
 } // namespace kudu
-#endif
diff --git a/be/src/kudu/rpc/rpc_controller.cc b/be/src/kudu/rpc/rpc_controller.cc
index 77c7ca4..821b881 100644
--- a/be/src/kudu/rpc/rpc_controller.cc
+++ b/be/src/kudu/rpc/rpc_controller.cc
@@ -24,7 +24,6 @@
 
 #include <glog/logging.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/rpc/messenger.h"
 #include "kudu/rpc/outbound_call.h"
diff --git a/be/src/kudu/rpc/rpc_controller.h b/be/src/kudu/rpc/rpc_controller.h
index aa61d83..bcdabf5 100644
--- a/be/src/kudu/rpc/rpc_controller.h
+++ b/be/src/kudu/rpc/rpc_controller.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_RPC_CONTROLLER_H
-#define KUDU_RPC_RPC_CONTROLLER_H
+#pragma once
 
 #include <cstdint>
 #include <memory>
@@ -279,4 +278,3 @@ class RpcController {
 
 } // namespace rpc
 } // namespace kudu
-#endif
diff --git a/be/src/kudu/rpc/rpc_header.proto b/be/src/kudu/rpc/rpc_header.proto
index 1d55b6a..03bce1e 100644
--- a/be/src/kudu/rpc/rpc_header.proto
+++ b/be/src/kudu/rpc/rpc_header.proto
@@ -313,6 +313,12 @@ message ErrorStatusPB {
     // time. The client may try again later.
     ERROR_UNAVAILABLE = 7;
 
+    // The authorization token is invalid or expired. Unlike
+    // FATAL_INVALID_AUTHENTICATION_TOKEN, receipt of this code does not mean
+    // that a reconnection attempt should be made; just that the client should
+    // obtain a new authz token.
+    ERROR_INVALID_AUTHORIZATION_TOKEN = 17;
+
     // FATAL_* errors indicate that the client should shut down the connection.
     //------------------------------------------------------------
     // The RPC server is already shutting down.
@@ -326,8 +332,9 @@ message ErrorStatusPB {
     // Auth failed.
     FATAL_UNAUTHORIZED = 15;
 
-    // The authentication token is invalid or expired;
-    // the client should obtain a new one.
+    // The authentication token is invalid or expired. The connection
+    // negotiation failed, and the client should obtain a new authn token and
+    // try to reconnect.
     FATAL_INVALID_AUTHENTICATION_TOKEN = 16;
   }
 
diff --git a/be/src/kudu/rpc/rpc_introspection.proto b/be/src/kudu/rpc/rpc_introspection.proto
index 05be722..3e4facd 100644
--- a/be/src/kudu/rpc/rpc_introspection.proto
+++ b/be/src/kudu/rpc/rpc_introspection.proto
@@ -61,8 +61,8 @@ message SocketStatsPB {
   optional uint32 snd_cwnd = 3;
   optional uint32 total_retrans = 4;
 
-  optional uint32 pacing_rate = 5;
-  optional uint32 max_pacing_rate = 6;
+  optional uint64 pacing_rate = 5;
+  optional uint64 max_pacing_rate = 6;
 
   optional uint64 bytes_acked = 7;
   optional uint64 bytes_received = 8;
@@ -73,7 +73,7 @@ message SocketStatsPB {
   optional uint64 receive_queue_bytes = 12;
 
   // Calculated sender throughput.
-  optional int32 send_bytes_per_sec = 13;
+  optional int64 send_bytes_per_sec = 13;
 };
 
 // Debugging information about a currently-open RPC connection.
diff --git a/be/src/kudu/rpc/rpc_service.h b/be/src/kudu/rpc/rpc_service.h
index dcaa9c1..42decc7 100644
--- a/be/src/kudu/rpc/rpc_service.h
+++ b/be/src/kudu/rpc/rpc_service.h
@@ -14,8 +14,9 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_SERVICE_H_
-#define KUDU_RPC_SERVICE_H_
+#pragma once
+
+#include <memory>
 
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/util/status.h"
@@ -34,14 +35,12 @@ class RpcService : public RefCountedThreadSafe<RpcService> {
   // Enqueue a call for processing.
   // On failure, the RpcService::QueueInboundCall() implementation is
   // responsible for responding to the client with a failure message.
-  virtual Status QueueInboundCall(gscoped_ptr<InboundCall> call) = 0;
+  virtual Status QueueInboundCall(std::unique_ptr<InboundCall> call) = 0;
 
-  virtual RpcMethodInfo* LookupMethod(const RemoteMethod& method) {
+  virtual RpcMethodInfo* LookupMethod(const RemoteMethod& /*method*/) {
     return nullptr;
   }
 };
 
 } // namespace rpc
 } // namespace kudu
-
-#endif // KUDU_RPC_SERVICE_H_
diff --git a/be/src/kudu/rpc/rpc_sidecar.h b/be/src/kudu/rpc/rpc_sidecar.h
index bfbfcea..cf555cb 100644
--- a/be/src/kudu/rpc/rpc_sidecar.h
+++ b/be/src/kudu/rpc/rpc_sidecar.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_RPC_SIDECAR_H
-#define KUDU_RPC_RPC_SIDECAR_H
+#pragma once
 
 #include <memory>
 
@@ -68,6 +67,3 @@ class RpcSidecar {
 
 } // namespace rpc
 } // namespace kudu
-
-
-#endif /* KUDU_RPC_RPC_SIDECAR_H */
diff --git a/be/src/kudu/rpc/rpc_stub-test.cc b/be/src/kudu/rpc/rpc_stub-test.cc
index ce26241..8c5a8a1 100644
--- a/be/src/kudu/rpc/rpc_stub-test.cc
+++ b/be/src/kudu/rpc/rpc_stub-test.cc
@@ -32,15 +32,12 @@
 #include <boost/core/ref.hpp>
 #include <boost/function.hpp>
 #include <gflags/gflags.h>
-#include <gflags/gflags_declare.h>
 #include <glog/logging.h>
 #include <glog/stl_logging.h>
 #include <gtest/gtest.h>
 
 #include "kudu/gutil/atomicops.h"
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/ref_counted.h"
-#include "kudu/gutil/stl_util.h"
 #include "kudu/rpc/messenger.h"
 #include "kudu/rpc/proxy.h"
 #include "kudu/rpc/rpc-test-base.h"
@@ -209,13 +206,35 @@ TEST_F(RpcStubTest, TestAuthorization) {
     p.set_user_credentials(creds);
 
     // Alice is disallowed by all RPCs.
-    RpcController controller;
-    WhoAmIRequestPB req;
-    WhoAmIResponsePB resp;
-    Status s = p.WhoAmI(req, &resp, &controller);
-    ASSERT_FALSE(s.ok());
-    ASSERT_EQ(s.ToString(),
-              "Remote error: Not authorized: alice is not allowed to call this method");
+    {
+      RpcController controller;
+      WhoAmIRequestPB req;
+      WhoAmIResponsePB resp;
+      Status s = p.WhoAmI(req, &resp, &controller);
+      ASSERT_FALSE(s.ok());
+      ASSERT_EQ(s.ToString(),
+                "Remote error: Not authorized: alice is not allowed to call this method");
+    }
+
+    // KUDU-2540: Authorization failures on exactly-once RPCs cause FATAL
+    {
+      RpcController controller;
+
+      unique_ptr<RequestIdPB> request_id(new RequestIdPB);
+      request_id->set_client_id("client-id");
+      request_id->set_attempt_no(0);
+      request_id->set_seq_no(0);
+      request_id->set_first_incomplete_seq_no(-1);
+      controller.SetRequestIdPB(std::move(request_id));
+
+      ExactlyOnceRequestPB req;
+      req.set_value_to_add(1);
+      ExactlyOnceResponsePB resp;
+      Status s = p.AddExactlyOnce(req, &resp, &controller);
+      ASSERT_FALSE(s.ok());
+      ASSERT_EQ(s.ToString(),
+                "Remote error: Not authorized: alice is not allowed to call this method");
+    }
   }
 
   // Try some calls as "bob".
@@ -429,17 +448,16 @@ struct AsyncSleep {
 
 TEST_F(RpcStubTest, TestDontHandleTimedOutCalls) {
   CalculatorServiceProxy p(client_messenger_, server_addr_, server_addr_.host());
-  vector<AsyncSleep*> sleeps;
-  ElementDeleter d(&sleeps);
+  vector<unique_ptr<AsyncSleep>> sleeps;
 
   // Send enough sleep calls to occupy the worker threads.
   for (int i = 0; i < n_worker_threads_; i++) {
-    gscoped_ptr<AsyncSleep> sleep(new AsyncSleep);
+    unique_ptr<AsyncSleep> sleep(new AsyncSleep);
     sleep->rpc.set_timeout(MonoDelta::FromSeconds(1));
     sleep->req.set_sleep_micros(1000*1000); // 1sec
     p.SleepAsync(sleep->req, &sleep->resp, &sleep->rpc,
                  boost::bind(&CountDownLatch::CountDown, &sleep->latch));
-    sleeps.push_back(sleep.release());
+    sleeps.push_back(std::move(sleep));
   }
 
   // We asynchronously sent the RPCs above, but the RPCs might still
@@ -471,7 +489,7 @@ TEST_F(RpcStubTest, TestDontHandleTimedOutCalls) {
     ASSERT_STR_CONTAINS(s.ToString(), "SENT");
   });
 
-  for (AsyncSleep* s : sleeps) {
+  for (const auto& s : sleeps) {
     s->latch.Wait();
   }
 
diff --git a/be/src/kudu/rpc/rpc_verification_util.cc b/be/src/kudu/rpc/rpc_verification_util.cc
new file mode 100644
index 0000000..c0bf8b1
--- /dev/null
+++ b/be/src/kudu/rpc/rpc_verification_util.cc
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "kudu/rpc/rpc_verification_util.h"
+
+#include <ostream>
+
+#include <glog/logging.h>
+
+#include "kudu/rpc/rpc_header.pb.h"
+#include "kudu/security/token_verifier.h"
+
+namespace kudu {
+
+using security::VerificationResult;
+
+namespace rpc {
+
+Status ParseVerificationResult(const VerificationResult& result,
+                               ErrorStatusPB::RpcErrorCodePB retry_error,
+                               ErrorStatusPB::RpcErrorCodePB* error) {
+  DCHECK(error);
+  switch (result) {
+    case VerificationResult::VALID: return Status::OK();
+
+    case VerificationResult::INVALID_TOKEN:
+    case VerificationResult::INVALID_SIGNATURE:
+    case VerificationResult::EXPIRED_TOKEN:
+    case VerificationResult::EXPIRED_SIGNING_KEY: {
+      // These errors indicate the client should get a new token and try again.
+      *error = retry_error;
+      break;
+    }
+    case VerificationResult::UNKNOWN_SIGNING_KEY: {
+      // The server doesn't recognize the signing key. This indicates that the
+      // server has not been updated with the most recent TSKs, so tell the
+      // client to try again later.
+      *error = ErrorStatusPB::ERROR_UNAVAILABLE;
+      break;
+    }
+    case VerificationResult::INCOMPATIBLE_FEATURE: {
+      // These error types aren't recoverable by having the client get a new token.
+      *error = ErrorStatusPB::FATAL_UNAUTHORIZED;
+      break;
+    }
+    default:
+      LOG(FATAL) << "Unknown verification result: " << static_cast<int>(result);
+  }
+  return Status::NotAuthorized(VerificationResultToString(result));
+}
+
+} // namespace rpc
+} // namespace kudu
diff --git a/be/src/kudu/rpc/rpc_service.h b/be/src/kudu/rpc/rpc_verification_util.h
similarity index 57%
copy from be/src/kudu/rpc/rpc_service.h
copy to be/src/kudu/rpc/rpc_verification_util.h
index dcaa9c1..0c15b9c 100644
--- a/be/src/kudu/rpc/rpc_service.h
+++ b/be/src/kudu/rpc/rpc_verification_util.h
@@ -14,34 +14,28 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_SERVICE_H_
-#define KUDU_RPC_SERVICE_H_
 
-#include "kudu/gutil/ref_counted.h"
+#pragma once
+
+#include "kudu/rpc/rpc_header.pb.h"
 #include "kudu/util/status.h"
 
 namespace kudu {
-namespace rpc {
-
-class RemoteMethod;
-struct RpcMethodInfo;
-class InboundCall;
 
-class RpcService : public RefCountedThreadSafe<RpcService> {
- public:
-  virtual ~RpcService() {}
+namespace security {
+enum class VerificationResult;
+} // namespace security
 
-  // Enqueue a call for processing.
-  // On failure, the RpcService::QueueInboundCall() implementation is
-  // responsible for responding to the client with a failure message.
-  virtual Status QueueInboundCall(gscoped_ptr<InboundCall> call) = 0;
+namespace rpc {
 
-  virtual RpcMethodInfo* LookupMethod(const RemoteMethod& method) {
-    return nullptr;
-  }
-};
+// Utility function to convert the result of a security token verification to
+// an appropriate RPC error code. Returns OK if 'result' is VALID, and
+// otherwise returns non-OK and sets 'error' appropriately.
+// 'retry_error' is the error code to be returned to denote that verification
+// should be retried after retrieving a new token.
+Status ParseVerificationResult(const security::VerificationResult& result,
+                               ErrorStatusPB::RpcErrorCodePB retry_error,
+                               ErrorStatusPB::RpcErrorCodePB* error);
 
 } // namespace rpc
 } // namespace kudu
-
-#endif // KUDU_RPC_SERVICE_H_
diff --git a/be/src/kudu/rpc/rpcz_store.cc b/be/src/kudu/rpc/rpcz_store.cc
index fd4c644..f7c7b3b 100644
--- a/be/src/kudu/rpc/rpcz_store.cc
+++ b/be/src/kudu/rpc/rpcz_store.cc
@@ -32,6 +32,7 @@
 
 #include "kudu/gutil/port.h"
 #include "kudu/gutil/ref_counted.h"
+#include "kudu/gutil/strings/human_readable.h"
 #include "kudu/gutil/strings/stringpiece.h"
 #include "kudu/gutil/walltime.h"
 #include "kudu/rpc/inbound_call.h"
@@ -248,8 +249,11 @@ void RpczStore::LogTrace(InboundCall* call) {
     if (duration_ms > log_threshold) {
       // TODO: consider pushing this onto another thread since it may be slow.
       // The traces may also be too large to fit in a log message.
-      LOG(WARNING) << call->ToString() << " took " << duration_ms << "ms (client timeout "
-                   << call->header_.timeout_millis() << ").";
+      int64_t timeout_ms = call->header_.timeout_millis();
+      LOG(WARNING) << call->ToString() << " took " << duration_ms << " ms "
+                   << "(" << HumanReadableElapsedTime::ToShortString(duration_ms * .001) << "). "
+                   << "Client timeout " << timeout_ms << " ms "
+                   << "(" << HumanReadableElapsedTime::ToShortString(timeout_ms * .001) << ")";
       string s = call->trace()->DumpToString();
       if (!s.empty()) {
         LOG(WARNING) << "Trace:\n" << s;
@@ -263,11 +267,7 @@ void RpczStore::LogTrace(InboundCall* call) {
     call->trace()->Dump(&LOG(INFO), true);
   } else if (duration_ms > FLAGS_rpc_duration_too_long_ms) {
     LOG(INFO) << call->ToString() << " took " << duration_ms << "ms. "
-              << "Request Metrics: " << call->trace()->MetricsAsJSON() << "\n";
-    string s = call->trace()->DumpToString();
-    if (!s.empty()) {
-      LOG(INFO) << "Trace:\n" << s;
-    }
+              << "Request Metrics: " << call->trace()->MetricsAsJSON();
   }
 }
 
diff --git a/be/src/kudu/rpc/sasl_common.cc b/be/src/kudu/rpc/sasl_common.cc
index 645e854..0107fba 100644
--- a/be/src/kudu/rpc/sasl_common.cc
+++ b/be/src/kudu/rpc/sasl_common.cc
@@ -37,6 +37,13 @@
 #include "kudu/util/net/sockaddr.h"
 #include "kudu/util/rw_mutex.h"
 
+#if defined(__APPLE__)
+// Almost all functions in the SASL API are marked as deprecated
+// since macOS 10.11.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif // #if defined(__APPLE__)
+
 using std::set;
 using std::string;
 
@@ -468,3 +475,7 @@ const char* SaslProtection::name_of(SaslProtection::Type val) {
 
 } // namespace rpc
 } // namespace kudu
+
+#if defined(__APPLE__)
+#pragma GCC diagnostic pop
+#endif // #if defined(__APPLE__)
diff --git a/be/src/kudu/rpc/sasl_common.h b/be/src/kudu/rpc/sasl_common.h
index 2454cfd..dcda131 100644
--- a/be/src/kudu/rpc/sasl_common.h
+++ b/be/src/kudu/rpc/sasl_common.h
@@ -14,9 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_RPC_SASL_COMMON_H
-#define KUDU_RPC_SASL_COMMON_H
+#pragma once
 
 #include <cstddef>
 #include <cstdint>
@@ -30,6 +28,13 @@
 #include "kudu/util/slice.h"
 #include "kudu/util/status.h"
 
+#if defined(__APPLE__)
+// Almost all functions in the SASL API are marked as deprecated
+// since macOS 10.11.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif // #if defined(__APPLE__)
+
 namespace kudu {
 
 class Sockaddr;
@@ -140,7 +145,7 @@ Status SaslDecode(sasl_conn_t* conn,
                   Slice ciphertext,
                   Slice* plaintext) WARN_UNUSED_RESULT;
 
-// Deleter for sasl_conn_t instances, for use with gscoped_ptr after calling sasl_*_new()
+// Deleter for sasl_conn_t instances, for use with unique_ptr after calling sasl_*_new().
 struct SaslDeleter {
   inline void operator()(sasl_conn_t* conn) {
     sasl_dispose(&conn);
@@ -155,4 +160,6 @@ void SaslSetMutex();
 } // namespace rpc
 } // namespace kudu
 
-#endif
+#if defined(__APPLE__)
+#pragma GCC diagnostic pop
+#endif // #if defined(__APPLE__)
diff --git a/be/src/kudu/rpc/sasl_helper.h b/be/src/kudu/rpc/sasl_helper.h
index aa0c8bf..998e7a1 100644
--- a/be/src/kudu/rpc/sasl_helper.h
+++ b/be/src/kudu/rpc/sasl_helper.h
@@ -14,9 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_RPC_SASL_HELPER_H
-#define KUDU_RPC_SASL_HELPER_H
+#pragma once
 
 #include <cstdint>
 #include <set>
@@ -105,5 +103,3 @@ class SaslHelper {
 
 } // namespace rpc
 } // namespace kudu
-
-#endif  // KUDU_RPC_SASL_HELPER_H
diff --git a/be/src/kudu/rpc/serialization.cc b/be/src/kudu/rpc/serialization.cc
index 473a817..78ea295 100644
--- a/be/src/kudu/rpc/serialization.cc
+++ b/be/src/kudu/rpc/serialization.cc
@@ -69,7 +69,7 @@ void SerializeMessage(const MessageLite& message, faststring* param_buf,
   // this is a safe limitation.
   CHECK_LE(total_size, std::numeric_limits<uint32_t>::max());
 
-  if (total_size > FLAGS_rpc_max_message_size) {
+  if (PREDICT_FALSE(total_size > FLAGS_rpc_max_message_size)) {
     LOG(WARNING) << Substitute("Serialized $0 ($1 bytes) is larger than the maximum configured "
                                "RPC message size ($2 bytes). "
                                "Sending anyway, but peer may reject the data.",
diff --git a/be/src/kudu/rpc/serialization.h b/be/src/kudu/rpc/serialization.h
index 8406a1f..9fa3858 100644
--- a/be/src/kudu/rpc/serialization.h
+++ b/be/src/kudu/rpc/serialization.h
@@ -14,9 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_RPC_SERIALIZATION_H
-#define KUDU_RPC_SERIALIZATION_H
+#pragma once
 
 #include <cstdint>
 #include <cstring>
@@ -85,4 +83,3 @@ Status ValidateConnHeader(const Slice& slice);
 } // namespace serialization
 } // namespace rpc
 } // namespace kudu
-#endif // KUDU_RPC_SERIALIZATION_H
diff --git a/be/src/kudu/rpc/server_negotiation.cc b/be/src/kudu/rpc/server_negotiation.cc
index a63d856..79d9925 100644
--- a/be/src/kudu/rpc/server_negotiation.cc
+++ b/be/src/kudu/rpc/server_negotiation.cc
@@ -40,6 +40,7 @@
 #include "kudu/rpc/blocking_ops.h"
 #include "kudu/rpc/constants.h"
 #include "kudu/rpc/messenger.h"
+#include "kudu/rpc/rpc_verification_util.h"
 #include "kudu/rpc/serialization.h"
 #include "kudu/security/cert.h"
 #include "kudu/security/crypto.h"
@@ -58,6 +59,13 @@
 #include "kudu/util/slice.h"
 #include "kudu/util/trace.h"
 
+#if defined(__APPLE__)
+// Almost all functions in the SASL API are marked as deprecated
+// since macOS 10.11.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif // #if defined(__APPLE__)
+
 using std::set;
 using std::string;
 using std::unique_ptr;
@@ -72,6 +80,18 @@ DEFINE_double(rpc_inject_invalid_authn_token_ratio, 0,
 TAG_FLAG(rpc_inject_invalid_authn_token_ratio, runtime);
 TAG_FLAG(rpc_inject_invalid_authn_token_ratio, unsafe);
 
+DEFINE_double(rpc_inject_invalid_channel_bindings_ratio, 0,
+            "The ratio of injection of invalid channel bindings during "
+            "connection negotiation. This is a test-only flag.");
+TAG_FLAG(rpc_inject_invalid_channel_bindings_ratio, runtime);
+TAG_FLAG(rpc_inject_invalid_channel_bindings_ratio, unsafe);
+
+DEFINE_bool(rpc_send_channel_bindings, true,
+            "Whether to send channel bindings in NegotiatePB response as "
+            "prescribed by RFC 5929. This is a test-only flag.");
+TAG_FLAG(rpc_send_channel_bindings, runtime);
+TAG_FLAG(rpc_send_channel_bindings, unsafe);
+
 DECLARE_bool(rpc_encrypt_loopback_connections);
 
 DEFINE_string(trusted_subnets,
@@ -388,7 +408,7 @@ Status ServerNegotiation::InitSaslServer() {
   //
   // [1] https://github.com/cyrusimap/cyrus-sasl/issues/583
   string default_server_fqdn;
-  if (server_fqdn == nullptr) {
+  if (!server_fqdn) {
     RETURN_NOT_OK_PREPEND(GetFQDN(&default_server_fqdn), "could not determine own FQDN");
     server_fqdn = default_server_fqdn.c_str();
   }
@@ -663,33 +683,12 @@ Status ServerNegotiation::AuthenticateByToken(faststring* recv_buf) {
   // so it knows how to intelligently retry.
   security::TokenPB token;
   auto verification_result = token_verifier_->VerifyTokenSignature(pb.authn_token(), &token);
-  switch (verification_result) {
-    case security::VerificationResult::VALID: break;
-
-    case security::VerificationResult::INVALID_TOKEN:
-    case security::VerificationResult::INVALID_SIGNATURE:
-    case security::VerificationResult::EXPIRED_TOKEN:
-    case security::VerificationResult::EXPIRED_SIGNING_KEY: {
-      // These errors indicate the client should get a new token and try again.
-      Status s = Status::NotAuthorized(VerificationResultToString(verification_result));
-      RETURN_NOT_OK(SendError(ErrorStatusPB::FATAL_INVALID_AUTHENTICATION_TOKEN, s));
-      return s;
-    }
-
-    case security::VerificationResult::UNKNOWN_SIGNING_KEY: {
-      // The server doesn't recognize the signing key. This indicates that the
-      // server has not been updated with the most recent TSKs, so tell the
-      // client to try again later.
-      Status s = Status::NotAuthorized(VerificationResultToString(verification_result));
-      RETURN_NOT_OK(SendError(ErrorStatusPB::ERROR_UNAVAILABLE, s));
-      return s;
-    }
-    case security::VerificationResult::INCOMPATIBLE_FEATURE: {
-      Status s = Status::NotAuthorized(VerificationResultToString(verification_result));
-      // These error types aren't recoverable by having the client get a new token.
-      RETURN_NOT_OK(SendError(ErrorStatusPB::FATAL_UNAUTHORIZED, s));
-      return s;
-    }
+  ErrorStatusPB::RpcErrorCodePB error;
+  Status s = ParseVerificationResult(verification_result,
+      ErrorStatusPB::FATAL_INVALID_AUTHENTICATION_TOKEN, &error);
+  if (!s.ok()) {
+    RETURN_NOT_OK(SendError(error, s));
+    return s;
   }
 
   if (!token.has_authn()) {
@@ -721,6 +720,8 @@ Status ServerNegotiation::AuthenticateByToken(faststring* recv_buf) {
       case 3:
         res = security::VerificationResult::EXPIRED_SIGNING_KEY;
         break;
+      default:
+        LOG(FATAL) << "unreachable";
     }
     if (kudu::fault_injection::MaybeTrue(FLAGS_rpc_inject_invalid_authn_token_ratio)) {
       Status s = Status::NotAuthorized(VerificationResultToString(res));
@@ -890,7 +891,7 @@ Status ServerNegotiation::SendSaslSuccess() {
     RETURN_NOT_OK(security::GenerateNonce(nonce_.get_ptr()));
     response.set_nonce(*nonce_);
 
-    if (tls_negotiated_) {
+    if (tls_negotiated_ && PREDICT_TRUE(FLAGS_rpc_send_channel_bindings)) {
       // Send the channel bindings to the client.
       security::Cert cert;
       RETURN_NOT_OK(tls_handshake_.GetLocalCert(&cert));
@@ -898,6 +899,12 @@ Status ServerNegotiation::SendSaslSuccess() {
       string plaintext_channel_bindings;
       RETURN_NOT_OK(cert.GetServerEndPointChannelBindings(&plaintext_channel_bindings));
 
+      if (kudu::fault_injection::MaybeTrue(
+          FLAGS_rpc_inject_invalid_channel_bindings_ratio)) {
+        DCHECK_GT(plaintext_channel_bindings.size(), 0);
+        plaintext_channel_bindings[0] += 1;
+      }
+
       Slice ciphertext;
       RETURN_NOT_OK(SaslEncode(sasl_conn_.get(),
                                plaintext_channel_bindings,
@@ -1000,3 +1007,7 @@ bool ServerNegotiation::IsTrustedConnection(const Sockaddr& addr) {
 
 } // namespace rpc
 } // namespace kudu
+
+#if defined(__APPLE__)
+#pragma GCC diagnostic pop
+#endif // #if defined(__APPLE__)
diff --git a/be/src/kudu/rpc/service_if.cc b/be/src/kudu/rpc/service_if.cc
index 008c478..af53452 100644
--- a/be/src/kudu/rpc/service_if.cc
+++ b/be/src/kudu/rpc/service_if.cc
@@ -110,20 +110,14 @@ void GeneratedServiceIf::Handle(InboundCall *call) {
   }
   Message* resp = method_info->resp_prototype->New();
 
-  bool track_result = call->header().has_request_id()
-                      && method_info->track_result
-                      && FLAGS_enable_exactly_once;
-  RpcContext* ctx = new RpcContext(call,
-                                   req.release(),
-                                   resp,
-                                   track_result ? result_tracker_ : nullptr);
+  RpcContext* ctx = new RpcContext(call, req.release(), resp);
   if (!method_info->authz_method(ctx->request_pb(), resp, ctx)) {
     // The authz_method itself should have responded to the RPC.
     return;
   }
 
-  if (track_result) {
-    RequestIdPB request_id(call->header().request_id());
+  if (call->header().has_request_id() && method_info->track_result && FLAGS_enable_exactly_once) {
+    ctx->SetResultTracker(result_tracker_);
     ResultTracker::RpcState state = ctx->result_tracker()->TrackRpc(
         call->header().request_id(),
         resp,
diff --git a/be/src/kudu/rpc/service_if.h b/be/src/kudu/rpc/service_if.h
index 9156b4a..686580e 100644
--- a/be/src/kudu/rpc/service_if.h
+++ b/be/src/kudu/rpc/service_if.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_RPC_SERVICE_IF_H
-#define KUDU_RPC_SERVICE_IF_H
+#pragma once
 
 #include <cstdint>
 #include <functional>
@@ -131,4 +130,3 @@ class GeneratedServiceIf : public ServiceIf {
 
 } // namespace rpc
 } // namespace kudu
-#endif
diff --git a/be/src/kudu/rpc/service_pool.cc b/be/src/kudu/rpc/service_pool.cc
index 62d46d6..a12a34d 100644
--- a/be/src/kudu/rpc/service_pool.cc
+++ b/be/src/kudu/rpc/service_pool.cc
@@ -21,7 +21,6 @@
 #include <memory>
 #include <ostream>
 #include <string>
-#include <type_traits>
 #include <utility>
 #include <vector>
 
@@ -29,7 +28,6 @@
 #include <glog/logging.h>
 
 #include "kudu/gutil/basictypes.h"
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/gutil/strings/join.h"
 #include "kudu/gutil/strings/substitute.h"
@@ -45,8 +43,8 @@
 #include "kudu/util/thread.h"
 #include "kudu/util/trace.h"
 
-using std::shared_ptr;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 using strings::Substitute;
 
@@ -54,24 +52,26 @@ METRIC_DEFINE_histogram(server, rpc_incoming_queue_time,
                         "RPC Queue Time",
                         kudu::MetricUnit::kMicroseconds,
                         "Number of microseconds incoming RPC requests spend in the worker queue",
+                        kudu::MetricLevel::kInfo,
                         60000000LU, 3);
 
 METRIC_DEFINE_counter(server, rpcs_timed_out_in_queue,
                       "RPC Queue Timeouts",
                       kudu::MetricUnit::kRequests,
                       "Number of RPCs whose timeout elapsed while waiting "
-                      "in the service queue, and thus were not processed.");
+                      "in the service queue, and thus were not processed.",
+                      kudu::MetricLevel::kWarn);
 
 METRIC_DEFINE_counter(server, rpcs_queue_overflow,
                       "RPC Queue Overflows",
                       kudu::MetricUnit::kRequests,
-                      "Number of RPCs dropped because the service queue "
-                      "was full.");
+                      "Number of RPCs dropped because the service queue was full.",
+                      kudu::MetricLevel::kWarn);
 
 namespace kudu {
 namespace rpc {
 
-ServicePool::ServicePool(gscoped_ptr<ServiceIf> service,
+ServicePool::ServicePool(unique_ptr<ServiceIf> service,
                          const scoped_refptr<MetricEntity>& entity,
                          size_t service_queue_length)
   : service_(std::move(service)),
@@ -126,7 +126,7 @@ void ServicePool::RejectTooBusy(InboundCall* c) {
                  c->remote_address().ToString(),
                  service_queue_.max_size());
   rpcs_queue_overflow_->Increment();
-  KLOG_EVERY_N_SECS(WARNING, 1) << err_msg;
+  KLOG_EVERY_N_SECS(WARNING, 1) << err_msg << THROTTLE_MSG;
   c->RespondFailure(ErrorStatusPB::ERROR_SERVER_TOO_BUSY,
                     Status::ServiceUnavailable(err_msg));
   DLOG(INFO) << err_msg << " Contents of service queue:\n"
@@ -141,7 +141,7 @@ RpcMethodInfo* ServicePool::LookupMethod(const RemoteMethod& method) {
   return service_->LookupMethod(method);
 }
 
-Status ServicePool::QueueInboundCall(gscoped_ptr<InboundCall> call) {
+Status ServicePool::QueueInboundCall(unique_ptr<InboundCall> call) {
   InboundCall* c = call.release();
 
   vector<uint32_t> unsupported_features;
diff --git a/be/src/kudu/rpc/service_pool.h b/be/src/kudu/rpc/service_pool.h
index 2bc8873..3e7229a 100644
--- a/be/src/kudu/rpc/service_pool.h
+++ b/be/src/kudu/rpc/service_pool.h
@@ -14,17 +14,15 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_SERVICE_POOL_H
-#define KUDU_SERVICE_POOL_H
+#pragma once
 
 #include <cstddef>
 #include <functional>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/macros.h"
 #include "kudu/gutil/port.h"
 #include "kudu/gutil/ref_counted.h"
@@ -52,7 +50,7 @@ struct RpcMethodInfo;
 // Also includes a queue that calls get pushed onto for handling by the pool.
 class ServicePool : public RpcService {
  public:
-  ServicePool(gscoped_ptr<ServiceIf> service,
+  ServicePool(std::unique_ptr<ServiceIf> service,
               const scoped_refptr<MetricEntity>& metric_entity,
               size_t service_queue_length);
   virtual ~ServicePool();
@@ -76,7 +74,7 @@ class ServicePool : public RpcService {
 
   RpcMethodInfo* LookupMethod(const RemoteMethod& method) override;
 
-  virtual Status QueueInboundCall(gscoped_ptr<InboundCall> call) OVERRIDE;
+  virtual Status QueueInboundCall(std::unique_ptr<InboundCall> call) OVERRIDE;
 
   const Counter* RpcsTimedOutInQueueMetricForTests() const {
     return rpcs_timed_out_in_queue_.get();
@@ -96,7 +94,7 @@ class ServicePool : public RpcService {
   void RunThread();
   void RejectTooBusy(InboundCall* c);
 
-  gscoped_ptr<ServiceIf> service_;
+  std::unique_ptr<ServiceIf> service_;
   std::vector<scoped_refptr<kudu::Thread> > threads_;
   LifoServiceQueue service_queue_;
   scoped_refptr<Histogram> incoming_queue_time_;
@@ -113,5 +111,3 @@ class ServicePool : public RpcService {
 
 } // namespace rpc
 } // namespace kudu
-
-#endif
diff --git a/be/src/kudu/rpc/service_queue.h b/be/src/kudu/rpc/service_queue.h
index 2751a30..5c1dcdd 100644
--- a/be/src/kudu/rpc/service_queue.h
+++ b/be/src/kudu/rpc/service_queue.h
@@ -14,8 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_UTIL_SERVICE_QUEUE_H
-#define KUDU_UTIL_SERVICE_QUEUE_H
+#pragma once
 
 #include <memory>
 #include <string>
@@ -221,5 +220,3 @@ class LifoServiceQueue {
 
 } // namespace rpc
 } // namespace kudu
-
-#endif
diff --git a/be/src/kudu/rpc/transfer.cc b/be/src/kudu/rpc/transfer.cc
index fefa86e..b268e77 100644
--- a/be/src/kudu/rpc/transfer.cc
+++ b/be/src/kudu/rpc/transfer.cc
@@ -36,13 +36,21 @@
 #include "kudu/util/logging.h"
 #include "kudu/util/net/socket.h"
 
-DEFINE_int64_hidden(rpc_max_message_size, (50 * 1024 * 1024),
+DEFINE_bool(rpc_max_message_size_enable_validation, true,
+            "Whether to turn off validation for --rpc_max_message_size flag. "
+            "This is a test-only flag.");
+TAG_FLAG(rpc_max_message_size_enable_validation, unsafe);
+
+DEFINE_int64(rpc_max_message_size, (50 * 1024 * 1024),
              "The maximum size of a message that any RPC that the server will accept. "
              "Must be at least 1MB.");
 TAG_FLAG(rpc_max_message_size, advanced);
 TAG_FLAG(rpc_max_message_size, runtime);
 
 static bool ValidateMaxMessageSize(const char* flagname, int64_t value) {
+  if (!FLAGS_rpc_max_message_size_enable_validation) {
+    return true;
+  }
   if (value < 1 * 1024 * 1024) {
     LOG(ERROR) << flagname << " must be at least 1MB.";
     return false;
@@ -54,8 +62,7 @@ static bool ValidateMaxMessageSize(const char* flagname, int64_t value) {
 
   return true;
 }
-static bool dummy = google::RegisterFlagValidator(
-    &FLAGS_rpc_max_message_size, &ValidateMaxMessageSize);
+DEFINE_validator(rpc_max_message_size, &ValidateMaxMessageSize);
 
 namespace kudu {
 namespace rpc {
diff --git a/be/src/kudu/rpc/transfer.h b/be/src/kudu/rpc/transfer.h
index b95d43d..3342c93 100644
--- a/be/src/kudu/rpc/transfer.h
+++ b/be/src/kudu/rpc/transfer.h
@@ -14,9 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
-#ifndef KUDU_RPC_TRANSFER_H
-#define KUDU_RPC_TRANSFER_H
+#pragma once
 
 #include <array>
 #include <cstddef>
@@ -209,4 +207,3 @@ struct TransferCallbacks {
 
 } // namespace rpc
 } // namespace kudu
-#endif
diff --git a/be/src/kudu/security/CMakeLists.txt b/be/src/kudu/security/CMakeLists.txt
index 9533523..7abaabb 100644
--- a/be/src/kudu/security/CMakeLists.txt
+++ b/be/src/kudu/security/CMakeLists.txt
@@ -96,15 +96,6 @@ ADD_EXPORTABLE_LIBRARY(security
   SRCS ${SECURITY_SRCS}
   DEPS ${SECURITY_LIBS})
 
-# Since Kudu tests are explicitly disabled, we want to expose some of their sources
-# to Impala using another variable.
-set(SECURITY_TEST_SRCS_FOR_IMPALA test/mini_kdc.cc)
-add_library(security-test-for-impala ${SECURITY_TEST_SRCS_FOR_IMPALA})
-target_link_libraries(security-test-for-impala
-  gutil
-  kudu_test_util
-  kudu_util
-  security)
 
 ##############################
 # mini_kdc
@@ -136,11 +127,10 @@ if (NOT NO_TESTS)
     security)
 
   # Tests
-  set(KUDU_TEST_LINK_LIBS
+  SET_KUDU_TEST_LINK_LIBS(
     mini_kdc
     security
-    security_test_util
-    ${KUDU_MIN_TEST_LIBS})
+    security_test_util)
 
   ADD_KUDU_TEST(ca/cert_management-test)
   ADD_KUDU_TEST(cert-test)
diff --git a/be/src/kudu/util/CMakeLists.txt b/be/src/kudu/util/CMakeLists.txt
index 2ca852d..4c576bb 100644
--- a/be/src/kudu/util/CMakeLists.txt
+++ b/be/src/kudu/util/CMakeLists.txt
@@ -30,6 +30,20 @@ ADD_EXPORTABLE_LIBRARY(util_compression_proto
   NONLINK_DEPS ${UTIL_COMPRESSION_PROTO_TGTS})
 
 #######################################
+# hash_proto
+#######################################
+
+PROTOBUF_GENERATE_CPP(
+  HASH_PROTO_SRCS HASH_PROTO_HDRS HASH_PROTO_TGTS
+  SOURCE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..
+  BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR}/../..
+  PROTO_FILES hash.proto)
+ADD_EXPORTABLE_LIBRARY(hash_proto
+  SRCS ${HASH_PROTO_SRCS}
+  DEPS protobuf
+  NONLINK_DEPS ${HASH_PROTO_TGTS})
+
+#######################################
 # histogram_proto
 #######################################
 
@@ -48,14 +62,28 @@ ADD_EXPORTABLE_LIBRARY(histogram_proto
 #######################################
 
 PROTOBUF_GENERATE_CPP(
-    MAINTENANCE_MANAGER_PROTO_SRCS MAINTENANCE_MANAGER_PROTO_HDRS MAINTENANCE_MANAGER_PROTO_TGTS
-    SOURCE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..
-    BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR}/../..
-    PROTO_FILES maintenance_manager.proto)
+  MAINTENANCE_MANAGER_PROTO_SRCS MAINTENANCE_MANAGER_PROTO_HDRS MAINTENANCE_MANAGER_PROTO_TGTS
+  SOURCE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..
+  BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR}/../..
+  PROTO_FILES maintenance_manager.proto)
 ADD_EXPORTABLE_LIBRARY(maintenance_manager_proto
-    SRCS ${MAINTENANCE_MANAGER_PROTO_SRCS}
-    DEPS protobuf
-    NONLINK_DEPS ${MAINTENANCE_MANAGER_PROTO_TGTS})
+  SRCS ${MAINTENANCE_MANAGER_PROTO_SRCS}
+  DEPS protobuf
+  NONLINK_DEPS ${MAINTENANCE_MANAGER_PROTO_TGTS})
+
+#######################################
+# mem_tracker_proto
+#######################################
+
+PROTOBUF_GENERATE_CPP(
+  MEM_TRACKER_PROTO_SRCS MEM_TRACKER_PROTO_HDRS MEM_TRACKER_PROTO_TGTS
+  SOURCE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..
+  BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR}/../..
+  PROTO_FILES mem_tracker.proto)
+ADD_EXPORTABLE_LIBRARY(mem_tracker_proto
+  SRCS ${MEM_TRACKER_PROTO_SRCS}
+  DEPS protobuf
+  NONLINK_DEPS ${MEM_TRACKER_PROTO_TGTS})
 
 #######################################
 # pb_util_proto
@@ -119,10 +147,11 @@ set(UTIL_SRCS
   async_logger.cc
   atomic.cc
   bitmap.cc
+  block_cache_metrics.cc
+  block_bloom_filter.cc
   bloom_filter.cc
-  bitmap.cc
   cache.cc
-  cache_metrics.cc
+  char_util.cc
   coding.cc
   condition_variable.cc
   cow_object.cc
@@ -139,6 +168,7 @@ set(UTIL_SRCS
   faststring.cc
   fault_injection.cc
   file_cache.cc
+  file_cache_metrics.cc
   flags.cc
   flag_tags.cc
   flag_validators.cc
@@ -160,13 +190,14 @@ set(UTIL_SRCS
   memory/overwrite.cc
   mem_tracker.cc
   metrics.cc
-  # minidump.cc
+  minidump.cc
   monotime.cc
   mutex.cc
   net/dns_resolver.cc
   net/net_util.cc
   net/sockaddr.cc
   net/socket.cc
+  nvm_cache.cc
   oid_generator.cc
   once.cc
   os-util.cc
@@ -198,11 +229,11 @@ set(UTIL_SRCS
   trace_metrics.cc
   user.cc
   url-coding.cc
-  # Remove from compilation, as it depends on generated method calls. Replaced by
-  # kudu_version.cc in Impala's common library.
-  # version_info.cc
+  version_info.cc
   version_util.cc
+  web_callback_registry.cc
   website_util.cc
+  yamlreader.cc
   zlib.cc
 )
 
@@ -211,10 +242,26 @@ set(UTIL_SRCS
 # optimized regardless of the default optimization options.
 set_source_files_properties(memory/overwrite.cc PROPERTIES COMPILE_FLAGS "-O3")
 
-if(HAVE_LIB_VMEM)
-  set(UTIL_SRCS
-    ${UTIL_SRCS}
-    nvm_cache.cc)
+# Detect AVX2 support
+set(AVX2_CMD "echo | ${CMAKE_CXX_COMPILER} -mavx2 -dM -E - | awk '$2 == \"__AVX2__\" { print $3 }'")
+execute_process(
+  COMMAND bash -c ${AVX2_CMD}
+  OUTPUT_VARIABLE AVX2_SUPPORT
+  OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+
+# block_bloom_filter_avx2.cc uses AVX2 operations.
+if (AVX2_SUPPORT)
+  list(APPEND UTIL_SRCS block_bloom_filter_avx2.cc)
+  set_source_files_properties(block_bloom_filter_avx2.cc PROPERTIES COMPILE_FLAGS "-mavx2")
+  # block_bloom_filter.cc is not compiled explicitly with AVX2 instructions(-mavx2) but it needs
+  # to know at compile time whether AVX2 support is available, hence the custom definition
+  # instead of relying on __AVX2__ defined by compiler with -mavx2.
+  set_source_files_properties(block_bloom_filter_avx2.cc block_bloom_filter.cc
+                              PROPERTIES COMPILE_DEFINITIONS "USE_AVX2=1")
+  message("Compiler supports AVX2")
+else()
+  message("Compiler does not support AVX2")
 endif()
 
 set(UTIL_LIBS
@@ -222,12 +269,15 @@ set(UTIL_LIBS
   gflags
   glog
   gutil
+  hash_proto
   histogram_proto
   libev
   maintenance_manager_proto
+  mem_tracker_proto
   pb_util_proto
   protobuf
   version_info_proto
+  yaml
   zlib)
 
 if(NOT APPLE)
@@ -238,12 +288,6 @@ if(NOT APPLE)
     rt)
 endif()
 
-if(HAVE_LIB_VMEM)
-  set(UTIL_LIBS
-    ${UTIL_LIBS}
-    vmem)
-endif()
-
 # We use MallocExtension, but not in the exported version of the library.
 set(EXPORTED_UTIL_LIBS ${UTIL_LIBS})
 if(${KUDU_TCMALLOC_AVAILABLE})
@@ -264,15 +308,62 @@ set(UTIL_COMPRESSION_SRCS
 set(UTIL_COMPRESSION_LIBS
   kudu_util
   util_compression_proto
-
   glog
   gutil
   lz4
   snappy
   zlib)
+
 ADD_EXPORTABLE_LIBRARY(kudu_util_compression
   SRCS ${UTIL_COMPRESSION_SRCS}
   DEPS ${UTIL_COMPRESSION_LIBS})
+# Define LZ4_DISABLE_DEPRECATE_WARNINGS to mute warnings like:
+# "'int LZ4_compress(const char*, char*, int)' is deprecated".
+target_compile_definitions(kudu_util_compression PUBLIC LZ4_DISABLE_DEPRECATE_WARNINGS)
+target_compile_definitions(kudu_util_compression_exported PUBLIC LZ4_DISABLE_DEPRECATE_WARNINGS)
+
+#######################################
+# kudu_curl_util
+#######################################
+add_library(kudu_curl_util
+  curl_util.cc)
+target_link_libraries(kudu_curl_util
+  security
+  ${CURL_LIBRARIES}
+  glog
+  gutil)
+
+#######################################
+# kudu_cloud_util
+#######################################
+add_library(kudu_cloud_util
+  cloud/instance_detector.cc
+  cloud/instance_metadata.cc)
+target_link_libraries(kudu_cloud_util
+  kudu_curl_util)
+
+# See the comment in sanitizer_options.cc for details on this library's usage.
+# The top-level CMakeLists sets a ${SANITIZER_OPTIONS_OVERRIDE} variable which
+# should be linked first into all Kudu binaries.
+
+#######################################
+# sanitizer_options
+#######################################
+add_library(sanitizer_options STATIC sanitizer_options.cc)
+target_link_libraries(sanitizer_options gutil)
+if ("${KUDU_USE_ASAN}" OR "${KUDU_USE_TSAN}" OR "${KUDU_USE_UBSAN}")
+  # By default the sanitizers use addr2line utility to symbolize reports.
+  # llvm-symbolizer is faster, consumes less memory and produces much better reports.
+  # We set KUDU_EXTERNAL_SYMBOLIZER_PATH which is used in sanitizer_options.cc
+  # to set the default external_symbolizer_path.
+  SET(KUDU_LLVM_SYMBOLIZER_PATH ${THIRDPARTY_INSTALL_UNINSTRUMENTED_DIR}/bin/llvm-symbolizer)
+  if (EXISTS ${KUDU_LLVM_SYMBOLIZER_PATH})
+    target_compile_definitions(sanitizer_options PRIVATE KUDU_EXTERNAL_SYMBOLIZER_PATH=${KUDU_LLVM_SYMBOLIZER_PATH})
+  else()
+    message(SEND_ERROR
+      "Could not find llvm-symbolizer required for sanitizer builds at ${KUDU_LLVM_SYMBOLIZER_PATH}")
+  endif()
+endif()
 
 #######################################
 # kudu_test_util
@@ -284,29 +375,9 @@ add_library(kudu_test_util
 target_link_libraries(kudu_test_util
   gflags
   glog
-  # Impala doesn't have gmock in its toolchain
-  gtest
-  #gmock
+  gmock
   kudu_util)
 
-if(HAVE_LIB_VMEM)
-  target_link_libraries(kudu_test_util
-    vmem)
-endif()
-
-#######################################
-# kudu_curl_util
-#######################################
-if(NOT NO_TESTS)
-  add_library(kudu_curl_util
-    curl_util.cc)
-  target_link_libraries(kudu_curl_util
-    security
-    ${CURL_LIBRARIES}
-    glog
-    gutil)
-endif()
-
 #######################################
 # kudu_test_main
 #######################################
@@ -333,22 +404,26 @@ endif()
 #######################################
 
 add_executable(protoc-gen-insertions protoc-gen-insertions.cc)
-target_link_libraries(protoc-gen-insertions gutil glog gflags protoc protobuf ${KUDU_BASE_LIBS})
+target_link_libraries(protoc-gen-insertions gutil protobuf protoc ${KUDU_BASE_LIBS})
 
 #######################################
 # Unit tests
 #######################################
 
-set(KUDU_TEST_LINK_LIBS kudu_util gutil ${KUDU_MIN_TEST_LIBS})
+SET_KUDU_TEST_LINK_LIBS(kudu_util gutil)
 ADD_KUDU_TEST(async_util-test)
 ADD_KUDU_TEST(atomic-test)
 ADD_KUDU_TEST(bit-util-test)
 ADD_KUDU_TEST(bitmap-test)
+ADD_KUDU_TEST(bitset-test)
 ADD_KUDU_TEST(blocking_queue-test)
+ADD_KUDU_TEST(block_bloom_filter-test)
 ADD_KUDU_TEST(bloom_filter-test)
 ADD_KUDU_TEST(cache-bench RUN_SERIAL true)
 ADD_KUDU_TEST(cache-test)
 ADD_KUDU_TEST(callback_bind-test)
+ADD_KUDU_TEST(char_util-test
+  DATA_FILES testdata/char_truncate_utf8.txt testdata/char_truncate_ascii.txt)
 ADD_KUDU_TEST(countdown_latch-test)
 ADD_KUDU_TEST(crc-test RUN_SERIAL true) # has a benchmark
 ADD_KUDU_TEST(debug-util-test)
@@ -412,9 +487,11 @@ ADD_KUDU_TEST(thread-test)
 ADD_KUDU_TEST(threadpool-test)
 ADD_KUDU_TEST(throttler-test)
 ADD_KUDU_TEST(trace-test PROCESSORS 4)
+ADD_KUDU_TEST(ttl_cache-test)
 ADD_KUDU_TEST(url-coding-test)
 ADD_KUDU_TEST(user-test)
 ADD_KUDU_TEST(version_util-test)
+ADD_KUDU_TEST(yamlreader-test)
 
 if (NOT APPLE)
   ADD_KUDU_TEST(minidump-test)
@@ -484,3 +561,21 @@ if(NOT NO_TESTS)
     cfile
     kudu_util_compression)
 endif()
+
+#######################################
+# curl_util-test
+#######################################
+ADD_KUDU_TEST(curl_util-test)
+if(NOT NO_TESTS)
+  target_link_libraries(curl_util-test
+    kudu_curl_util)
+endif()
+
+#######################################
+# instance_detector-test
+#######################################
+ADD_KUDU_TEST(cloud/instance_detector-test)
+if(NOT NO_TESTS)
+  target_link_libraries(instance_detector-test
+    kudu_cloud_util)
+endif()
diff --git a/be/src/kudu/util/async_util-test.cc b/be/src/kudu/util/async_util-test.cc
index 5cb7a63..91f2baa 100644
--- a/be/src/kudu/util/async_util-test.cc
+++ b/be/src/kudu/util/async_util-test.cc
@@ -28,6 +28,7 @@
 #include "kudu/gutil/basictypes.h"
 #include "kudu/gutil/callback.h"
 #include "kudu/util/monotime.h"
+#include "kudu/util/scoped_cleanup.h"
 #include "kudu/util/status.h"
 #include "kudu/util/test_macros.h"
 #include "kudu/util/test_util.h"
@@ -43,7 +44,7 @@ class AsyncUtilTest : public KuduTest {
     // Set up an alarm to fail the test in case of deadlock.
     alarm(30);
   }
-  ~AsyncUtilTest() {
+  virtual ~AsyncUtilTest() {
     // Disable the alarm on test exit.
     alarm(0);
   }
@@ -99,31 +100,72 @@ TEST_F(AsyncUtilTest, TestSynchronizerMultiWait) {
   }
 }
 
-TEST_F(AsyncUtilTest, TestSynchronizerTimedWait) {
-  thread waiter;
-  {
-    Synchronizer sync;
-    auto cb = sync.AsStatusCallback();
-    waiter = thread([cb] {
-        SleepFor(MonoDelta::FromMilliseconds(5));
-        cb.Run(Status::OK());
-    });
-    ASSERT_OK(sync.WaitFor(MonoDelta::FromMilliseconds(1000)));
-  }
-  waiter.join();
+// Flavors of wait that Synchronizer is capable of: WaitFor() or WaitUntil().
+enum class TimedWaitFlavor {
+  WaitFor,
+  WaitUntil,
+};
 
-  {
-    Synchronizer sync;
-    auto cb = sync.AsStatusCallback();
-    waiter = thread([cb] {
-        SleepFor(MonoDelta::FromMilliseconds(1000));
-        cb.Run(Status::OK());
-    });
-    ASSERT_TRUE(sync.WaitFor(MonoDelta::FromMilliseconds(5)).IsTimedOut());
+class AsyncUtilTimedWaitTest:
+    public AsyncUtilTest,
+    public ::testing::WithParamInterface<TimedWaitFlavor> {
+};
+
+TEST_P(AsyncUtilTimedWaitTest, SynchronizerTimedWaitSuccess) {
+  const auto kWaitInterval = MonoDelta::FromMilliseconds(1000);
+
+  Synchronizer sync;
+  auto cb = sync.AsStatusCallback();
+  auto waiter = thread([cb] {
+    SleepFor(MonoDelta::FromMilliseconds(5));
+    cb.Run(Status::OK());
+  });
+  SCOPED_CLEANUP({
+    waiter.join();
+  });
+  const auto mode = GetParam();
+  switch (mode) {
+    case TimedWaitFlavor::WaitFor:
+      ASSERT_OK(sync.WaitFor(kWaitInterval));
+      break;
+    case TimedWaitFlavor::WaitUntil:
+      ASSERT_OK(sync.WaitUntil(MonoTime::Now() + kWaitInterval));
+      break;
+    default:
+      FAIL() << "unsupported wait mode " << static_cast<int>(mode);
+      break;
   }
+}
+
+TEST_P(AsyncUtilTimedWaitTest, SynchronizerTimedWaitTimeout) {
+  const auto kWaitInterval = MonoDelta::FromMilliseconds(5);
 
-  // Waiting on the thread gives TSAN to check that no thread safety issues
-  // occurred.
-  waiter.join();
+  Synchronizer sync;
+  auto cb = sync.AsStatusCallback();
+  auto waiter = thread([cb] {
+    SleepFor(MonoDelta::FromMilliseconds(1000));
+    cb.Run(Status::OK());
+  });
+  SCOPED_CLEANUP({
+    waiter.join();
+  });
+  const auto mode = GetParam();
+  switch (mode) {
+    case TimedWaitFlavor::WaitFor:
+      ASSERT_TRUE(sync.WaitFor(kWaitInterval).IsTimedOut());
+      break;
+    case TimedWaitFlavor::WaitUntil:
+      ASSERT_TRUE(sync.WaitUntil(MonoTime::Now() + kWaitInterval).IsTimedOut());
+      break;
+    default:
+      FAIL() << "unsupported wait mode " << static_cast<int>(mode);
+      break;
+  }
 }
+
+INSTANTIATE_TEST_CASE_P(WaitFlavors,
+                        AsyncUtilTimedWaitTest,
+                        ::testing::Values(TimedWaitFlavor::WaitFor,
+                                          TimedWaitFlavor::WaitUntil));
+
 } // namespace kudu
diff --git a/be/src/kudu/util/async_util.h b/be/src/kudu/util/async_util.h
index 338c6c2..61621d6 100644
--- a/be/src/kudu/util/async_util.h
+++ b/be/src/kudu/util/async_util.h
@@ -44,7 +44,7 @@ namespace kudu {
 class Synchronizer {
  public:
   Synchronizer()
-    : data_(std::make_shared<Data>()) {
+      : data_(std::make_shared<Data>()) {
   }
 
   void StatusCB(const Status& status) {
@@ -71,6 +71,13 @@ class Synchronizer {
     return data_->status;
   }
 
+  Status WaitUntil(const MonoTime& deadline) const {
+    if (PREDICT_FALSE(!data_->latch.WaitUntil(deadline))) {
+      return Status::TimedOut("timed out while waiting for the callback to be called");
+    }
+    return data_->status;
+  }
+
   void Reset() {
     data_->latch.Reset(1);
   }
diff --git a/be/src/kudu/util/bitmap-test.cc b/be/src/kudu/util/bitmap-test.cc
index 089ed3b..b6a280a 100644
--- a/be/src/kudu/util/bitmap-test.cc
+++ b/be/src/kudu/util/bitmap-test.cc
@@ -15,6 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include "kudu/util/bitmap.h"
+
 #include <cstdint>
 #include <cstring>
 #include <vector>
@@ -22,7 +24,6 @@
 #include <gtest/gtest.h>
 
 #include "kudu/gutil/strings/join.h"
-#include "kudu/util/bitmap.h"
 
 namespace kudu {
 
@@ -73,7 +74,7 @@ TEST(TestBitMap, TestIteration2) {
   ASSERT_EQ("1", JoinElements(read_back, ","));
 }
 
-TEST(TestBitmap, TestSetAndTestBits) {
+TEST(TestBitMap, TestSetAndTestBits) {
   uint8_t bm[1];
   memset(bm, 0, sizeof(bm));
 
@@ -137,17 +138,17 @@ TEST(TestBitMap, TestBulkSetAndTestBits) {
         BitmapChangeBits(bm, 0, total_size, !value);
         BitmapChangeBits(bm, offset, num_bits - offset, value);
 
-        ASSERT_EQ(value, BitMapIsAllSet(bm, offset, num_bits));
+        ASSERT_EQ(value, BitmapIsAllSet(bm, offset, num_bits));
         ASSERT_EQ(!value, BitmapIsAllZero(bm, offset, num_bits));
 
         if (offset > 1) {
           ASSERT_EQ(value, BitmapIsAllZero(bm, 0, offset - 1));
-          ASSERT_EQ(!value, BitMapIsAllSet(bm, 0, offset - 1));
+          ASSERT_EQ(!value, BitmapIsAllSet(bm, 0, offset - 1));
         }
 
         if ((offset + num_bits) < total_size) {
           ASSERT_EQ(value, BitmapIsAllZero(bm, num_bits, total_size));
-          ASSERT_EQ(!value, BitMapIsAllSet(bm, num_bits, total_size));
+          ASSERT_EQ(!value, BitmapIsAllSet(bm, num_bits, total_size));
         }
       }
       num_bits--;
@@ -227,4 +228,127 @@ TEST(TestBitMap, TestBitmapIteration) {
   ASSERT_EQ(expected_sizes[i], size);
 }
 
+TEST(TestBitMap, TestEquals) {
+  uint8_t bm1[8] = { 0 };
+  uint8_t bm2[8] = { 0 };
+  size_t num_bits = sizeof(bm1) * 8;
+  ASSERT_TRUE(BitmapEquals(bm1, bm2, num_bits));
+
+  // Loop over each bit starting from the end and going to the beginning. In
+  // each iteration, set the bit in one bitmap and verify that although the two
+  // bitmaps aren't equal, if we were to ignore the changed bits, they are still equal.
+  for (int i = num_bits - 1; i >= 0; i--) {
+    SCOPED_TRACE(i);
+    BitmapChange(bm1, i, true);
+    ASSERT_FALSE(BitmapEquals(bm1, bm2, num_bits));
+    ASSERT_TRUE(BitmapEquals(bm1, bm2, i));
+  }
+
+  // Now loop in the other direction, setting the second bitmap bit by bit.
+  // As before, if we consider the bitmaps in their entirety, they're not equal,
+  // but if we consider just the sequences where both are set, they are equal.
+  for (int i = 0; i < num_bits - 1; i++) {
+    SCOPED_TRACE(i);
+    BitmapChange(bm2, i, true);
+    ASSERT_FALSE(BitmapEquals(bm1, bm2, num_bits));
+    ASSERT_TRUE(BitmapEquals(bm1, bm2, i + 1));
+  }
+
+  // If we set the very last bit, both bitmaps are now equal in their entirety.
+  BitmapChange(bm2, num_bits - 1, true);
+  ASSERT_TRUE(BitmapEquals(bm1, bm2, num_bits));
+
+  // Test equality on overlapped bitmaps (i.e. a single underlying bitmap, two
+  // subsequences of which are considered to be two separate bitmaps).
+
+  // Set every third bit; the rest are unset.
+  uint8_t bm3[8] = { 0 };
+  for (int i = 0; i < num_bits; i += 3) {
+    BitmapChange(bm3, i, true);
+  }
+
+  ASSERT_TRUE(BitmapEquals(bm3, bm3, num_bits)); // fully overlapped
+  ASSERT_FALSE(BitmapEquals(bm3, bm3 + 1, num_bits - 8)); // off by one byte
+  ASSERT_TRUE(BitmapEquals(bm3, bm3 + 3, num_bits - 24)); // off by three bytes
+}
+
+TEST(TestBitMap, TestCopy) {
+  constexpr int kNumBytes = 8;
+  constexpr int kNumBits = kNumBytes * 8;
+  constexpr uint8_t kAllZeroes[kNumBytes] = { 0 };
+
+  {
+    // Byte-aligned copy with no offsets.
+    uint8_t res[kNumBytes];
+    BitmapChangeBits(res, 0, kNumBits, 1);
+
+    BitmapCopy(res, 0, kAllZeroes, 0, kNumBits);
+    ASSERT_TRUE(BitmapIsAllZero(res, 0, kNumBits));
+  }
+  {
+    // Byte-aligned copy with offsets.
+    uint8_t res[kNumBytes];
+    BitmapChangeBits(res, 0, kNumBits, 1);
+
+    ASSERT_TRUE(BitmapIsAllSet(res, 0, kNumBits));
+    constexpr size_t stride = kNumBits / 4;
+    for (int i = 0; i < kNumBits; i += stride) {
+      BitmapCopy(res, i, kAllZeroes, i, stride);
+      // The bits before the copy should reflect the copied data, while the bits
+      // after should reflect the original data.
+      ASSERT_TRUE(BitmapIsAllZero(res, 0, stride + i));
+      ASSERT_TRUE(BitmapIsAllSet(res, stride + i, kNumBits));
+    }
+    ASSERT_TRUE(BitmapIsAllZero(res, 0, kNumBits));
+  }
+  {
+    // Non-byte aligned; overwrite all but the first bit.
+    uint8_t res[kNumBytes];
+    BitmapChangeBits(res, 0, kNumBits, 1);
+
+    BitmapCopy(res, 1, kAllZeroes, 0, kNumBits - 1);
+    ASSERT_TRUE(BitmapTest(res, 0));
+    ASSERT_TRUE(BitmapIsAllZero(res, 1, kNumBits - 1));
+  }
+  {
+    // Non-byte aligned; overwrite all but the last bit.
+    uint8_t res[kNumBytes];
+    BitmapChangeBits(res, 0, kNumBits, 1);
+
+    BitmapCopy(res, 0, kAllZeroes, 1, kNumBits - 1);
+    ASSERT_TRUE(BitmapTest(res, kNumBits - 1));
+    ASSERT_TRUE(BitmapIsAllZero(res, 0, kNumBits - 1));
+  }
+  {
+    // Non-byte aligned; overwrite all but the first and last bits.
+    uint8_t res[kNumBytes];
+    BitmapChangeBits(res, 0, kNumBits, 1);
+
+    BitmapCopy(res, 1, kAllZeroes, 1, kNumBits - 2);
+    ASSERT_TRUE(BitmapTest(res, 0));
+    ASSERT_TRUE(BitmapTest(res, kNumBits - 1));
+    ASSERT_TRUE(BitmapIsAllZero(res, 1, kNumBits - 2));
+  }
+}
+
+#ifndef NDEBUG
+TEST(TestBitMapDeathTest, TestCopyOverlap) {
+  uint8_t bm[2] = { 0 };
+  ASSERT_DEATH({ BitmapCopy(bm, 0, bm, 0, 16); },
+               "Source and destination overlap");
+}
+
+TEST(TestBitMapDeathTest, TestCopyOverlapSrcAfterDst) {
+  uint8_t bm[2] = { 0 };
+  ASSERT_DEATH({ BitmapCopy(bm, 0, bm, 1, 15); },
+               "Source and destination overlap");
+}
+
+TEST(TestBitMapDeathTest, TestCopyOverlapDstAfterSrc) {
+  uint8_t bm[2] = { 0 };
+  ASSERT_DEATH({ BitmapCopy(bm, 1, bm, 0, 15); },
+               "Source and destination overlap");
+}
+#endif
+
 } // namespace kudu
diff --git a/be/src/kudu/util/bitmap.cc b/be/src/kudu/util/bitmap.cc
index eed7880..370f361 100644
--- a/be/src/kudu/util/bitmap.cc
+++ b/be/src/kudu/util/bitmap.cc
@@ -24,6 +24,8 @@
 
 #include "kudu/gutil/stringprintf.h"
 
+using std::string;
+
 namespace kudu {
 
 void BitmapChangeBits(uint8_t *bitmap, size_t offset, size_t num_bits, bool value) {
@@ -116,8 +118,39 @@ bool BitmapFindFirst(const uint8_t *bitmap, size_t offset, size_t bitmap_size,
   return false;
 }
 
-std::string BitmapToString(const uint8_t *bitmap, size_t num_bits) {
-  std::string s;
+void BitmapCopy(uint8_t* dst, size_t dst_offset,
+                const uint8_t* src, size_t src_offset,
+                size_t num_bits) {
+  DCHECK_GT(num_bits, 0);
+
+  // Prohibit overlap in debug builds.
+#ifndef NDEBUG
+  const uint8_t* src_start = src + (src_offset >> 3);
+  const uint8_t* src_end = src + ((src_offset + num_bits) >> 3);
+  uint8_t* dst_start = dst + (dst_offset >> 3);
+  uint8_t* dst_end = dst + ((dst_offset + num_bits) >> 3);
+  DCHECK(src_start >= dst_end || dst_start >= src_end)
+      << "Source and destination overlap";
+#endif
+
+  // If the copy is entirely byte-aligned, we can use memcpy.
+  if ((src_offset & 7) == 0 && (dst_offset & 7) == 0 && (num_bits & 7) == 0) {
+    memcpy(dst + (dst_offset >> 3),
+           src + (src_offset >> 3),
+           BitmapSize(num_bits));
+    return;
+  }
+
+  // Otherwise, we fallback to a slower bit-by-bit copy.
+  //
+  // TODO(adar): this can be optimized using word-by-word operations.
+  for (size_t bit_num = 0; bit_num < num_bits; bit_num++) {
+    BitmapChange(dst, dst_offset + bit_num, BitmapTest(src, src_offset + bit_num));
+  }
+}
+
+string BitmapToString(const uint8_t *bitmap, size_t num_bits) {
+  string s;
   size_t index = 0;
   while (index < num_bits) {
     StringAppendF(&s, "%4zu: ", index);
diff --git a/be/src/kudu/util/bitmap.h b/be/src/kudu/util/bitmap.h
index d9f5260..c29ba09 100644
--- a/be/src/kudu/util/bitmap.h
+++ b/be/src/kudu/util/bitmap.h
@@ -28,6 +28,7 @@
 
 #include "kudu/gutil/bits.h"
 #include "kudu/gutil/port.h"
+#include "kudu/gutil/strings/fastmem.h"
 
 namespace kudu {
 
@@ -85,19 +86,47 @@ inline bool BitmapFindFirstZero(const uint8_t *bitmap, size_t offset,
 }
 
 // Returns true if the bitmap contains only ones.
-inline bool BitMapIsAllSet(const uint8_t *bitmap, size_t offset, size_t bitmap_size) {
-  DCHECK_LT(offset, bitmap_size);
+inline bool BitmapIsAllSet(const uint8_t *bitmap, size_t offset, size_t bitmap_size) {
+  DCHECK_LE(offset, bitmap_size);
   size_t idx;
   return !BitmapFindFirstZero(bitmap, offset, bitmap_size, &idx);
 }
 
 // Returns true if the bitmap contains only zeros.
 inline bool BitmapIsAllZero(const uint8_t *bitmap, size_t offset, size_t bitmap_size) {
-  DCHECK_LT(offset, bitmap_size);
+  DCHECK_LE(offset, bitmap_size);
   size_t idx;
   return !BitmapFindFirstSet(bitmap, offset, bitmap_size, &idx);
 }
 
+// Returns true if the two bitmaps are equal.
+//
+// It is assumed that both bitmaps have 'bitmap_size' number of bits.
+inline bool BitmapEquals(const uint8_t* bm1, const uint8_t* bm2, size_t bitmap_size) {
+  // Use memeq() to check all of the full bytes.
+  size_t num_full_bytes = bitmap_size >> 3;
+  if (!strings::memeq(bm1, bm2, num_full_bytes)) {
+    return false;
+  }
+
+  // Check any remaining bits in one extra operation.
+  size_t num_remaining_bits = bitmap_size - (num_full_bytes << 3);
+  if (num_remaining_bits == 0) {
+    return true;
+  }
+  DCHECK_LT(num_remaining_bits, 8);
+  uint8_t mask = (1 << num_remaining_bits) - 1;
+  return (bm1[num_full_bytes] & mask) == (bm2[num_full_bytes] & mask);
+}
+
+// Copies a slice of 'src' to 'dst'. Offsets and sizing are all expected to be
+// bit quantities.
+//
+// Note: 'src' and 'dst' may not overlap.
+void BitmapCopy(uint8_t* dst, size_t dst_offset,
+                const uint8_t* src, size_t src_offset,
+                size_t num_bits);
+
 std::string BitmapToString(const uint8_t *bitmap, size_t num_bits);
 
 // Iterator which yields ranges of set and unset bits.
diff --git a/be/src/kudu/util/bitset-test.cc b/be/src/kudu/util/bitset-test.cc
new file mode 100644
index 0000000..b12fd16
--- /dev/null
+++ b/be/src/kudu/util/bitset-test.cc
@@ -0,0 +1,170 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "kudu/util/bitset.h"
+
+#include <cstddef>
+#include <set>
+#include <string>
+#include <utility>
+
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+#include "kudu/gutil/map-util.h"
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/util/test_macros.h"
+
+using std::set;
+using std::string;
+using strings::Substitute;
+
+namespace {
+
+enum TestEnum {
+  BEGINNING,
+  MIDDLE,
+  END,
+};
+constexpr size_t kMaxEnumVal = TestEnum::END + 1;
+
+typedef set<TestEnum> EnumSet;
+typedef FixedBitSet<TestEnum, kMaxEnumVal> EnumBitSet;
+
+const EnumSet kFullEnumSet({
+  TestEnum::BEGINNING,
+  TestEnum::MIDDLE,
+  TestEnum::END,
+});
+
+bool CompareContainers(EnumSet enum_set, EnumBitSet ebs) {
+  if (enum_set.empty() != ebs.empty()) {
+    LOG(INFO) << Substitute("enum set is $0, bitset is $1",
+                            enum_set.empty() ? "empty" : "not empty",
+                            ebs.empty() ? "empty" : "not empty");
+    return false;
+  }
+  if (enum_set.size() != ebs.size()) {
+    LOG(INFO) << Substitute("enum set has $0 elements, bitset has $1",
+                            enum_set.size(), ebs.size());
+    return false;
+  }
+  for (const auto& e : enum_set) {
+    if (!ContainsKey(ebs, e)) {
+      LOG(INFO) << Substitute("enum set contains $0, not found in bitset", e);
+      return false;
+    }
+  }
+  for (TestEnum e : ebs) {
+    if (!ContainsKey(enum_set, e)) {
+      LOG(INFO) << Substitute("bitset contains $0, not found in enum set", e);
+      return false;
+    }
+  }
+  return true;
+}
+
+} // anonymous namespace
+
+TEST(BitSetTest, TestConstruction) {
+  EnumSet enum_set;
+  for (int i = 0; i < kMaxEnumVal; i++) {
+    InsertOrDie(&enum_set, static_cast<TestEnum>(i));
+    EnumBitSet ebs(enum_set);
+    ASSERT_TRUE(CompareContainers(enum_set, ebs));
+  }
+}
+
+TEST(BitSetTest, TestInitializerList) {
+  EnumBitSet ebs({ TestEnum::BEGINNING });
+  ASSERT_TRUE(ContainsKey(ebs, TestEnum::BEGINNING));
+  ASSERT_EQ(1, ebs.size());
+}
+
+// Test basic operations for a bitset of enums, comparing is to an STL
+// container of enums.
+TEST(BitSetTest, TestBasicOperations) {
+  EnumBitSet bitset;
+  ASSERT_TRUE(bitset.empty());
+
+  EnumSet enum_set;
+  const auto add_to_containers = [&] (TestEnum e) {
+    ASSERT_EQ(InsertIfNotPresent(&enum_set, e),
+              InsertIfNotPresent(&bitset, e));
+  };
+  const auto remove_from_containers = [&] (TestEnum e) {
+    ASSERT_EQ(enum_set.erase(e), bitset.erase(e));
+  };
+  // Insert all elements, checking to make sure our containers' contents remain
+  // the same.
+  for (const auto& e : kFullEnumSet) {
+    NO_FATALS(add_to_containers(e));
+    ASSERT_TRUE(CompareContainers(enum_set, bitset));
+  }
+
+  // Do a sanity check that we can't insert something that already exists in
+  // the set.
+  ASSERT_FALSE(InsertIfNotPresent(&bitset, TestEnum::BEGINNING));
+
+  // Now remove all elements.
+  for (const auto& e : kFullEnumSet) {
+    NO_FATALS(remove_from_containers(e));
+    ASSERT_TRUE(CompareContainers(enum_set, bitset));
+  }
+
+  // Do a final sanity check that the bitset looks how we expect.
+  ASSERT_TRUE(CompareContainers(enum_set, bitset));
+  ASSERT_TRUE(bitset.empty());
+}
+
+// Test the set's insert interface.
+TEST(BitSetTest, TestInsert) {
+  EnumBitSet bitset;
+  {
+    auto iter_and_inserted = bitset.insert(TestEnum::BEGINNING);
+    ASSERT_EQ(TestEnum::BEGINNING, *iter_and_inserted.first);
+    ASSERT_TRUE(iter_and_inserted.second);
+  }
+  {
+    auto iter_and_inserted = bitset.insert(TestEnum::BEGINNING);
+    ASSERT_EQ(TestEnum::BEGINNING, *iter_and_inserted.first);
+    ASSERT_FALSE(iter_and_inserted.second);
+  }
+}
+
+#ifndef NDEBUG
+// Make sure we hit a check failure if we attempt to use the bitset with values
+// outside of its range.
+TEST(BitSetDeathTest, TestInvalidUsage) {
+  const string kDeathMessage = "Check failed";
+  const TestEnum kOutOfRange = TestEnum(kMaxEnumVal);
+  EnumBitSet bitset;
+  EXPECT_DEATH({
+    bitset.insert(kOutOfRange);
+  }, kDeathMessage);
+
+  EXPECT_DEATH({
+    bitset.erase(kOutOfRange);
+  }, kDeathMessage);
+
+  EXPECT_DEATH({
+    bitset.contains(kOutOfRange);
+  }, kDeathMessage);
+
+  ASSERT_TRUE(bitset.empty());
+}
+#endif // NDEBUG
diff --git a/be/src/kudu/util/bitset.h b/be/src/kudu/util/bitset.h
new file mode 100644
index 0000000..61a6274
--- /dev/null
+++ b/be/src/kudu/util/bitset.h
@@ -0,0 +1,202 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+#pragma once
+
+#include <bitset>
+#include <cstddef>
+#include <initializer_list>
+#include <iterator>
+#include <utility>
+
+#include <glog/logging.h>
+
+#include "kudu/gutil/macros.h"
+
+// Utility template for working with a bitset to make it feel more like a
+// container. E.g., this is useful for building containers for enum types,
+// instead of using a hashed container.
+//
+// This supports operating on 'IntType' types with values ranging from [0,
+// MaxVals). The underlying bitset is of size 'MaxVals', which must be known at
+// compile time.
+//
+// Under the hood, std::bitset uses word-sized bitwise instructions on
+// stack-allocated bytes, so the expectation is that 'MaxVals' is not many
+// times larger than the word-size. A size limit of 64 bits is enforced at
+// compile time.
+template <typename IntType, size_t MaxVals>
+class FixedBitSet {
+ public:
+  // These types are exposed to match those provided by STL containers, which
+  // allows template instantiations to be used by map utility functions.
+  class iterator;
+  typedef IntType key_type;
+  typedef IntType value_type;
+
+  // Constructs an empty FixedBitSet.
+  FixedBitSet() {}
+
+  // Constructs a new FixedBitSet from an initializer list.
+  FixedBitSet(std::initializer_list<IntType> list) {
+    for (const IntType& val : list) {
+      insert(val);
+    }
+  }
+
+  // Constructs a new FixedBitSet from a container.
+  template <typename Container>
+  explicit FixedBitSet(const Container& c) {
+    for (const IntType& val : c) {
+      insert(val);
+    }
+  }
+
+  // Inserts 'val' into the set.
+  std::pair<iterator, bool> insert(const IntType& val) {
+    DCHECK_LT(val, MaxVals);
+    bool not_present = !contains(val);
+    if (not_present) {
+      bitset_.set(static_cast<size_t>(val));
+    }
+    return { iterator(this, static_cast<int>(val)), not_present };
+  }
+
+  // Removes 'val' from the set if it exists.
+  size_t erase(const IntType val) {
+    DCHECK_LT(val, MaxVals);
+    bool not_present = !contains(val);
+    if (not_present) {
+      return 0;
+    }
+    bitset_.set(static_cast<size_t>(val), false);
+    return 1;
+  }
+
+  // Returns whether 'val' exists in the set.
+  bool contains(const IntType& val) const {
+    DCHECK_LT(val, MaxVals);
+    return bitset_.test(val);
+  }
+
+  // Returns whether the set is empty.
+  bool empty() const {
+    return bitset_.none();
+  }
+
+  // Clears the contents of the set.
+  void clear() {
+    bitset_.reset();
+  }
+
+  // Returns the number of set bits.
+  size_t size() const {
+    return bitset_.count();
+  }
+
+  // Resets the set to have the contents of the container of int-typed values.
+  template <typename C>
+  void reset(const C& container) {
+    clear();
+    for (const IntType& item : container) {
+      insert(item);
+    }
+  }
+
+  // Forward iterator that points to the start of the values in the bitset.
+  // Points at the first set bit, or at end() if no bits are set.
+  iterator begin() const {
+    iterator iter(this, -1);
+    iter.seek_forward();
+    return iter;
+  }
+
+  // Forward iterator that points to the end of the values in the bitset.
+  iterator end() const {
+    return iterator(this, MaxVals);
+  }
+
+  // Forward iterator that points at the element 'val' if it exists, or at
+  // end() if it doesn't exist.
+  iterator find(const IntType& val) const {
+    return contains(val) ? iterator(this, val) : end();
+  }
+
+ private:
+  COMPILE_ASSERT(MaxVals < 64, bitset_size_too_large);
+  std::bitset<MaxVals> bitset_;
+};
+
+// Forward iterator class for a FixedBitSet.
+template <typename IntType, size_t MaxVals>
+class FixedBitSet<IntType, MaxVals>::iterator :
+    public std::iterator<std::forward_iterator_tag, IntType> {
+ public:
+  // Returns the value currently pointed at by this iterator.
+  IntType operator*() {
+    return static_cast<IntType>(idx_);
+  }
+
+  // Prefix increment operator. Advances the iterator to the next position and
+  // returns it.
+  iterator& operator++() {
+    seek_forward();
+    return *this;
+  }
+
+  // Postfix increment operator. Advances the iterator to the next position,
+  // returning a non-iterated iterator.
+  iterator operator++(int) {
+    iterator iter_copy = *this;
+    seek_forward();
+    return iter_copy;
+  }
+
+  // Returns whether this iterator is the same as 'other'.
+  bool operator==(const iterator& other) const {
+    return (idx_ == other.idx_) && (fbs_ == other.fbs_);
+  }
+
+  // Returns whether this iterator is not the same as 'other'.
+  bool operator!=(const iterator& other) const {
+    return !(*this == other);
+  }
+
+ private:
+  friend class FixedBitSet<IntType, MaxVals>;
+  iterator(const FixedBitSet<IntType, MaxVals>* fbs, int idx)
+      : fbs_(fbs),
+        idx_(idx) {}
+
+  // Seeks forward to the next set bit, or until at the end of the iterator.
+  void seek_forward() {
+    if (idx_ == MaxVals) {
+      return;
+    }
+    while (++idx_ < MaxVals) {
+      if (fbs_->contains(static_cast<IntType>(idx_))) {
+        break;
+      }
+    }
+  }
+
+  // The underlying FixedBitSet that this iterator is iterating over.
+  const FixedBitSet<IntType, MaxVals>* const fbs_;
+
+  // This iterator's current position.
+  int idx_;
+};
+
diff --git a/be/src/kudu/util/block_bloom_filter-test.cc b/be/src/kudu/util/block_bloom_filter-test.cc
new file mode 100644
index 0000000..a7ebe11
--- /dev/null
+++ b/be/src/kudu/util/block_bloom_filter-test.cc
@@ -0,0 +1,243 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "kudu/util/block_bloom_filter.h"
+
+#include <cmath> // IWYU pragma: keep
+#include <cstdint>
+#include <cstdlib>
+#include <iosfwd>
+#include <memory>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <gflags/gflags_declare.h>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+#include "kudu/util/status.h"
+#include "kudu/util/test_macros.h"
+#include "kudu/util/test_util.h"
+
+DECLARE_bool(disable_blockbloomfilter_avx2);
+
+using namespace std; // NOLINT(*)
+
+namespace kudu {
+
+class BlockBloomFilterTest : public KuduTest {
+ public:
+  void SetUp() override {
+    SeedRandom();
+  }
+  // Make a random uint32_t, avoiding the absent high bit and the low-entropy low bits
+  // produced by rand().
+  static uint32_t MakeRand() {
+    uint32_t result = (rand() >> 8) & 0xffff;
+    result <<= 16;
+    result |= (rand() >> 8) & 0xffff;
+    return result;
+  }
+
+  BlockBloomFilter* CreateBloomFilter(size_t log_space_bytes) {
+    FLAGS_disable_blockbloomfilter_avx2 = (MakeRand() & 0x1) == 0;
+
+    unique_ptr<BlockBloomFilter> bf(
+        new BlockBloomFilter(DefaultBlockBloomFilterBufferAllocator::GetSingleton()));
+    CHECK_OK(bf->Init(log_space_bytes));
+    bloom_filters_.emplace_back(move(bf));
+    return bloom_filters_.back().get();
+  }
+
+  void TearDown() override {
+    for (const auto& bf : bloom_filters_) {
+      bf->Close();
+    }
+  }
+
+ private:
+  vector<unique_ptr<BlockBloomFilter>> bloom_filters_;
+};
+
+// We can construct (and destruct) Bloom filters with different spaces.
+TEST_F(BlockBloomFilterTest, Constructor) {
+  for (int i = 0; i < 30; ++i) {
+    CreateBloomFilter(i);
+  }
+}
+
+TEST_F(BlockBloomFilterTest, InvalidSpace) {
+  BlockBloomFilter bf(DefaultBlockBloomFilterBufferAllocator::GetSingleton());
+  // Random number in the range [38, 64).
+  const int log_space_bytes = 38 + rand() % (64 - 38);
+  Status s = bf.Init(log_space_bytes);
+  ASSERT_TRUE(s.IsInvalidArgument());
+  ASSERT_STR_CONTAINS(s.ToString(), "Bloom filter too large");
+  bf.Close();
+}
+
+// We can Insert() hashes into a Bloom filter with different spaces.
+TEST_F(BlockBloomFilterTest, Insert) {
+  for (int i = 13; i < 17; ++i) {
+    auto* bf = CreateBloomFilter(i);
+    for (int k = 0; k < (1 << 15); ++k) {
+      bf->Insert(MakeRand());
+    }
+  }
+}
+
+// After Insert()ing something into a Bloom filter, it can be found again immediately.
+TEST_F(BlockBloomFilterTest, Find) {
+  for (int i = 13; i < 17; ++i) {
+    auto* bf = CreateBloomFilter(i);
+    for (int k = 0; k < (1 << 15); ++k) {
+      const auto to_insert = MakeRand();
+      bf->Insert(to_insert);
+      EXPECT_TRUE(bf->Find(to_insert));
+    }
+  }
+}
+
+// After Insert()ing something into a Bloom filter, it can be found again much later.
+TEST_F(BlockBloomFilterTest, CumulativeFind) {
+  for (int i = 5; i < 11; ++i) {
+    vector<uint32_t> inserted;
+    auto* bf = CreateBloomFilter(i);
+    for (int k = 0; k < (1 << 10); ++k) {
+      const uint32_t to_insert = MakeRand();
+      inserted.push_back(to_insert);
+      bf->Insert(to_insert);
+      for (int n = 0; n < inserted.size(); ++n) {
+        EXPECT_TRUE(bf->Find(inserted[n]));
+      }
+    }
+  }
+}
+
+// The empirical false positives we find when looking for random items is with a constant
+// factor of the false positive probability the Bloom filter was constructed for.
+TEST_F(BlockBloomFilterTest, FindInvalid) {
+  static const int find_limit = 1 << 20;
+  unordered_set<uint32_t> to_find;
+  while (to_find.size() < find_limit) {
+    to_find.insert(MakeRand());
+  }
+  static const int max_log_ndv = 19;
+  unordered_set<uint32_t> to_insert;
+  while (to_insert.size() < (1ULL << max_log_ndv)) {
+    const auto candidate = MakeRand();
+    if (to_find.find(candidate) == to_find.end()) {
+      to_insert.insert(candidate);
+    }
+  }
+  vector<uint32_t> shuffled_insert(to_insert.begin(), to_insert.end());
+  for (int log_ndv = 12; log_ndv < max_log_ndv; ++log_ndv) {
+    for (int log_fpp = 4; log_fpp < 15; ++log_fpp) {
+      double fpp = 1.0 / (1 << log_fpp);
+      const size_t ndv = 1 << log_ndv;
+      const int log_heap_space = BlockBloomFilter::MinLogSpace(ndv, fpp);
+      auto* bf = CreateBloomFilter(log_heap_space);
+      // Fill up a BF with exactly as much ndv as we planned for it:
+      for (size_t i = 0; i < ndv; ++i) {
+        bf->Insert(shuffled_insert[i]);
+      }
+      int found = 0;
+      // Now we sample from the set of possible hashes, looking for hits.
+      for (const auto& i : to_find) {
+        found += bf->Find(i);
+      }
+      EXPECT_LE(found, find_limit * fpp * 2)
+          << "Too many false positives with -log2(fpp) = " << log_fpp;
+      // Because the space is rounded up to a power of 2, we might actually get a lower
+      // fpp than the one passed to MinLogSpace().
+      const double expected_fpp = BlockBloomFilter::FalsePositiveProb(ndv, log_heap_space);
+      EXPECT_GE(found, find_limit * expected_fpp)
+          << "Too few false positives with -log2(fpp) = " << log_fpp;
+      EXPECT_LE(found, find_limit * expected_fpp * 16)
+          << "Too many false positives with -log2(fpp) = " << log_fpp;
+    }
+  }
+}
+
+// Test that MaxNdv() and MinLogSpace() are dual
+TEST_F(BlockBloomFilterTest, MinSpaceMaxNdv) {
+  for (int log2fpp = -2; log2fpp >= -63; --log2fpp) {
+    const double fpp = pow(2, log2fpp);
+    for (int given_log_space = 8; given_log_space < 30; ++given_log_space) {
+      const size_t derived_ndv = BlockBloomFilter::MaxNdv(given_log_space, fpp);
+      int derived_log_space = BlockBloomFilter::MinLogSpace(derived_ndv, fpp);
+
+      EXPECT_EQ(derived_log_space, given_log_space) << "fpp: " << fpp
+                                                    << " derived_ndv: " << derived_ndv;
+
+      // If we lower the fpp, we need more space; if we raise it we need less.
+      derived_log_space = BlockBloomFilter::MinLogSpace(derived_ndv, fpp / 2);
+      EXPECT_GE(derived_log_space, given_log_space) << "fpp: " << fpp
+                                                    << " derived_ndv: " << derived_ndv;
+      derived_log_space = BlockBloomFilter::MinLogSpace(derived_ndv, fpp * 2);
+      EXPECT_LE(derived_log_space, given_log_space) << "fpp: " << fpp
+                                                    << " derived_ndv: " << derived_ndv;
+    }
+    for (size_t given_ndv = 1000; given_ndv < 1000 * 1000; given_ndv *= 3) {
+      const int derived_log_space = BlockBloomFilter::MinLogSpace(given_ndv, fpp);
+      const size_t derived_ndv = BlockBloomFilter::MaxNdv(derived_log_space, fpp);
+
+      // The max ndv is close to, but larger than, then ndv we asked for
+      EXPECT_LE(given_ndv, derived_ndv) << "fpp: " << fpp
+                                        << " derived_log_space: " << derived_log_space;
+      EXPECT_GE(given_ndv * 2, derived_ndv)
+          << "fpp: " << fpp << " derived_log_space: " << derived_log_space;
+
+      // Changing the fpp changes the ndv capacity in the expected direction.
+      size_t new_derived_ndv = BlockBloomFilter::MaxNdv(derived_log_space, fpp / 2);
+      EXPECT_GE(derived_ndv, new_derived_ndv)
+          << "fpp: " << fpp << " derived_log_space: " << derived_log_space;
+      new_derived_ndv = BlockBloomFilter::MaxNdv(derived_log_space, fpp * 2);
+      EXPECT_LE(derived_ndv, new_derived_ndv)
+          << "fpp: " << fpp << " derived_log_space: " << derived_log_space;
+    }
+  }
+}
+
+TEST_F(BlockBloomFilterTest, MinSpaceEdgeCase) {
+  int min_space = BlockBloomFilter::MinLogSpace(1, 0.75);
+  EXPECT_GE(min_space, 0) << "LogSpace should always be >= 0";
+}
+
+// Check that MinLogSpace() and FalsePositiveProb() are dual
+TEST_F(BlockBloomFilterTest, MinSpaceForFpp) {
+  for (size_t ndv = 10000; ndv < 100 * 1000 * 1000; ndv *= 1.01) {
+    for (double fpp = 0.1; fpp > pow(2, -20); fpp *= 0.99) { // NOLINT: loop on double
+      // When contructing a Bloom filter, we can request a particular fpp by calling
+      // MinLogSpace().
+      const int min_log_space = BlockBloomFilter::MinLogSpace(ndv, fpp);
+      // However, at the resulting ndv and space, the expected fpp might be lower than
+      // the one that was requested.
+      double expected_fpp = BlockBloomFilter::FalsePositiveProb(ndv, min_log_space);
+      EXPECT_LE(expected_fpp, fpp);
+      // The fpp we get might be much lower than the one we asked for. However, if the
+      // space were just one size smaller, the fpp we get would be larger than the one we
+      // asked for.
+      expected_fpp = BlockBloomFilter::FalsePositiveProb(ndv, min_log_space - 1);
+      EXPECT_GE(expected_fpp, fpp);
+      // Therefore, the return value of MinLogSpace() is actually the minimum
+      // log space at which we can guarantee the requested fpp.
+    }
+  }
+}
+}  // namespace kudu
diff --git a/be/src/kudu/util/block_bloom_filter.cc b/be/src/kudu/util/block_bloom_filter.cc
new file mode 100644
index 0000000..4112f8f
--- /dev/null
+++ b/be/src/kudu/util/block_bloom_filter.cc
@@ -0,0 +1,199 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "kudu/util/block_bloom_filter.h"
+
+#include <emmintrin.h>
+#include <mm_malloc.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <ostream>
+
+#include <gflags/gflags.h>
+
+#include "kudu/gutil/singleton.h"
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/util/flag_tags.h"
+
+DEFINE_bool(disable_blockbloomfilter_avx2, false,
+            "Disable AVX2 operations in BlockBloomFilter. This flag has no effect if the target "
+            "CPU doesn't support AVX2 at run-time or BlockBloomFilter was built with a compiler "
+            "that doesn't support AVX2.");
+TAG_FLAG(disable_blockbloomfilter_avx2, hidden);
+
+namespace kudu {
+
+constexpr uint32_t BlockBloomFilter::kRehash[8] __attribute__((aligned(32)));
+const base::CPU BlockBloomFilter::kCpu = base::CPU();
+
+BlockBloomFilter::BlockBloomFilter(BlockBloomFilterBufferAllocatorIf* buffer_allocator) :
+  always_false_(true),
+  buffer_allocator_(buffer_allocator),
+  log_num_buckets_(0),
+  directory_mask_(0),
+  directory_(nullptr) {
+#ifdef USE_AVX2
+  if (has_avx2()) {
+    bucket_insert_func_ptr_ = &BlockBloomFilter::BucketInsertAVX2;
+    bucket_find_func_ptr_ = &BlockBloomFilter::BucketFindAVX2;
+  } else {
+    bucket_insert_func_ptr_ = &BlockBloomFilter::BucketInsert;
+    bucket_find_func_ptr_ = &BlockBloomFilter::BucketFind;
+  }
+#else
+  bucket_insert_func_ptr_ = &BlockBloomFilter::BucketInsert;
+  bucket_find_func_ptr_ = &BlockBloomFilter::BucketFind;
+#endif
+}
+
+BlockBloomFilter::~BlockBloomFilter() {
+  DCHECK(directory_ == nullptr) <<
+    "Close() should have been called before the object is destroyed.";
+}
+
+Status BlockBloomFilter::Init(const int log_space_bytes) {
+  // Since log_space_bytes is in bytes, we need to convert it to the number of tiny
+  // Bloom filters we will use.
+  log_num_buckets_ = std::max(1, log_space_bytes - kLogBucketByteSize);
+  // Since we use 32 bits in the arguments of Insert() and Find(), log_num_buckets_
+  // must be limited.
+  if (log_num_buckets_ > 32) {
+    return Status::InvalidArgument(
+        strings::Substitute("Bloom filter too large. log_space_bytes: $0", log_space_bytes));
+  }
+  // Don't use log_num_buckets_ if it will lead to undefined behavior by a shift
+  // that is too large.
+  directory_mask_ = (1ULL << log_num_buckets_) - 1;
+
+  const size_t alloc_size = directory_size();
+  Close(); // Ensure that any previously allocated memory for directory_ is released.
+  RETURN_NOT_OK(buffer_allocator_->AllocateBuffer(alloc_size,
+                                                  reinterpret_cast<void**>(&directory_)));
+  memset(directory_, 0, alloc_size);
+  return Status::OK();
+}
+
+void BlockBloomFilter::Close() {
+  if (directory_ != nullptr) {
+    buffer_allocator_->FreeBuffer(directory_);
+    directory_ = nullptr;
+  }
+}
+
+ATTRIBUTE_NO_SANITIZE_INTEGER
+void BlockBloomFilter::BucketInsert(const uint32_t bucket_idx, const uint32_t hash) noexcept {
+  // new_bucket will be all zeros except for eight 1-bits, one in each 32-bit word. It is
+  // 16-byte aligned so it can be read as a __m128i using aligned SIMD loads in the second
+  // part of this method.
+  uint32_t new_bucket[kBucketWords] __attribute__((aligned(16)));
+  for (int i = 0; i < kBucketWords; ++i) {
+    // Rehash 'hash' and use the top kLogBucketWordBits bits, following Dietzfelbinger.
+    new_bucket[i] = (kRehash[i] * hash) >> ((1 << kLogBucketWordBits) - kLogBucketWordBits);
+    new_bucket[i] = 1U << new_bucket[i];
+  }
+  for (int i = 0; i < 2; ++i) {
+    __m128i new_bucket_sse = _mm_load_si128(reinterpret_cast<__m128i*>(new_bucket + 4 * i));
+    __m128i* existing_bucket = reinterpret_cast<__m128i*>(
+        &DCHECK_NOTNULL(directory_)[bucket_idx][4 * i]);
+    *existing_bucket = _mm_or_si128(*existing_bucket, new_bucket_sse);
+  }
+}
+
+ATTRIBUTE_NO_SANITIZE_INTEGER
+bool BlockBloomFilter::BucketFind(
+    const uint32_t bucket_idx, const uint32_t hash) const noexcept {
+  for (int i = 0; i < kBucketWords; ++i) {
+    BucketWord hval = (kRehash[i] * hash) >> ((1 << kLogBucketWordBits) - kLogBucketWordBits);
+    hval = 1U << hval;
+    if (!(DCHECK_NOTNULL(directory_)[bucket_idx][i] & hval)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// The following three methods are derived from
+//
+// fpp = (1 - exp(-kBucketWords * ndv/space))^kBucketWords
+//
+// where space is in bits.
+size_t BlockBloomFilter::MaxNdv(const int log_space_bytes, const double fpp) {
+  DCHECK(log_space_bytes > 0 && log_space_bytes < 61);
+  DCHECK(0 < fpp && fpp < 1);
+  static const double ik = 1.0 / kBucketWords;
+  return -1 * ik * static_cast<double>(1ULL << (log_space_bytes + 3)) * log(1 - pow(fpp, ik));
+}
+
+int BlockBloomFilter::MinLogSpace(const size_t ndv, const double fpp) {
+  static const double k = kBucketWords;
+  if (0 == ndv) return 0;
+  // m is the number of bits we would need to get the fpp specified
+  const double m = -k * ndv / log(1 - pow(fpp, 1.0 / k));
+
+  // Handle case where ndv == 1 => ceil(log2(m/8)) < 0.
+  return std::max(0, static_cast<int>(ceil(log2(m / 8))));
+}
+
+double BlockBloomFilter::FalsePositiveProb(const size_t ndv, const int log_space_bytes) {
+  return pow(1 - exp((-1.0 * static_cast<double>(kBucketWords) * static_cast<double>(ndv))
+                     / static_cast<double>(1ULL << (log_space_bytes + 3))),
+             kBucketWords);
+}
+
+void BlockBloomFilter::InsertNoAvx2(const uint32_t hash) noexcept {
+  always_false_ = false;
+  const uint32_t bucket_idx = Rehash32to32(hash) & directory_mask_;
+  BucketInsert(bucket_idx, hash);
+}
+
+// To set 8 bits in an 32-byte Bloom filter, we set one bit in each 32-bit uint32_t. This
+// is a "split Bloom filter", and it has approximately the same false positive probability
+// as standard a Bloom filter; See Mitzenmacher's "Bloom Filters and Such". It also has
+// the advantage of requiring fewer random bits: log2(32) * 8 = 5 * 8 = 40 random bits for
+// a split Bloom filter, but log2(256) * 8 = 64 random bits for a standard Bloom filter.
+void BlockBloomFilter::Insert(const uint32_t hash) noexcept {
+  always_false_ = false;
+  const uint32_t bucket_idx = Rehash32to32(hash) & directory_mask_;
+  (this->*bucket_insert_func_ptr_)(bucket_idx, hash);
+}
+
+bool BlockBloomFilter::Find(const uint32_t hash) const noexcept {
+  if (always_false_) {
+    return false;
+  }
+  const uint32_t bucket_idx = Rehash32to32(hash) & directory_mask_;
+  return (this->*bucket_find_func_ptr_)(bucket_idx, hash);
+}
+
+Status DefaultBlockBloomFilterBufferAllocator::AllocateBuffer(size_t bytes, void** ptr) {
+  int ret_code = posix_memalign(ptr, CACHELINE_SIZE, bytes);
+  return ret_code == 0 ? Status::OK() :
+                         Status::RuntimeError(strings::Substitute("bad_alloc. bytes: $0", bytes));
+}
+
+void DefaultBlockBloomFilterBufferAllocator::FreeBuffer(void* ptr) {
+  free(DCHECK_NOTNULL(ptr));
+}
+
+DefaultBlockBloomFilterBufferAllocator* DefaultBlockBloomFilterBufferAllocator::GetSingleton() {
+  return Singleton<DefaultBlockBloomFilterBufferAllocator>::get();
+}
+
+} // namespace kudu
diff --git a/be/src/kudu/util/block_bloom_filter.h b/be/src/kudu/util/block_bloom_filter.h
new file mode 100644
index 0000000..2ae7594
--- /dev/null
+++ b/be/src/kudu/util/block_bloom_filter.h
@@ -0,0 +1,231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#pragma once
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+
+#include <gflags/gflags_declare.h>
+#include <glog/logging.h>
+
+#include "kudu/gutil/cpu.h"
+#include "kudu/gutil/macros.h"
+#include "kudu/gutil/port.h"
+#include "kudu/util/status.h"
+
+DECLARE_bool(disable_blockbloomfilter_avx2);
+
+namespace kudu {
+
+// Forward declaration.
+class BlockBloomFilterBufferAllocatorIf;
+
+// Space and cache efficient block based BloomFilter that takes 32-bit hash as key.
+// For a simple BloomFilter that takes arbitrary datatype as key see BloomFilter in bloom_filter.h
+//
+// A BloomFilter stores sets of items and offers a query operation indicating whether or
+// not that item is in the set.  BloomFilters use much less space than other compact data
+// structures, but they are less accurate: for a small percentage of elements, the query
+// operation incorrectly returns true even when the item is not in the set.
+//
+// When talking about Bloom filter size, rather than talking about 'size', which might be
+// ambiguous, we distinguish two different quantities:
+//
+// 1. Space: the amount of memory used
+//
+// 2. NDV: the number of unique items that have been inserted
+//
+// BlockBloomFilter is implemented using block Bloom filters from Putze et al.'s "Cache-,
+// Hash- and Space-Efficient Bloom Filters". The basic idea is to hash the item to a tiny
+// Bloom filter the size of a single cache line or smaller. This implementation sets 8
+// bits in each tiny Bloom filter. This provides a false positive rate near optimal for
+// between 5 and 15 bits per distinct value, which corresponds to false positive
+// probabilities between 0.1% (for 15 bits) and 10% (for 5 bits).
+//
+// Our tiny BloomFilters are 32 bytes to take advantage of 32-byte SIMD in newer Intel
+// machines.
+class BlockBloomFilter {
+ public:
+  explicit BlockBloomFilter(BlockBloomFilterBufferAllocatorIf* buffer_allocator);
+  ~BlockBloomFilter();
+
+  // Reset the filter state, allocate/reallocate the internal data structures.
+  // All calls to Insert() and Find() should only be done between the calls to Init() and
+  // Close().Init and Close are safe to call multiple times.
+  Status Init(int log_space_bytes);
+  void Close();
+
+  // Representation of a filter which allows all elements to pass.
+  static constexpr BlockBloomFilter* const kAlwaysTrueFilter = nullptr;
+
+  bool AlwaysFalse() const { return always_false_; }
+
+  // Adds an element to the BloomFilter. The function used to generate 'hash' need not
+  // have good uniformity, but it should have low collision probability. For instance, if
+  // the set of values is 32-bit ints, the identity function is a valid hash function for
+  // this Bloom filter, since the collision probability (the probability that two
+  // non-equal values will have the same hash value) is 0.
+  void Insert(uint32_t hash) noexcept;
+
+  // Finds an element in the BloomFilter, returning true if it is found and false (with
+  // high probability) if it is not.
+  bool Find(uint32_t hash) const noexcept;
+
+  // As more distinct items are inserted into a BloomFilter, the false positive rate
+  // rises. MaxNdv() returns the NDV (number of distinct values) at which a BloomFilter
+  // constructed with (1 << log_space_bytes) bytes of space hits false positive
+  // probability fpp.
+  static size_t MaxNdv(int log_space_bytes, double fpp);
+
+  // If we expect to fill a Bloom filter with 'ndv' different unique elements and we
+  // want a false positive probability of less than 'fpp', then this is the log (base 2)
+  // of the minimum number of bytes we need.
+  static int MinLogSpace(size_t ndv, double fpp);
+
+  // Returns the expected false positive rate for the given ndv and log_space_bytes.
+  static double FalsePositiveProb(size_t ndv, int log_space_bytes);
+
+  // Returns amount of space used, in bytes.
+  int64_t GetSpaceUsed() const { return sizeof(Bucket) * (1LL << log_num_buckets_); }
+
+  static int64_t GetExpectedMemoryUsed(uint32_t log_heap_size) {
+    DCHECK_GE(log_heap_size, kLogBucketWordBits);
+    return sizeof(Bucket) * (1LL << std::max<int>(1, log_heap_size - kLogBucketWordBits));
+  }
+
+ private:
+  // always_false_ is true when the bloom filter hasn't had any elements inserted.
+  bool always_false_;
+
+  static const base::CPU kCpu;
+
+  // The BloomFilter is divided up into Buckets and each Bucket comprises of 8 BucketWords of
+  // 4 bytes each.
+  static constexpr uint64_t kBucketWords = 8;
+  typedef uint32_t BucketWord;
+
+  // log2(number of bits in a BucketWord)
+  static constexpr int kLogBucketWordBits = 5;
+  static constexpr BucketWord kBucketWordMask = (1 << kLogBucketWordBits) - 1;
+
+  // log2(number of bytes in a bucket)
+  static constexpr int kLogBucketByteSize = 5;
+
+  static_assert((1 << kLogBucketWordBits) == std::numeric_limits<BucketWord>::digits,
+      "BucketWord must have a bit-width that is be a power of 2, like 64 for uint64_t.");
+
+  typedef BucketWord Bucket[kBucketWords];
+
+  BlockBloomFilterBufferAllocatorIf* buffer_allocator_;
+
+  // log_num_buckets_ is the log (base 2) of the number of buckets in the directory.
+  int log_num_buckets_;
+
+  // directory_mask_ is (1 << log_num_buckets_) - 1. It is precomputed for
+  // efficiency reasons.
+  uint32_t directory_mask_;
+
+  Bucket* directory_;
+
+  // Same as Insert(), but skips the CPU check and assumes that AVX is not available.
+  void InsertNoAvx2(uint32_t hash) noexcept;
+
+  // Does the actual work of Insert(). bucket_idx is the index of the bucket to insert
+  // into and 'hash' is the value passed to Insert().
+  void BucketInsert(uint32_t bucket_idx, uint32_t hash) noexcept;
+
+  bool BucketFind(uint32_t bucket_idx, uint32_t hash) const noexcept;
+
+#ifdef USE_AVX2
+  // Same as Insert(), but skips the CPU check and assumes that AVX is available.
+  void InsertAvx2(uint32_t hash) noexcept __attribute__((__target__("avx2")));
+
+  // A faster SIMD version of BucketInsert().
+  void BucketInsertAVX2(uint32_t bucket_idx, uint32_t hash) noexcept
+      __attribute__((__target__("avx2")));
+
+  // A faster SIMD version of BucketFind().
+  bool BucketFindAVX2(uint32_t bucket_idx, uint32_t hash) const noexcept
+      __attribute__((__target__("avx2")));
+#endif
+
+  // Function pointers initialized in constructor to avoid run-time cost
+  // in hot-path of Find and Insert operations.
+  decltype(&BlockBloomFilter::BucketInsert) bucket_insert_func_ptr_;
+  decltype(&BlockBloomFilter::BucketFind) bucket_find_func_ptr_;
+
+  int64_t directory_size() const {
+    return 1ULL << (log_num_buckets_ + kLogBucketByteSize);
+  }
+
+  // Detect at run-time whether CPU supports AVX2
+  static bool has_avx2() {
+    return !FLAGS_disable_blockbloomfilter_avx2 && kCpu.has_avx2();
+  }
+
+  // Some constants used in hashing. #defined for efficiency reasons.
+#define BLOOM_HASH_CONSTANTS                                             \
+  0x47b6137bU, 0x44974d91U, 0x8824ad5bU, 0xa2b7289dU, 0x705495c7U, 0x2df1424bU, \
+      0x9efc4947U, 0x5c6bfb31U
+
+  // kRehash is used as 8 odd 32-bit unsigned ints.  See Dietzfelbinger et al.'s "A
+  // reliable randomized algorithm for the closest-pair problem".
+  static constexpr uint32_t kRehash[8]
+      __attribute__((aligned(32))) = {BLOOM_HASH_CONSTANTS};
+
+  // Get 32 more bits of randomness from a 32-bit hash:
+  ATTRIBUTE_NO_SANITIZE_INTEGER
+  static inline uint32_t Rehash32to32(const uint32_t hash) {
+    // Constants generated by uuidgen(1) with the -r flag
+    static constexpr uint64_t m = 0x7850f11ec6d14889ULL;
+    static constexpr uint64_t a = 0x6773610597ca4c63ULL;
+    // This is strongly universal hashing following Dietzfelbinger's "Universal hashing
+    // and k-wise independent random variables via integer arithmetic without primes". As
+    // such, for any two distinct uint32_t's hash1 and hash2, the probability (over the
+    // randomness of the constants) that any subset of bit positions of
+    // Rehash32to32(hash1) is equal to the same subset of bit positions
+    // Rehash32to32(hash2) is minimal.
+    return (static_cast<uint64_t>(hash) * m + a) >> 32U;
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(BlockBloomFilter);
+};
+
+// Generic interface to allocate and de-allocate memory for the BlockBloomFilter.
+class BlockBloomFilterBufferAllocatorIf {
+ public:
+  virtual Status AllocateBuffer(size_t bytes, void** ptr) = 0;
+  virtual void FreeBuffer(void* ptr) = 0;
+};
+
+class DefaultBlockBloomFilterBufferAllocator : public BlockBloomFilterBufferAllocatorIf {
+ public:
+  // Required for Singleton.
+  DefaultBlockBloomFilterBufferAllocator() = default;
+
+  Status AllocateBuffer(size_t bytes, void** ptr) override;
+  void FreeBuffer(void* ptr) override;
+
+  static DefaultBlockBloomFilterBufferAllocator* GetSingleton();
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DefaultBlockBloomFilterBufferAllocator);
+};
+
+}  // namespace kudu
diff --git a/be/src/kudu/util/block_bloom_filter_avx2.cc b/be/src/kudu/util/block_bloom_filter_avx2.cc
new file mode 100644
index 0000000..e10b6cc
--- /dev/null
+++ b/be/src/kudu/util/block_bloom_filter_avx2.cc
@@ -0,0 +1,78 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+// This file is conditionally compiled if compiler supports AVX2.
+// However the tidy bot appears to compile this file regardless and does not define the USE_AVX2
+// macro raising incorrect errors.
+#if defined(CLANG_TIDY)
+#define USE_AVX2 1
+#endif
+
+#include "kudu/util/block_bloom_filter.h"
+
+#include <cstdint>
+#include <immintrin.h>
+
+#include "kudu/gutil/port.h"
+
+namespace kudu {
+
+// A static helper function for the AVX2 methods. Turns a 32-bit hash into a 256-bit Bucket
+// with 1 single 1-bit set in each 32-bit lane.
+static inline ATTRIBUTE_ALWAYS_INLINE __attribute__((__target__("avx2"))) __m256i MakeMask(
+    const uint32_t hash) {
+  const __m256i ones = _mm256_set1_epi32(1);
+  const __m256i rehash = _mm256_setr_epi32(BLOOM_HASH_CONSTANTS);
+  // Load hash into a YMM register, repeated eight times
+  __m256i hash_data = _mm256_set1_epi32(hash);
+  // Multiply-shift hashing ala Dietzfelbinger et al.: multiply 'hash' by eight different
+  // odd constants, then keep the 5 most significant bits from each product.
+  hash_data = _mm256_mullo_epi32(rehash, hash_data);
+  hash_data = _mm256_srli_epi32(hash_data, 27);
+  // Use these 5 bits to shift a single bit to a location in each 32-bit lane
+  return _mm256_sllv_epi32(ones, hash_data);
+}
+
+void BlockBloomFilter::BucketInsertAVX2(const uint32_t bucket_idx, const uint32_t hash) noexcept {
+  const __m256i mask = MakeMask(hash);
+  __m256i* const bucket = &(reinterpret_cast<__m256i*>(directory_)[bucket_idx]);
+  _mm256_store_si256(bucket, _mm256_or_si256(*bucket, mask));
+  // For SSE compatibility, unset the high bits of each YMM register so SSE instructions
+  // dont have to save them off before using XMM registers.
+  _mm256_zeroupper();
+}
+
+bool BlockBloomFilter::BucketFindAVX2(const uint32_t bucket_idx, const uint32_t hash) const
+    noexcept {
+  const __m256i mask = MakeMask(hash);
+  const __m256i bucket = reinterpret_cast<__m256i*>(directory_)[bucket_idx];
+  // We should return true if 'bucket' has a one wherever 'mask' does. _mm256_testc_si256
+  // takes the negation of its first argument and ands that with its second argument. In
+  // our case, the result is zero everywhere iff there is a one in 'bucket' wherever
+  // 'mask' is one. testc returns 1 if the result is 0 everywhere and returns 0 otherwise.
+  const bool result = _mm256_testc_si256(bucket, mask);
+  _mm256_zeroupper();
+  return result;
+}
+
+void BlockBloomFilter::InsertAvx2(const uint32_t hash) noexcept {
+  always_false_ = false;
+  const uint32_t bucket_idx = Rehash32to32(hash) & directory_mask_;
+  BucketInsertAVX2(bucket_idx, hash);
+}
+
+} // namespace kudu
diff --git a/be/src/kudu/util/cache_metrics.cc b/be/src/kudu/util/block_cache_metrics.cc
similarity index 63%
rename from be/src/kudu/util/cache_metrics.cc
rename to be/src/kudu/util/block_cache_metrics.cc
index ac2fadf..83529d7 100644
--- a/be/src/kudu/util/cache_metrics.cc
+++ b/be/src/kudu/util/block_cache_metrics.cc
@@ -15,53 +15,61 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#include "kudu/util/cache_metrics.h"
+#include "kudu/util/block_cache_metrics.h"
 
 #include "kudu/util/metrics.h"
 
 METRIC_DEFINE_counter(server, block_cache_inserts,
                       "Block Cache Inserts", kudu::MetricUnit::kBlocks,
-                      "Number of blocks inserted in the cache");
+                      "Number of blocks inserted in the cache",
+                      kudu::MetricLevel::kDebug);
 METRIC_DEFINE_counter(server, block_cache_lookups,
                       "Block Cache Lookups", kudu::MetricUnit::kBlocks,
-                      "Number of blocks looked up from the cache");
+                      "Number of blocks looked up from the cache",
+                      kudu::MetricLevel::kDebug);
 METRIC_DEFINE_counter(server, block_cache_evictions,
                       "Block Cache Evictions", kudu::MetricUnit::kBlocks,
-                      "Number of blocks evicted from the cache");
+                      "Number of blocks evicted from the cache",
+                      kudu::MetricLevel::kDebug);
 METRIC_DEFINE_counter(server, block_cache_misses,
                       "Block Cache Misses", kudu::MetricUnit::kBlocks,
-                      "Number of lookups that didn't yield a block");
+                      "Number of lookups that didn't yield a block",
+                      kudu::MetricLevel::kDebug);
 METRIC_DEFINE_counter(server, block_cache_misses_caching,
                       "Block Cache Misses (Caching)", kudu::MetricUnit::kBlocks,
                       "Number of lookups that were expecting a block that didn't yield one."
                       "Use this number instead of cache_misses when trying to determine how "
-                      "efficient the cache is");
+                      "efficient the cache is",
+                      kudu::MetricLevel::kDebug);
 METRIC_DEFINE_counter(server, block_cache_hits,
                       "Block Cache Hits", kudu::MetricUnit::kBlocks,
-                      "Number of lookups that found a block");
+                      "Number of lookups that found a block",
+                      kudu::MetricLevel::kDebug);
 METRIC_DEFINE_counter(server, block_cache_hits_caching,
                       "Block Cache Hits (Caching)", kudu::MetricUnit::kBlocks,
                       "Number of lookups that were expecting a block that found one."
                       "Use this number instead of cache_hits when trying to determine how "
-                      "efficient the cache is");
+                      "efficient the cache is",
+                      kudu::MetricLevel::kDebug);
 
 METRIC_DEFINE_gauge_uint64(server, block_cache_usage, "Block Cache Memory Usage",
                            kudu::MetricUnit::kBytes,
-                           "Memory consumed by the block cache");
+                           "Memory consumed by the block cache",
+                           kudu::MetricLevel::kInfo);
 
 namespace kudu {
 
-#define MINIT(member, x) member(METRIC_##x.Instantiate(entity))
-#define GINIT(member, x) member(METRIC_##x.Instantiate(entity, 0))
-CacheMetrics::CacheMetrics(const scoped_refptr<MetricEntity>& entity)
-  : MINIT(inserts, block_cache_inserts),
-    MINIT(lookups, block_cache_lookups),
-    MINIT(evictions, block_cache_evictions),
-    MINIT(cache_hits, block_cache_hits),
-    MINIT(cache_hits_caching, block_cache_hits_caching),
-    MINIT(cache_misses, block_cache_misses),
-    MINIT(cache_misses_caching, block_cache_misses_caching),
-    GINIT(cache_usage, block_cache_usage) {
+#define MINIT(member, x) member = METRIC_##x.Instantiate(entity)
+#define GINIT(member, x) member = METRIC_##x.Instantiate(entity, 0)
+BlockCacheMetrics::BlockCacheMetrics(const scoped_refptr<MetricEntity>& entity) {
+  MINIT(inserts, block_cache_inserts);
+  MINIT(lookups, block_cache_lookups);
+  MINIT(evictions, block_cache_evictions);
+  MINIT(cache_hits, block_cache_hits);
+  MINIT(cache_hits_caching, block_cache_hits_caching);
+  MINIT(cache_misses, block_cache_misses);
+  MINIT(cache_misses_caching, block_cache_misses_caching);
+  GINIT(cache_usage, block_cache_usage);
 }
 #undef MINIT
 #undef GINIT
diff --git a/be/src/kudu/rpc/response_callback.h b/be/src/kudu/util/block_cache_metrics.h
similarity index 76%
copy from be/src/kudu/rpc/response_callback.h
copy to be/src/kudu/util/block_cache_metrics.h
index 8c4fc03..9994ad6 100644
--- a/be/src/kudu/rpc/response_callback.h
+++ b/be/src/kudu/util/block_cache_metrics.h
@@ -15,17 +15,17 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#ifndef KUDU_RPC_RESPONSE_CALLBACK_H
-#define KUDU_RPC_RESPONSE_CALLBACK_H
+#pragma once
 
-#include <boost/function.hpp>
+#include "kudu/gutil/ref_counted.h"
+#include "kudu/util/cache_metrics.h"
 
 namespace kudu {
-namespace rpc {
 
-typedef boost::function<void()> ResponseCallback;
+class MetricEntity;
 
-}
-}
+struct BlockCacheMetrics : public CacheMetrics {
+  explicit BlockCacheMetrics(const scoped_refptr<MetricEntity>& entity);
+};
 
-#endif
+} // namespace kudu
diff --git a/be/src/kudu/util/bloom_filter.h b/be/src/kudu/util/bloom_filter.h
index ad4e3eb..30cd64c 100644
--- a/be/src/kudu/util/bloom_filter.h
+++ b/be/src/kudu/util/bloom_filter.h
@@ -25,10 +25,16 @@
 #include "kudu/gutil/macros.h"
 #include "kudu/gutil/port.h"
 #include "kudu/util/bitmap.h"
+#include "kudu/util/hash.pb.h"
+#include "kudu/util/hash_util.h"
 #include "kudu/util/slice.h"
 
 namespace kudu {
 
+// A simple BloomFilter that takes arbitrary datatype as key.
+// For a space and cache efficient block based BloomFilter that takes 32-bit hash as key see
+// BlockBloomFilter in block_bloom_filter.h
+
 // Probe calculated from a given key. This caches the calculated
 // hash values which are necessary for probing into a Bloom Filter,
 // so that when many bloom filters have to be consulted for a given
@@ -52,11 +58,22 @@ class BloomKeyProbe {
   //
   // NOTE: proper operation requires that the referenced memory remain
   // valid for the lifetime of this object.
-  explicit BloomKeyProbe(const Slice &key) : key_(key) {
-    uint64_t h = util_hash::CityHash64(
-      reinterpret_cast<const char *>(key.data()),
-      key.size());
-
+  explicit BloomKeyProbe(const Slice &key, HashAlgorithm hash_algorithm = CITY_HASH)
+      : key_(key) {
+    uint64_t h = 0;
+    switch (hash_algorithm) {
+      case MURMUR_HASH_2:
+        h = HashUtil::MurmurHash2_64(
+                reinterpret_cast<const char *>(key.data()),
+                key.size(),
+                /*seed=*/0);
+        break;
+      case CITY_HASH:
+      default:
+        h = util_hash::CityHash64(
+                reinterpret_cast<const char *>(key.data()),
+                key.size());
+    }
     // Use the top and bottom halves of the 64-bit hash
     // as the two independent hash functions for mixing.
     h_1_ = static_cast<uint32_t>(h);
diff --git a/be/src/kudu/util/cache-bench.cc b/be/src/kudu/util/cache-bench.cc
index 1e705be..91026ae 100644
--- a/be/src/kudu/util/cache-bench.cc
+++ b/be/src/kudu/util/cache-bench.cc
@@ -15,10 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#include <string.h>
-
 #include <atomic>
 #include <cstdint>
+#include <cstring>
 #include <memory>
 #include <ostream>
 #include <string>
@@ -96,8 +95,7 @@ class CacheBench : public KuduTest,
  public:
   void SetUp() override {
     KuduTest::SetUp();
-
-    cache_.reset(NewLRUCache(DRAM_CACHE, kCacheCapacity, "test-cache"));
+    cache_.reset(NewCache(kCacheCapacity, "test-cache"));
   }
 
   // Run queries against the cache until '*done' becomes true.
@@ -117,17 +115,15 @@ class CacheBench : public KuduTest,
       char key_buf[sizeof(int_key)];
       memcpy(key_buf, &int_key, sizeof(int_key));
       Slice key_slice(key_buf, arraysize(key_buf));
-      Cache::Handle* h = cache_->Lookup(key_slice, Cache::EXPECT_IN_CACHE);
+      auto h(cache_->Lookup(key_slice, Cache::EXPECT_IN_CACHE));
       if (h) {
-        hits++;
+        ++hits;
       } else {
-        Cache::PendingHandle* ph = cache_->Allocate(
-            key_slice, /* val_len=*/kEntrySize, /* charge=*/kEntrySize);
-        h = cache_->Insert(ph, nullptr);
+        auto ph(cache_->Allocate(
+            key_slice, /* val_len=*/kEntrySize, /* charge=*/kEntrySize));
+        cache_->Insert(std::move(ph), nullptr);
       }
-
-      cache_->Release(h);
-      lookups++;
+      ++lookups;
     }
     return {hits, lookups};
   }
diff --git a/be/src/kudu/util/cache-test.cc b/be/src/kudu/util/cache-test.cc
index 3fd1d5f..6ba34b2 100644
--- a/be/src/kudu/util/cache-test.cc
+++ b/be/src/kudu/util/cache-test.cc
@@ -2,36 +2,46 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <cassert>
+#include "kudu/util/cache.h"
+
 #include <cstring>
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
-#include <glog/logging.h>
 #include <gflags/gflags.h>
 #include <gflags/gflags_declare.h>
+#include <glog/logging.h>
 #include <gtest/gtest.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
-#include "kudu/gutil/port.h"
+#include "kudu/gutil/macros.h"
 #include "kudu/gutil/ref_counted.h"
-#include "kudu/util/cache.h"
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/util/block_cache_metrics.h"
+#include "kudu/util/cache_metrics.h"
 #include "kudu/util/coding.h"
 #include "kudu/util/env.h"
 #include "kudu/util/faststring.h"
 #include "kudu/util/mem_tracker.h"
 #include "kudu/util/metrics.h"
+#include "kudu/util/nvm_cache.h"
 #include "kudu/util/slice.h"
 #include "kudu/util/test_macros.h"
 #include "kudu/util/test_util.h"
 
-#if defined(__linux__)
+DECLARE_bool(cache_force_single_shard);
 DECLARE_string(nvm_cache_path);
-#endif // defined(__linux__)
 
 DECLARE_double(cache_memtracker_approximation_ratio);
 
+using std::make_tuple;
+using std::tuple;
+using std::shared_ptr;
+using std::unique_ptr;
+using std::vector;
+using strings::Substitute;
+
 namespace kudu {
 
 // Conversions between numeric keys/values and the types expected by Cache.
@@ -41,85 +51,171 @@ static std::string EncodeInt(int k) {
   return result.ToString();
 }
 static int DecodeInt(const Slice& k) {
-  assert(k.size() == 4);
+  CHECK_EQ(4, k.size());
   return DecodeFixed32(k.data());
 }
 
-class CacheTest : public KuduTest,
-                  public ::testing::WithParamInterface<CacheType>,
-                  public Cache::EvictionCallback {
+// Cache sharding policy affects the composition of the cache. Some test
+// scenarios assume cache is single-sharded to keep the logic simpler.
+enum class ShardingPolicy {
+  MultiShard,
+  SingleShard,
+};
+
+class CacheBaseTest : public KuduTest,
+                      public Cache::EvictionCallback {
  public:
+  explicit CacheBaseTest(size_t cache_size)
+      : cache_size_(cache_size) {
+  }
+
+  size_t cache_size() const {
+    return cache_size_;
+  }
 
-  // Implementation of the EvictionCallback interface
+  // Implementation of the EvictionCallback interface.
   void EvictedEntry(Slice key, Slice val) override {
     evicted_keys_.push_back(DecodeInt(key));
     evicted_values_.push_back(DecodeInt(val));
   }
-  std::vector<int> evicted_keys_;
-  std::vector<int> evicted_values_;
-  std::shared_ptr<MemTracker> mem_tracker_;
-  gscoped_ptr<Cache> cache_;
-  MetricRegistry metric_registry_;
 
-  static const int kCacheSize = 14*1024*1024;
+  int Lookup(int key) {
+    auto handle(cache_->Lookup(EncodeInt(key), Cache::EXPECT_IN_CACHE));
+    return handle ? DecodeInt(cache_->Value(handle)) : -1;
+  }
 
-  virtual void SetUp() OVERRIDE {
+  void Insert(int key, int value, int charge = 1) {
+    std::string key_str = EncodeInt(key);
+    std::string val_str = EncodeInt(value);
+    auto handle(cache_->Allocate(key_str, val_str.size(), charge));
+    CHECK(handle);
+    memcpy(cache_->MutableValue(&handle), val_str.data(), val_str.size());
+    cache_->Insert(std::move(handle), this);
+  }
 
-#if defined(HAVE_LIB_VMEM)
-    if (google::GetCommandLineFlagInfoOrDie("nvm_cache_path").is_default) {
-      FLAGS_nvm_cache_path = GetTestPath("nvm-cache");
-      ASSERT_OK(Env::Default()->CreateDir(FLAGS_nvm_cache_path));
-    }
-#endif // defined(HAVE_LIB_VMEM)
+  void Erase(int key) {
+    cache_->Erase(EncodeInt(key));
+  }
 
+ protected:
+  void SetupWithParameters(Cache::MemoryType mem_type,
+                           Cache::EvictionPolicy eviction_policy,
+                           ShardingPolicy sharding_policy) {
     // Disable approximate tracking of cache memory since we make specific
     // assertions on the MemTracker in this test.
     FLAGS_cache_memtracker_approximation_ratio = 0;
 
-    cache_.reset(NewLRUCache(GetParam(), kCacheSize, "cache_test"));
+    // Using single shard makes the logic of scenarios simple for capacity-
+    // and eviction-related behavior.
+    FLAGS_cache_force_single_shard =
+        (sharding_policy == ShardingPolicy::SingleShard);
+
+    if (google::GetCommandLineFlagInfoOrDie("nvm_cache_path").is_default) {
+      FLAGS_nvm_cache_path = GetTestPath("nvm-cache");
+      ASSERT_OK(Env::Default()->CreateDir(FLAGS_nvm_cache_path));
+    }
+
+    switch (eviction_policy) {
+      case Cache::EvictionPolicy::FIFO:
+        if (mem_type != Cache::MemoryType::DRAM) {
+          FAIL() << "FIFO cache can only be of DRAM type";
+        }
+        cache_.reset(NewCache<Cache::EvictionPolicy::FIFO,
+                              Cache::MemoryType::DRAM>(cache_size(),
+                                                       "cache_test"));
+        MemTracker::FindTracker("cache_test-sharded_fifo_cache", &mem_tracker_);
+        break;
+      case Cache::EvictionPolicy::LRU:
+        switch (mem_type) {
+          case Cache::MemoryType::DRAM:
+            cache_.reset(NewCache<Cache::EvictionPolicy::LRU,
+                                  Cache::MemoryType::DRAM>(cache_size(),
+                                                           "cache_test"));
+            break;
+          case Cache::MemoryType::NVM:
+            if (CanUseNVMCacheForTests()) {
+              cache_.reset(NewCache<Cache::EvictionPolicy::LRU,
+                                    Cache::MemoryType::NVM>(cache_size(),
+                                                            "cache_test"));
+            }
+            break;
+          default:
+            FAIL() << mem_type << ": unrecognized cache memory type";
+            break;
+        }
+        MemTracker::FindTracker("cache_test-sharded_lru_cache", &mem_tracker_);
+        break;
+      default:
+        FAIL() << "unrecognized cache eviction policy";
+        break;
+    }
 
-    MemTracker::FindTracker("cache_test-sharded_lru_cache", &mem_tracker_);
     // Since nvm cache does not have memtracker due to the use of
     // tcmalloc for this we only check for it in the DRAM case.
-    if (GetParam() == DRAM_CACHE) {
+    if (mem_type == Cache::MemoryType::DRAM) {
       ASSERT_TRUE(mem_tracker_.get());
     }
 
-    scoped_refptr<MetricEntity> entity = METRIC_ENTITY_server.Instantiate(
-        &metric_registry_, "test");
-    cache_->SetMetrics(entity);
-  }
-
-  int Lookup(int key) {
-    Cache::Handle* handle = cache_->Lookup(EncodeInt(key), Cache::EXPECT_IN_CACHE);
-    const int r = (handle == nullptr) ? -1 : DecodeInt(cache_->Value(handle));
-    if (handle != nullptr) {
-      cache_->Release(handle);
+    // cache_ will be null if we're trying to set up a test for the NVM cache
+    // and were unable to do so.
+    if (cache_) {
+      scoped_refptr<MetricEntity> entity = METRIC_ENTITY_server.Instantiate(
+          &metric_registry_, "test");
+      unique_ptr<BlockCacheMetrics> metrics(new BlockCacheMetrics(entity));
+      cache_->SetMetrics(std::move(metrics));
     }
-    return r;
   }
 
-  void Insert(int key, int value, int charge = 1) {
-    std::string key_str = EncodeInt(key);
-    std::string val_str = EncodeInt(value);
-    Cache::PendingHandle* handle = CHECK_NOTNULL(cache_->Allocate(key_str, val_str.size(), charge));
-    memcpy(cache_->MutableValue(handle), val_str.data(), val_str.size());
+  const size_t cache_size_;
+  vector<int> evicted_keys_;
+  vector<int> evicted_values_;
+  shared_ptr<MemTracker> mem_tracker_;
+  unique_ptr<Cache> cache_;
+  MetricRegistry metric_registry_;
+};
 
-    cache_->Release(cache_->Insert(handle, this));
+class CacheTest :
+    public CacheBaseTest,
+    public ::testing::WithParamInterface<tuple<Cache::MemoryType,
+                                               Cache::EvictionPolicy,
+                                               ShardingPolicy>> {
+ public:
+  CacheTest()
+      : CacheBaseTest(16 * 1024 * 1024) {
   }
 
-  void Erase(int key) {
-    cache_->Erase(EncodeInt(key));
+  void SetUp() override {
+    const auto& param = GetParam();
+    SetupWithParameters(std::get<0>(param),
+                        std::get<1>(param),
+                        std::get<2>(param));
   }
 };
 
-#if defined(__linux__)
-INSTANTIATE_TEST_CASE_P(CacheTypes, CacheTest, ::testing::Values(DRAM_CACHE, NVM_CACHE));
-#else
-INSTANTIATE_TEST_CASE_P(CacheTypes, CacheTest, ::testing::Values(DRAM_CACHE));
-#endif // defined(__linux__)
+INSTANTIATE_TEST_CASE_P(
+    CacheTypes, CacheTest,
+    ::testing::Values(
+        make_tuple(Cache::MemoryType::DRAM,
+                   Cache::EvictionPolicy::FIFO,
+                   ShardingPolicy::MultiShard),
+        make_tuple(Cache::MemoryType::DRAM,
+                   Cache::EvictionPolicy::FIFO,
+                   ShardingPolicy::SingleShard),
+        make_tuple(Cache::MemoryType::DRAM,
+                   Cache::EvictionPolicy::LRU,
+                   ShardingPolicy::MultiShard),
+        make_tuple(Cache::MemoryType::DRAM,
+                   Cache::EvictionPolicy::LRU,
+                   ShardingPolicy::SingleShard),
+        make_tuple(Cache::MemoryType::NVM,
+                   Cache::EvictionPolicy::LRU,
+                   ShardingPolicy::MultiShard),
+        make_tuple(Cache::MemoryType::NVM,
+                   Cache::EvictionPolicy::LRU,
+                   ShardingPolicy::SingleShard)));
 
 TEST_P(CacheTest, TrackMemory) {
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
   if (mem_tracker_) {
     Insert(100, 100, 1);
     ASSERT_EQ(1, mem_tracker_->consumption());
@@ -130,6 +226,7 @@ TEST_P(CacheTest, TrackMemory) {
 }
 
 TEST_P(CacheTest, HitAndMiss) {
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
   ASSERT_EQ(-1, Lookup(100));
 
   Insert(100, 101);
@@ -153,6 +250,7 @@ TEST_P(CacheTest, HitAndMiss) {
 }
 
 TEST_P(CacheTest, Erase) {
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
   Erase(200);
   ASSERT_EQ(0, evicted_keys_.size());
 
@@ -172,16 +270,17 @@ TEST_P(CacheTest, Erase) {
 }
 
 TEST_P(CacheTest, EntriesArePinned) {
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
   Insert(100, 101);
-  Cache::Handle* h1 = cache_->Lookup(EncodeInt(100), Cache::EXPECT_IN_CACHE);
+  auto h1 = cache_->Lookup(EncodeInt(100), Cache::EXPECT_IN_CACHE);
   ASSERT_EQ(101, DecodeInt(cache_->Value(h1)));
 
   Insert(100, 102);
-  Cache::Handle* h2 = cache_->Lookup(EncodeInt(100), Cache::EXPECT_IN_CACHE);
+  auto h2 = cache_->Lookup(EncodeInt(100), Cache::EXPECT_IN_CACHE);
   ASSERT_EQ(102, DecodeInt(cache_->Value(h2)));
   ASSERT_EQ(0, evicted_keys_.size());
 
-  cache_->Release(h1);
+  h1.reset();
   ASSERT_EQ(1, evicted_keys_.size());
   ASSERT_EQ(100, evicted_keys_[0]);
   ASSERT_EQ(101, evicted_values_[0]);
@@ -190,41 +289,22 @@ TEST_P(CacheTest, EntriesArePinned) {
   ASSERT_EQ(-1, Lookup(100));
   ASSERT_EQ(1, evicted_keys_.size());
 
-  cache_->Release(h2);
+  h2.reset();
   ASSERT_EQ(2, evicted_keys_.size());
   ASSERT_EQ(100, evicted_keys_[1]);
   ASSERT_EQ(102, evicted_values_[1]);
 }
 
-TEST_P(CacheTest, EvictionPolicy) {
-  Insert(100, 101);
-  Insert(200, 201);
-
-  const int kNumElems = 1000;
-  const int kSizePerElem = kCacheSize / kNumElems;
-
-  // Loop adding and looking up new entries, but repeatedly accessing key 101. This
-  // frequently-used entry should not be evicted.
-  for (int i = 0; i < kNumElems + 1000; i++) {
-    Insert(1000+i, 2000+i, kSizePerElem);
-    ASSERT_EQ(2000+i, Lookup(1000+i));
-    ASSERT_EQ(101, Lookup(100));
-  }
-  ASSERT_EQ(101, Lookup(100));
-  // Since '200' wasn't accessed in the loop above, it should have
-  // been evicted.
-  ASSERT_EQ(-1, Lookup(200));
-}
-
+// Add a bunch of light and heavy entries and then count the combined
+// size of items still in the cache, which must be approximately the
+// same as the total capacity.
 TEST_P(CacheTest, HeavyEntries) {
-  // Add a bunch of light and heavy entries and then count the combined
-  // size of items still in the cache, which must be approximately the
-  // same as the total capacity.
-  const int kLight = kCacheSize/1000;
-  const int kHeavy = kCacheSize/100;
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
+  const int kLight = cache_size() / 1000;
+  const int kHeavy = cache_size() / 100;
   int added = 0;
   int index = 0;
-  while (added < 2*kCacheSize) {
+  while (added < 2 * cache_size()) {
     const int weight = (index & 1) ? kLight : kHeavy;
     Insert(index, 1000+index, weight);
     added += weight;
@@ -240,7 +320,216 @@ TEST_P(CacheTest, HeavyEntries) {
       ASSERT_EQ(1000+i, r);
     }
   }
-  ASSERT_LE(cached_weight, kCacheSize + kCacheSize/10);
+  ASSERT_LE(cached_weight, cache_size() + cache_size() / 10);
+}
+
+TEST_P(CacheTest, InvalidateAllEntries) {
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
+  constexpr const int kEntriesNum = 1024;
+  // This scenarios assumes no evictions are done at the cache capacity.
+  ASSERT_LE(kEntriesNum, cache_size());
+
+  // Running invalidation on empty cache should yield no invalidated entries.
+  ASSERT_EQ(0, cache_->Invalidate({}));
+  for (auto i = 0; i < kEntriesNum; ++i) {
+    Insert(i, i);
+  }
+  // Remove a few entries from the cache (sparse pattern of keys).
+  constexpr const int kSparseKeys[] = {1, 100, 101, 500, 501, 512, 999, 1001};
+  for (const auto key : kSparseKeys) {
+    Erase(key);
+  }
+  ASSERT_EQ(ARRAYSIZE(kSparseKeys), evicted_keys_.size());
+
+  // All inserted entries, except for the removed one, should be invalidated.
+  ASSERT_EQ(kEntriesNum - ARRAYSIZE(kSparseKeys), cache_->Invalidate({}));
+  // In the end, no entries should be left in the cache.
+  ASSERT_EQ(kEntriesNum, evicted_keys_.size());
+}
+
+TEST_P(CacheTest, InvalidateNoEntries) {
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
+  constexpr const int kEntriesNum = 10;
+  // This scenarios assumes no evictions are done at the cache capacity.
+  ASSERT_LE(kEntriesNum, cache_size());
+
+  const Cache::ValidityFunc func = [](Slice /* key */, Slice /* value */) {
+    return true;
+  };
+  // Running invalidation on empty cache should yield no invalidated entries.
+  ASSERT_EQ(0, cache_->Invalidate({ func }));
+
+  for (auto i = 0; i < kEntriesNum; ++i) {
+    Insert(i, i);
+  }
+
+  // No entries should be invalidated since the validity function considers
+  // all entries valid.
+  ASSERT_EQ(0, cache_->Invalidate({ func }));
+  ASSERT_TRUE(evicted_keys_.empty());
+}
+
+TEST_P(CacheTest, InvalidateNoEntriesNoAdvanceIterationFunctor) {
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
+  constexpr const int kEntriesNum = 256;
+  // This scenarios assumes no evictions are done at the cache capacity.
+  ASSERT_LE(kEntriesNum, cache_size());
+
+  const Cache::InvalidationControl ctl = {
+    Cache::kInvalidateAllEntriesFunc,
+    [](size_t /* valid_entries_count */, size_t /* invalid_entries_count */) {
+      // Never advance over the item list.
+      return false;
+    }
+  };
+
+  // Running invalidation on empty cache should yield no invalidated entries.
+  ASSERT_EQ(0, cache_->Invalidate(ctl));
+
+  for (auto i = 0; i < kEntriesNum; ++i) {
+    Insert(i, i);
+  }
+
+  // No entries should be invalidated since the iteration functor doesn't
+  // advance over the list of entries, even if every entry is declared invalid.
+  ASSERT_EQ(0, cache_->Invalidate(ctl));
+  // In the end, all entries should be in the cache.
+  ASSERT_EQ(0, evicted_keys_.size());
+}
+
+TEST_P(CacheTest, InvalidateOddKeyEntries) {
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
+  constexpr const int kEntriesNum = 64;
+  // This scenarios assumes no evictions are done at the cache capacity.
+  ASSERT_LE(kEntriesNum, cache_size());
+
+  const Cache::ValidityFunc func = [](Slice key, Slice /* value */) {
+    return DecodeInt(key) % 2 == 0;
+  };
+  // Running invalidation on empty cache should yield no invalidated entries.
+  ASSERT_EQ(0, cache_->Invalidate({ func }));
+
+  for (auto i = 0; i < kEntriesNum; ++i) {
+    Insert(i, i);
+  }
+  ASSERT_EQ(kEntriesNum / 2, cache_->Invalidate({ func }));
+  ASSERT_EQ(kEntriesNum / 2, evicted_keys_.size());
+  for (auto i = 0; i < kEntriesNum; ++i) {
+    if (i % 2 == 0) {
+      ASSERT_EQ(i,  Lookup(i));
+    } else {
+      ASSERT_EQ(-1,  Lookup(i));
+    }
+  }
+}
+
+// This class is dedicated for scenarios specific for FIFOCache.
+// The scenarios use a single-shard cache for simpler logic.
+class FIFOCacheTest : public CacheBaseTest {
+ public:
+  FIFOCacheTest()
+      : CacheBaseTest(10 * 1024) {
+  }
+
+  void SetUp() override {
+    SetupWithParameters(Cache::MemoryType::DRAM,
+                        Cache::EvictionPolicy::FIFO,
+                        ShardingPolicy::SingleShard);
+  }
+};
+
+// Verify how the eviction behavior of a FIFO cache.
+TEST_F(FIFOCacheTest, EvictionPolicy) {
+  static constexpr int kNumElems = 20;
+  const int size_per_elem = cache_size() / kNumElems;
+  // First data chunk: fill the cache up to the capacity.
+  int idx = 0;
+  do {
+    Insert(idx, idx, size_per_elem);
+    // Keep looking up the very first entry: this is to make sure lookups
+    // do not affect the recency criteria of the eviction policy for FIFO cache.
+    Lookup(0);
+    ++idx;
+  } while (evicted_keys_.empty());
+  ASSERT_GT(idx, 1);
+
+  // Make sure the earliest inserted entry was evicted.
+  ASSERT_EQ(-1, Lookup(0));
+
+  // Verify that the 'empirical' capacity matches the expected capacity
+  // (it's a single-shard cache).
+  const int capacity = idx - 1;
+  ASSERT_EQ(kNumElems, capacity);
+
+  // Second data chunk: add (capacity / 2) more elements.
+  for (int i = 1; i < capacity / 2; ++i) {
+    // Earlier inserted elements should be gone one-by-one as new elements are
+    // inserted, and lookups should not affect the recency criteria of the FIFO
+    // eviction policy.
+    ASSERT_EQ(i, Lookup(i));
+    Insert(capacity + i, capacity + i, size_per_elem);
+    ASSERT_EQ(capacity + i, Lookup(capacity + i));
+    ASSERT_EQ(-1, Lookup(i));
+  }
+  ASSERT_EQ(capacity / 2, evicted_keys_.size());
+
+  // Early inserted elements from the first chunk should be evicted
+  // to accommodate the elements from the second chunk.
+  for (int i = 0; i < capacity / 2; ++i) {
+    SCOPED_TRACE(Substitute("early inserted elements: index $0", i));
+    ASSERT_EQ(-1, Lookup(i));
+  }
+  // The later inserted elements from the first chunk should be still
+  // in the cache.
+  for (int i = capacity / 2; i < capacity; ++i) {
+    SCOPED_TRACE(Substitute("late inserted elements: index $0", i));
+    ASSERT_EQ(i, Lookup(i));
+  }
+}
+
+class LRUCacheTest :
+    public CacheBaseTest,
+    public ::testing::WithParamInterface<tuple<Cache::MemoryType,
+                                               ShardingPolicy>> {
+ public:
+  LRUCacheTest()
+      : CacheBaseTest(16 * 1024 * 1024) {
+  }
+
+  void SetUp() override {
+    const auto& param = GetParam();
+    SetupWithParameters(std::get<0>(param),
+                        Cache::EvictionPolicy::LRU,
+                        std::get<1>(param));
+  }
+};
+
+INSTANTIATE_TEST_CASE_P(
+    CacheTypes, LRUCacheTest,
+    ::testing::Combine(::testing::Values(Cache::MemoryType::DRAM,
+                                         Cache::MemoryType::NVM),
+                       ::testing::Values(ShardingPolicy::MultiShard,
+                                         ShardingPolicy::SingleShard)));
+
+TEST_P(LRUCacheTest, EvictionPolicy) {
+  RETURN_IF_NO_NVM_CACHE(std::get<0>(GetParam()));
+  static constexpr int kNumElems = 1000;
+  const int size_per_elem = cache_size() / kNumElems;
+
+  Insert(100, 101);
+  Insert(200, 201);
+
+  // Loop adding and looking up new entries, but repeatedly accessing key 101.
+  // This frequently-used entry should not be evicted.
+  for (int i = 0; i < kNumElems + 1000; i++) {
+    Insert(1000+i, 2000+i, size_per_elem);
+    ASSERT_EQ(2000+i, Lookup(1000+i));
+    ASSERT_EQ(101, Lookup(100));
+  }
+  ASSERT_EQ(101, Lookup(100));
+  // Since '200' wasn't accessed in the loop above, it should have
+  // been evicted.
+  ASSERT_EQ(-1, Lookup(200));
 }
 
 }  // namespace kudu
diff --git a/be/src/kudu/util/cache.cc b/be/src/kudu/util/cache.cc
index 00f2e52..073fa51 100644
--- a/be/src/kudu/util/cache.cc
+++ b/be/src/kudu/util/cache.cc
@@ -11,13 +11,13 @@
 #include <mutex>
 #include <ostream>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 
 #include "kudu/gutil/bits.h"
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/hash/city.h"
 #include "kudu/gutil/macros.h"
 #include "kudu/gutil/port.h"
@@ -35,10 +35,6 @@
 #include "kudu/util/slice.h"
 #include "kudu/util/test_util_prod.h"
 
-#if !defined(__APPLE__)
-#include "kudu/util/nvm_cache.h"
-#endif
-
 // Useful in tests that require accurate cache capacity accounting.
 DEFINE_bool(cache_force_single_shard, false,
             "Override all cache implementations to use just one shard");
@@ -52,6 +48,7 @@ TAG_FLAG(cache_memtracker_approximation_ratio, hidden);
 using std::atomic;
 using std::shared_ptr;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 
 namespace kudu {
@@ -59,19 +56,28 @@ namespace kudu {
 Cache::~Cache() {
 }
 
-namespace {
+const Cache::ValidityFunc Cache::kInvalidateAllEntriesFunc = [](
+    Slice /* key */, Slice /* value */) {
+  return false;
+};
 
-typedef simple_spinlock MutexType;
+const Cache::IterationFunc Cache::kIterateOverAllEntriesFunc = [](
+    size_t /* valid_entries_num */, size_t /* invalid_entries_num */) {
+  return true;
+};
 
-// LRU cache implementation
+namespace {
 
-// An entry is a variable length heap-allocated structure.  Entries
-// are kept in a circular doubly linked list ordered by access time.
-struct LRUHandle {
+// Recency list cache implementations (FIFO, LRU, etc.)
+
+// Recency list handle. An entry is a variable length heap-allocated structure.
+// Entries are kept in a circular doubly linked list ordered by some recency
+// criterion (e.g., access time for LRU policy, insertion time for FIFO policy).
+struct RLHandle {
   Cache::EvictionCallback* eviction_callback;
-  LRUHandle* next_hash;
-  LRUHandle* next;
-  LRUHandle* prev;
+  RLHandle* next_hash;
+  RLHandle* next;
+  RLHandle* prev;
   size_t charge;      // TODO(opt): Only allow uint32_t?
   uint32_t key_length;
   uint32_t val_length;
@@ -92,7 +98,7 @@ struct LRUHandle {
   }
 
   const uint8_t* val_ptr() const {
-    return const_cast<LRUHandle*>(this)->mutable_val_ptr();
+    return const_cast<RLHandle*>(this)->mutable_val_ptr();
   }
 
   Slice value() const {
@@ -110,13 +116,13 @@ class HandleTable {
   HandleTable() : length_(0), elems_(0), list_(nullptr) { Resize(); }
   ~HandleTable() { delete[] list_; }
 
-  LRUHandle* Lookup(const Slice& key, uint32_t hash) {
+  RLHandle* Lookup(const Slice& key, uint32_t hash) {
     return *FindPointer(key, hash);
   }
 
-  LRUHandle* Insert(LRUHandle* h) {
-    LRUHandle** ptr = FindPointer(h->key(), h->hash);
-    LRUHandle* old = *ptr;
+  RLHandle* Insert(RLHandle* h) {
+    RLHandle** ptr = FindPointer(h->key(), h->hash);
+    RLHandle* old = *ptr;
     h->next_hash = (old == nullptr ? nullptr : old->next_hash);
     *ptr = h;
     if (old == nullptr) {
@@ -130,9 +136,9 @@ class HandleTable {
     return old;
   }
 
-  LRUHandle* Remove(const Slice& key, uint32_t hash) {
-    LRUHandle** ptr = FindPointer(key, hash);
-    LRUHandle* result = *ptr;
+  RLHandle* Remove(const Slice& key, uint32_t hash) {
+    RLHandle** ptr = FindPointer(key, hash);
+    RLHandle* result = *ptr;
     if (result != nullptr) {
       *ptr = result->next_hash;
       --elems_;
@@ -145,13 +151,13 @@ class HandleTable {
   // a linked list of cache entries that hash into the bucket.
   uint32_t length_;
   uint32_t elems_;
-  LRUHandle** list_;
+  RLHandle** list_;
 
   // Return a pointer to slot that points to a cache entry that
   // matches key/hash.  If there is no such cache entry, return a
   // pointer to the trailing slot in the corresponding linked list.
-  LRUHandle** FindPointer(const Slice& key, uint32_t hash) {
-    LRUHandle** ptr = &list_[hash & (length_ - 1)];
+  RLHandle** FindPointer(const Slice& key, uint32_t hash) {
+    RLHandle** ptr = &list_[hash & (length_ - 1)];
     while (*ptr != nullptr &&
            ((*ptr)->hash != hash || key != (*ptr)->key())) {
       ptr = &(*ptr)->next_hash;
@@ -164,15 +170,15 @@ class HandleTable {
     while (new_length < elems_ * 1.5) {
       new_length *= 2;
     }
-    auto new_list = new LRUHandle*[new_length];
+    auto new_list = new RLHandle*[new_length];
     memset(new_list, 0, sizeof(new_list[0]) * new_length);
     uint32_t count = 0;
     for (uint32_t i = 0; i < length_; i++) {
-      LRUHandle* h = list_[i];
+      RLHandle* h = list_[i];
       while (h != nullptr) {
-        LRUHandle* next = h->next_hash;
+        RLHandle* next = h->next_hash;
         uint32_t hash = h->hash;
-        LRUHandle** ptr = &new_list[hash & (new_length - 1)];
+        RLHandle** ptr = &new_list[hash & (new_length - 1)];
         h->next_hash = *ptr;
         *ptr = h;
         h = next;
@@ -186,13 +192,27 @@ class HandleTable {
   }
 };
 
+string ToString(Cache::EvictionPolicy p) {
+  switch (p) {
+    case Cache::EvictionPolicy::FIFO:
+      return "fifo";
+    case Cache::EvictionPolicy::LRU:
+      return "lru";
+    default:
+      LOG(FATAL) << "unexpected cache eviction policy: " << static_cast<int>(p);
+      break;
+  }
+  return "unknown";
+}
+
 // A single shard of sharded cache.
-class LRUCache {
+template<Cache::EvictionPolicy policy>
+class CacheShard {
  public:
-  explicit LRUCache(MemTracker* tracker);
-  ~LRUCache();
+  explicit CacheShard(MemTracker* tracker);
+  ~CacheShard();
 
-  // Separate from constructor so caller can easily make an array of LRUCache
+  // Separate from constructor so caller can easily make an array of CacheShard
   void SetCapacity(size_t capacity) {
     capacity_ = capacity;
     max_deferred_consumption_ = capacity * FLAGS_cache_memtracker_approximation_ratio;
@@ -200,20 +220,24 @@ class LRUCache {
 
   void SetMetrics(CacheMetrics* metrics) { metrics_ = metrics; }
 
-  Cache::Handle* Insert(LRUHandle* handle, Cache::EvictionCallback* eviction_callback);
+  Cache::Handle* Insert(RLHandle* handle, Cache::EvictionCallback* eviction_callback);
   // Like Cache::Lookup, but with an extra "hash" parameter.
   Cache::Handle* Lookup(const Slice& key, uint32_t hash, bool caching);
   void Release(Cache::Handle* handle);
   void Erase(const Slice& key, uint32_t hash);
+  size_t Invalidate(const Cache::InvalidationControl& ctl);
 
  private:
-  void LRU_Remove(LRUHandle* e);
-  void LRU_Append(LRUHandle* e);
+  void RL_Remove(RLHandle* e);
+  void RL_Append(RLHandle* e);
+  // Update the recency list after a lookup operation.
+  void RL_UpdateAfterLookup(RLHandle* e);
   // Just reduce the reference count by 1.
   // Return true if last reference
-  bool Unref(LRUHandle* e);
+  bool Unref(RLHandle* e);
   // Call the user's eviction callback, if it exists, and free the entry.
-  void FreeEntry(LRUHandle* e);
+  void FreeEntry(RLHandle* e);
+
 
   // Update the memtracker's consumption by the given amount.
   //
@@ -235,16 +259,19 @@ class LRUCache {
   // Positive delta indicates an increased memory consumption.
   void UpdateMemTracker(int64_t delta);
 
+  // Update the metrics for a lookup operation in the cache.
+  void UpdateMetricsLookup(bool was_hit, bool caching);
+
   // Initialized before use.
   size_t capacity_;
 
   // mutex_ protects the following state.
-  MutexType mutex_;
+  simple_spinlock mutex_;
   size_t usage_;
 
-  // Dummy head of LRU list.
-  // lru.prev is newest entry, lru.next is oldest entry.
-  LRUHandle lru_;
+  // Dummy head of recency list.
+  // rl.prev is newest entry, rl.next is oldest entry.
+  RLHandle rl_;
 
   HandleTable table_;
 
@@ -258,18 +285,20 @@ class LRUCache {
   CacheMetrics* metrics_;
 };
 
-LRUCache::LRUCache(MemTracker* tracker)
- : usage_(0),
-   mem_tracker_(tracker),
-   metrics_(nullptr) {
-  // Make empty circular linked list
-  lru_.next = &lru_;
-  lru_.prev = &lru_;
+template<Cache::EvictionPolicy policy>
+CacheShard<policy>::CacheShard(MemTracker* tracker)
+    : usage_(0),
+      mem_tracker_(tracker),
+      metrics_(nullptr) {
+  // Make empty circular linked list.
+  rl_.next = &rl_;
+  rl_.prev = &rl_;
 }
 
-LRUCache::~LRUCache() {
-  for (LRUHandle* e = lru_.next; e != &lru_; ) {
-    LRUHandle* next = e->next;
+template<Cache::EvictionPolicy policy>
+CacheShard<policy>::~CacheShard() {
+  for (RLHandle* e = rl_.next; e != &rl_; ) {
+    RLHandle* next = e->next;
     DCHECK_EQ(e->refs.load(std::memory_order_relaxed), 1)
         << "caller has an unreleased handle";
     if (Unref(e)) {
@@ -280,12 +309,14 @@ LRUCache::~LRUCache() {
   mem_tracker_->Consume(deferred_consumption_);
 }
 
-bool LRUCache::Unref(LRUHandle* e) {
+template<Cache::EvictionPolicy policy>
+bool CacheShard<policy>::Unref(RLHandle* e) {
   DCHECK_GT(e->refs.load(std::memory_order_relaxed), 0);
   return e->refs.fetch_sub(1) == 1;
 }
 
-void LRUCache::FreeEntry(LRUHandle* e) {
+template<Cache::EvictionPolicy policy>
+void CacheShard<policy>::FreeEntry(RLHandle* e) {
   DCHECK_EQ(e->refs.load(std::memory_order_relaxed), 0);
   if (e->eviction_callback) {
     e->eviction_callback->EvictedEntry(e->key(), e->value());
@@ -298,7 +329,8 @@ void LRUCache::FreeEntry(LRUHandle* e) {
   delete [] e;
 }
 
-void LRUCache::UpdateMemTracker(int64_t delta) {
+template<Cache::EvictionPolicy policy>
+void CacheShard<policy>::UpdateMemTracker(int64_t delta) {
   int64_t old_deferred = deferred_consumption_.fetch_add(delta);
   int64_t new_deferred = old_deferred + delta;
 
@@ -309,93 +341,116 @@ void LRUCache::UpdateMemTracker(int64_t delta) {
   }
 }
 
-void LRUCache::LRU_Remove(LRUHandle* e) {
+template<Cache::EvictionPolicy policy>
+void CacheShard<policy>::UpdateMetricsLookup(bool was_hit, bool caching) {
+  if (PREDICT_TRUE(metrics_)) {
+    metrics_->lookups->Increment();
+    if (was_hit) {
+      if (caching) {
+        metrics_->cache_hits_caching->Increment();
+      } else {
+        metrics_->cache_hits->Increment();
+      }
+    } else {
+      if (caching) {
+        metrics_->cache_misses_caching->Increment();
+      } else {
+        metrics_->cache_misses->Increment();
+      }
+    }
+  }
+}
+
+template<Cache::EvictionPolicy policy>
+void CacheShard<policy>::RL_Remove(RLHandle* e) {
   e->next->prev = e->prev;
   e->prev->next = e->next;
+  DCHECK_GE(usage_, e->charge);
   usage_ -= e->charge;
 }
 
-void LRUCache::LRU_Append(LRUHandle* e) {
-  // Make "e" newest entry by inserting just before lru_
-  e->next = &lru_;
-  e->prev = lru_.prev;
+template<Cache::EvictionPolicy policy>
+void CacheShard<policy>::RL_Append(RLHandle* e) {
+  // Make "e" newest entry by inserting just before rl_.
+  e->next = &rl_;
+  e->prev = rl_.prev;
   e->prev->next = e;
   e->next->prev = e;
   usage_ += e->charge;
 }
 
-Cache::Handle* LRUCache::Lookup(const Slice& key, uint32_t hash, bool caching) {
-  LRUHandle* e;
+template<>
+void CacheShard<Cache::EvictionPolicy::FIFO>::RL_UpdateAfterLookup(RLHandle* /* e */) {
+}
+
+template<>
+void CacheShard<Cache::EvictionPolicy::LRU>::RL_UpdateAfterLookup(RLHandle* e) {
+  RL_Remove(e);
+  RL_Append(e);
+}
+
+template<Cache::EvictionPolicy policy>
+Cache::Handle* CacheShard<policy>::Lookup(const Slice& key,
+                                          uint32_t hash,
+                                          bool caching) {
+  RLHandle* e;
   {
-    std::lock_guard<MutexType> l(mutex_);
+    std::lock_guard<decltype(mutex_)> l(mutex_);
     e = table_.Lookup(key, hash);
     if (e != nullptr) {
       e->refs.fetch_add(1, std::memory_order_relaxed);
-      LRU_Remove(e);
-      LRU_Append(e);
+      RL_UpdateAfterLookup(e);
     }
   }
 
   // Do the metrics outside of the lock.
-  if (metrics_) {
-    metrics_->lookups->Increment();
-    bool was_hit = (e != nullptr);
-    if (was_hit) {
-      if (caching) {
-        metrics_->cache_hits_caching->Increment();
-      } else {
-        metrics_->cache_hits->Increment();
-      }
-    } else {
-      if (caching) {
-        metrics_->cache_misses_caching->Increment();
-      } else {
-        metrics_->cache_misses->Increment();
-      }
-    }
-  }
+  UpdateMetricsLookup(e != nullptr, caching);
 
   return reinterpret_cast<Cache::Handle*>(e);
 }
 
-void LRUCache::Release(Cache::Handle* handle) {
-  LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
+template<Cache::EvictionPolicy policy>
+void CacheShard<policy>::Release(Cache::Handle* handle) {
+  RLHandle* e = reinterpret_cast<RLHandle*>(handle);
   bool last_reference = Unref(e);
   if (last_reference) {
     FreeEntry(e);
   }
 }
 
-Cache::Handle* LRUCache::Insert(LRUHandle* e, Cache::EvictionCallback *eviction_callback) {
-
-  // Set the remaining LRUHandle members which were not already allocated during
+template<Cache::EvictionPolicy policy>
+Cache::Handle* CacheShard<policy>::Insert(
+    RLHandle* handle,
+    Cache::EvictionCallback* eviction_callback) {
+  // Set the remaining RLHandle members which were not already allocated during
   // Allocate().
-  e->eviction_callback = eviction_callback;
-  e->refs.store(2, std::memory_order_relaxed);  // One from LRUCache, one for the returned handle
-  UpdateMemTracker(e->charge);
+  handle->eviction_callback = eviction_callback;
+  // Two refs for the handle: one from CacheShard, one for the returned handle.
+  handle->refs.store(2, std::memory_order_relaxed);
+  UpdateMemTracker(handle->charge);
   if (PREDICT_TRUE(metrics_)) {
-    metrics_->cache_usage->IncrementBy(e->charge);
+    metrics_->cache_usage->IncrementBy(handle->charge);
     metrics_->inserts->Increment();
   }
 
-  LRUHandle* to_remove_head = nullptr;
+  RLHandle* to_remove_head = nullptr;
   {
-    std::lock_guard<MutexType> l(mutex_);
+    std::lock_guard<decltype(mutex_)> l(mutex_);
 
-    LRU_Append(e);
+    RL_Append(handle);
 
-    LRUHandle* old = table_.Insert(e);
+    RLHandle* old = table_.Insert(handle);
     if (old != nullptr) {
-      LRU_Remove(old);
+      RL_Remove(old);
       if (Unref(old)) {
         old->next = to_remove_head;
         to_remove_head = old;
       }
     }
 
-    while (usage_ > capacity_ && lru_.next != &lru_) {
-      LRUHandle* old = lru_.next;
-      LRU_Remove(old);
+    while (usage_ > capacity_ && rl_.next != &rl_) {
+      RLHandle* old = rl_.next;
+      RL_Remove(old);
       table_.Remove(old->key(), old->hash);
       if (Unref(old)) {
         old->next = to_remove_head;
@@ -407,22 +462,23 @@ Cache::Handle* LRUCache::Insert(LRUHandle* e, Cache::EvictionCallback *eviction_
   // we free the entries here outside of mutex for
   // performance reasons
   while (to_remove_head != nullptr) {
-    LRUHandle* next = to_remove_head->next;
+    RLHandle* next = to_remove_head->next;
     FreeEntry(to_remove_head);
     to_remove_head = next;
   }
 
-  return reinterpret_cast<Cache::Handle*>(e);
+  return reinterpret_cast<Cache::Handle*>(handle);
 }
 
-void LRUCache::Erase(const Slice& key, uint32_t hash) {
-  LRUHandle* e;
+template<Cache::EvictionPolicy policy>
+void CacheShard<policy>::Erase(const Slice& key, uint32_t hash) {
+  RLHandle* e;
   bool last_reference = false;
   {
-    std::lock_guard<MutexType> l(mutex_);
+    std::lock_guard<decltype(mutex_)> l(mutex_);
     e = table_.Remove(key, hash);
     if (e != nullptr) {
-      LRU_Remove(e);
+      RL_Remove(e);
       last_reference = Unref(e);
     }
   }
@@ -433,140 +489,223 @@ void LRUCache::Erase(const Slice& key, uint32_t hash) {
   }
 }
 
+template<Cache::EvictionPolicy policy>
+size_t CacheShard<policy>::Invalidate(const Cache::InvalidationControl& ctl) {
+  size_t invalid_entry_count = 0;
+  size_t valid_entry_count = 0;
+  RLHandle* to_remove_head = nullptr;
+
+  {
+    std::lock_guard<decltype(mutex_)> l(mutex_);
+
+    // rl_.next is the oldest (a.k.a. least relevant) entry in the recency list.
+    RLHandle* h = rl_.next;
+    while (h != nullptr && h != &rl_ &&
+           ctl.iteration_func(valid_entry_count, invalid_entry_count)) {
+      if (ctl.validity_func(h->key(), h->value())) {
+        // Continue iterating over the list.
+        h = h->next;
+        ++valid_entry_count;
+        continue;
+      }
+      // Copy the handle slated for removal.
+      RLHandle* h_to_remove = h;
+      // Prepare for next iteration of the cycle.
+      h = h->next;
+
+      RL_Remove(h_to_remove);
+      table_.Remove(h_to_remove->key(), h_to_remove->hash);
+      if (Unref(h_to_remove)) {
+        h_to_remove->next = to_remove_head;
+        to_remove_head = h_to_remove;
+      }
+      ++invalid_entry_count;
+    }
+  }
+  // Once removed from the lookup table and the recency list, the entries
+  // with no references left must be deallocated because Cache::Release()
+  // wont be called for them from elsewhere.
+  while (to_remove_head != nullptr) {
+    RLHandle* next = to_remove_head->next;
+    FreeEntry(to_remove_head);
+    to_remove_head = next;
+  }
+  return invalid_entry_count;
+}
+
 // Determine the number of bits of the hash that should be used to determine
 // the cache shard. This, in turn, determines the number of shards.
 int DetermineShardBits() {
   int bits = PREDICT_FALSE(FLAGS_cache_force_single_shard) ?
       0 : Bits::Log2Ceiling(base::NumCPUs());
-  VLOG(1) << "Will use " << (1 << bits) << " shards for LRU cache.";
+  VLOG(1) << "Will use " << (1 << bits) << " shards for recency list cache.";
   return bits;
 }
 
-class ShardedLRUCache : public Cache {
- private:
-  shared_ptr<MemTracker> mem_tracker_;
-  gscoped_ptr<CacheMetrics> metrics_;
-  vector<LRUCache*> shards_;
-
-  // Number of bits of hash used to determine the shard.
-  const int shard_bits_;
-
-  // Protects 'metrics_'. Used only when metrics are set, to ensure
-  // that they are set only once in test environments.
-  MutexType metrics_lock_;
-
-  static inline uint32_t HashSlice(const Slice& s) {
-    return util_hash::CityHash64(
-      reinterpret_cast<const char *>(s.data()), s.size());
-  }
-
-  uint32_t Shard(uint32_t hash) {
-    // Widen to uint64 before shifting, or else on a single CPU,
-    // we would try to shift a uint32_t by 32 bits, which is undefined.
-    return static_cast<uint64_t>(hash) >> (32 - shard_bits_);
-  }
-
+template<Cache::EvictionPolicy policy>
+class ShardedCache : public Cache {
  public:
-  explicit ShardedLRUCache(size_t capacity, const string& id)
-      : shard_bits_(DetermineShardBits()) {
+  explicit ShardedCache(size_t capacity, const string& id)
+        : shard_bits_(DetermineShardBits()) {
     // A cache is often a singleton, so:
     // 1. We reuse its MemTracker if one already exists, and
     // 2. It is directly parented to the root MemTracker.
     mem_tracker_ = MemTracker::FindOrCreateGlobalTracker(
-        -1, strings::Substitute("$0-sharded_lru_cache", id));
+        -1, strings::Substitute("$0-sharded_$1_cache", id, ToString(policy)));
 
     int num_shards = 1 << shard_bits_;
     const size_t per_shard = (capacity + (num_shards - 1)) / num_shards;
     for (int s = 0; s < num_shards; s++) {
-      gscoped_ptr<LRUCache> shard(new LRUCache(mem_tracker_.get()));
+      unique_ptr<CacheShard<policy>> shard(
+          new CacheShard<policy>(mem_tracker_.get()));
       shard->SetCapacity(per_shard);
       shards_.push_back(shard.release());
     }
   }
 
-  virtual ~ShardedLRUCache() {
+  virtual ~ShardedCache() {
     STLDeleteElements(&shards_);
   }
 
-  virtual Handle* Insert(PendingHandle* handle,
-                         Cache::EvictionCallback* eviction_callback) OVERRIDE {
-    LRUHandle* h = reinterpret_cast<LRUHandle*>(DCHECK_NOTNULL(handle));
-    return shards_[Shard(h->hash)]->Insert(h, eviction_callback);
-  }
-  virtual Handle* Lookup(const Slice& key, CacheBehavior caching) OVERRIDE {
-    const uint32_t hash = HashSlice(key);
-    return shards_[Shard(hash)]->Lookup(key, hash, caching == EXPECT_IN_CACHE);
-  }
-  virtual void Release(Handle* handle) OVERRIDE {
-    LRUHandle* h = reinterpret_cast<LRUHandle*>(handle);
-    shards_[Shard(h->hash)]->Release(handle);
-  }
-  virtual void Erase(const Slice& key) OVERRIDE {
-    const uint32_t hash = HashSlice(key);
-    shards_[Shard(hash)]->Erase(key, hash);
-  }
-  virtual Slice Value(Handle* handle) OVERRIDE {
-    return reinterpret_cast<LRUHandle*>(handle)->value();
-  }
-  virtual void SetMetrics(const scoped_refptr<MetricEntity>& entity) OVERRIDE {
+  void SetMetrics(std::unique_ptr<CacheMetrics> metrics) override {
     // TODO(KUDU-2165): reuse of the Cache singleton across multiple MiniCluster servers
     // causes TSAN errors. So, we'll ensure that metrics only get attached once, from
     // whichever server starts first. This has the downside that, in test builds, we won't
     // get accurate cache metrics, but that's probably better than spurious failures.
-    std::lock_guard<simple_spinlock> l(metrics_lock_);
+    std::lock_guard<decltype(metrics_lock_)> l(metrics_lock_);
     if (metrics_) {
       CHECK(IsGTest()) << "Metrics should only be set once per Cache singleton";
       return;
     }
-    metrics_.reset(new CacheMetrics(entity));
-    for (LRUCache* cache : shards_) {
+    metrics_ = std::move(metrics);
+    for (auto* cache : shards_) {
       cache->SetMetrics(metrics_.get());
     }
   }
 
-  virtual PendingHandle* Allocate(Slice key, int val_len, int charge) OVERRIDE {
+  UniqueHandle Lookup(const Slice& key, CacheBehavior caching) override {
+    const uint32_t hash = HashSlice(key);
+    return UniqueHandle(
+        shards_[Shard(hash)]->Lookup(key, hash, caching == EXPECT_IN_CACHE),
+        Cache::HandleDeleter(this));
+  }
+
+  void Erase(const Slice& key) override {
+    const uint32_t hash = HashSlice(key);
+    shards_[Shard(hash)]->Erase(key, hash);
+  }
+
+  Slice Value(const UniqueHandle& handle) const override {
+    return reinterpret_cast<const RLHandle*>(handle.get())->value();
+  }
+
+  UniqueHandle Insert(UniquePendingHandle handle,
+                      Cache::EvictionCallback* eviction_callback) override {
+    RLHandle* h = reinterpret_cast<RLHandle*>(DCHECK_NOTNULL(handle.release()));
+    return UniqueHandle(
+        shards_[Shard(h->hash)]->Insert(h, eviction_callback),
+        Cache::HandleDeleter(this));
+  }
+
+  UniquePendingHandle Allocate(Slice key, int val_len, int charge) override {
     int key_len = key.size();
     DCHECK_GE(key_len, 0);
     DCHECK_GE(val_len, 0);
     int key_len_padded = KUDU_ALIGN_UP(key_len, sizeof(void*));
-    uint8_t* buf = new uint8_t[sizeof(LRUHandle)
-                               + key_len_padded + val_len // the kv_data VLA data
-                               - 1 // (the VLA has a 1-byte placeholder)
-                               ];
-    LRUHandle* handle = reinterpret_cast<LRUHandle*>(buf);
+    UniquePendingHandle h(reinterpret_cast<PendingHandle*>(
+        new uint8_t[sizeof(RLHandle)
+                    + key_len_padded + val_len // the kv_data VLA data
+                    - 1 // (the VLA has a 1-byte placeholder)
+                   ]),
+        PendingHandleDeleter(this));
+    RLHandle* handle = reinterpret_cast<RLHandle*>(h.get());
     handle->key_length = key_len;
     handle->val_length = val_len;
-    handle->charge = (charge == kAutomaticCharge) ? kudu_malloc_usable_size(buf) : charge;
+    // TODO(KUDU-1091): account for the footprint of structures used by Cache's
+    //                  internal housekeeping (RL handles, etc.) in case of
+    //                  non-automatic charge.
+    handle->charge = (charge == kAutomaticCharge) ? kudu_malloc_usable_size(h.get())
+                                                  : charge;
     handle->hash = HashSlice(key);
     memcpy(handle->kv_data, key.data(), key_len);
 
-    return reinterpret_cast<PendingHandle*>(handle);
+    return h;
+  }
+
+  uint8_t* MutableValue(UniquePendingHandle* handle) override {
+    return reinterpret_cast<RLHandle*>(handle->get())->mutable_val_ptr();
+  }
+
+  size_t Invalidate(const InvalidationControl& ctl) override {
+    size_t invalidated_count = 0;
+    for (auto& shard: shards_) {
+      invalidated_count += shard->Invalidate(ctl);
+    }
+    return invalidated_count;
   }
 
-  virtual void Free(PendingHandle* h) OVERRIDE {
+ protected:
+  void Release(Handle* handle) override {
+    RLHandle* h = reinterpret_cast<RLHandle*>(handle);
+    shards_[Shard(h->hash)]->Release(handle);
+  }
+
+  void Free(PendingHandle* h) override {
     uint8_t* data = reinterpret_cast<uint8_t*>(h);
     delete [] data;
   }
 
-  virtual uint8_t* MutableValue(PendingHandle* h) OVERRIDE {
-    return reinterpret_cast<LRUHandle*>(h)->mutable_val_ptr();
+ private:
+  static inline uint32_t HashSlice(const Slice& s) {
+    return util_hash::CityHash64(
+      reinterpret_cast<const char *>(s.data()), s.size());
   }
 
+  uint32_t Shard(uint32_t hash) {
+    // Widen to uint64 before shifting, or else on a single CPU,
+    // we would try to shift a uint32_t by 32 bits, which is undefined.
+    return static_cast<uint64_t>(hash) >> (32 - shard_bits_);
+  }
+
+  shared_ptr<MemTracker> mem_tracker_;
+  unique_ptr<CacheMetrics> metrics_;
+  vector<CacheShard<policy>*> shards_;
+
+  // Number of bits of hash used to determine the shard.
+  const int shard_bits_;
+
+  // Protects 'metrics_'. Used only when metrics are set, to ensure
+  // that they are set only once in test environments.
+  simple_spinlock metrics_lock_;
 };
 
 }  // end anonymous namespace
 
-Cache* NewLRUCache(CacheType type, size_t capacity, const string& id) {
-  switch (type) {
-    case DRAM_CACHE:
-      return new ShardedLRUCache(capacity, id);
-#if defined(HAVE_LIB_VMEM)
-    case NVM_CACHE:
-      return NewLRUNvmCache(capacity, id);
-#endif
+template<>
+Cache* NewCache<Cache::EvictionPolicy::FIFO,
+                Cache::MemoryType::DRAM>(size_t capacity, const std::string& id) {
+  return new ShardedCache<Cache::EvictionPolicy::FIFO>(capacity, id);
+}
+
+template<>
+Cache* NewCache<Cache::EvictionPolicy::LRU,
+                Cache::MemoryType::DRAM>(size_t capacity, const std::string& id) {
+  return new ShardedCache<Cache::EvictionPolicy::LRU>(capacity, id);
+}
+
+std::ostream& operator<<(std::ostream& os, Cache::MemoryType mem_type) {
+  switch (mem_type) {
+    case Cache::MemoryType::DRAM:
+      os << "DRAM";
+      break;
+    case Cache::MemoryType::NVM:
+      os << "NVM";
+      break;
     default:
-      LOG(FATAL) << "Unsupported LRU cache type: " << type;
+      os << "unknown (" << static_cast<int>(mem_type) << ")";
+      break;
   }
+  return os;
 }
 
 }  // namespace kudu
diff --git a/be/src/kudu/util/cache.h b/be/src/kudu/util/cache.h
index 82ef8c9..ff3a309 100644
--- a/be/src/kudu/util/cache.h
+++ b/be/src/kudu/util/cache.h
@@ -12,75 +12,66 @@
 //
 // This is taken from LevelDB and evolved to fit the kudu codebase.
 //
-// TODO: this is pretty lock-heavy. Would be good to sub out something
+// TODO(unknown): this is pretty lock-heavy. Would be good to sub out something
 // a little more concurrent.
 
-#ifndef KUDU_UTIL_CACHE_H_
-#define KUDU_UTIL_CACHE_H_
+#pragma once
 
 #include <cstddef>
 #include <cstdint>
+#include <functional>
+#include <iosfwd>
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "kudu/gutil/macros.h"
-#include "kudu/gutil/ref_counted.h"
 #include "kudu/util/slice.h"
 
 namespace kudu {
 
-class Cache;
-class MetricEntity;
-
-enum CacheType {
-  DRAM_CACHE,
-  NVM_CACHE
-};
-
-// Create a new cache with a fixed size capacity.  This implementation
-// of Cache uses a least-recently-used eviction policy.
-Cache* NewLRUCache(CacheType type, size_t capacity, const std::string& id);
+struct CacheMetrics;
 
 class Cache {
  public:
+  // Type of memory backing the cache's storage.
+  enum class MemoryType {
+    DRAM,
+    NVM,
+  };
+
+  // Supported eviction policies for the cache. Eviction policy determines what
+  // items to evict if the cache is at capacity when trying to accommodate
+  // an extra item.
+  enum class EvictionPolicy {
+    // The earliest added items are evicted (a.k.a. queue).
+    FIFO,
+
+    // The least-recently-used items are evicted.
+    LRU,
+  };
+
   // Callback interface which is called when an entry is evicted from the
   // cache.
   class EvictionCallback {
    public:
     virtual void EvictedEntry(Slice key, Slice value) = 0;
-    virtual ~EvictionCallback() {}
+    virtual ~EvictionCallback() = default;
   };
 
-  Cache() { }
+  Cache() = default;
 
   // Destroys all existing entries by calling the "deleter"
   // function that was passed to the constructor.
   virtual ~Cache();
 
+  // Set the cache metrics to update corresponding counters accordingly.
+  virtual void SetMetrics(std::unique_ptr<CacheMetrics> metrics) = 0;
+
   // Opaque handle to an entry stored in the cache.
   struct Handle { };
 
-  // Custom handle "deleter", primarily intended for use with std::unique_ptr.
-  //
-  // Sample usage:
-  //
-  //   Cache* cache = NewLRUCache(...);
-  //   ...
-  //   {
-  //     unique_ptr<Cache::Handle, Cache::HandleDeleter> h(
-  //       cache->Lookup(...), Cache::HandleDeleter(cache));
-  //     ...
-  //   } // 'h' is automatically released here
-  //
-  // Or:
-  //
-  //   Cache* cache = NewLRUCache(...);
-  //   ...
-  //   {
-  //     Cache::UniqueHandle h(cache->Lookup(...), Cache::HandleDeleter(cache));
-  //     ...
-  //   } // 'h' is automatically released here
-  //
+  // Custom deleter: intended for use with std::unique_ptr<Handle>.
   class HandleDeleter {
    public:
     explicit HandleDeleter(Cache* c)
@@ -88,14 +79,51 @@ class Cache {
     }
 
     void operator()(Cache::Handle* h) const {
-      c_->Release(h);
+      if (h != nullptr) {
+        c_->Release(h);
+      }
+    }
+
+    Cache* cache() const {
+      return c_;
     }
 
    private:
     Cache* c_;
   };
+
+  // UniqueHandle -- a wrapper around opaque Handle structure to facilitate
+  // automatic reference counting of cache's handles.
   typedef std::unique_ptr<Handle, HandleDeleter> UniqueHandle;
 
+  // Opaque handle to an entry which is being prepared to be added to the cache.
+  struct PendingHandle { };
+
+  // Custom deleter: intended for use with std::unique_ptr<PendingHandle>.
+  class PendingHandleDeleter {
+   public:
+    explicit PendingHandleDeleter(Cache* c)
+        : c_(c) {
+    }
+
+    void operator()(Cache::PendingHandle* h) const {
+      if (h != nullptr) {
+        c_->Free(h);
+      }
+    }
+
+    Cache* cache() const {
+      return c_;
+    }
+
+   private:
+    Cache* c_;
+  };
+
+  // UniquePendingHandle -- a wrapper around opaque PendingHandle structure
+  // to facilitate automatic reference counting newly allocated cache's handles.
+  typedef std::unique_ptr<PendingHandle, PendingHandleDeleter> UniquePendingHandle;
+
   // Passing EXPECT_IN_CACHE will increment the hit/miss metrics that track the number of times
   // blocks were requested that the users were hoping to get the block from the cache, along with
   // with the basic metrics.
@@ -108,29 +136,37 @@ class Cache {
 
   // If the cache has no mapping for "key", returns NULL.
   //
-  // Else return a handle that corresponds to the mapping.  The caller
-  // must call this->Release(handle) when the returned mapping is no
-  // longer needed.
-  virtual Handle* Lookup(const Slice& key, CacheBehavior caching) = 0;
-
-  // Release a mapping returned by a previous Lookup().
-  // REQUIRES: handle must not have been released yet.
-  // REQUIRES: handle must have been returned by a method on *this.
-  virtual void Release(Handle* handle) = 0;
-
-  // Return the value encapsulated in a handle returned by a
-  // successful Lookup().
-  // REQUIRES: handle must not have been released yet.
-  // REQUIRES: handle must have been returned by a method on *this.
-  virtual Slice Value(Handle* handle) = 0;
+  // Else return a handle that corresponds to the mapping.
+  //
+  // Sample usage:
+  //
+  //   unique_ptr<Cache> cache(NewCache(...));
+  //   ...
+  //   {
+  //     Cache::UniqueHandle h(cache->Lookup(...)));
+  //     ...
+  //   } // 'h' is automatically released here
+  //
+  // Or:
+  //
+  //   unique_ptr<Cache> cache(NewCache(...));
+  //   ...
+  //   {
+  //     auto h(cache->Lookup(...)));
+  //     ...
+  //   } // 'h' is automatically released here
+  //
+  virtual UniqueHandle Lookup(const Slice& key, CacheBehavior caching) = 0;
 
   // If the cache contains entry for key, erase it.  Note that the
   // underlying entry will be kept around until all existing handles
   // to it have been released.
   virtual void Erase(const Slice& key) = 0;
 
-  // Pass a metric entity in order to start recoding metrics.
-  virtual void SetMetrics(const scoped_refptr<MetricEntity>& metric_entity) = 0;
+  // Return the value encapsulated in a raw handle returned by a successful
+  // Lookup().
+  virtual Slice Value(const UniqueHandle& handle) const = 0;
+
 
   // ------------------------------------------------------------
   // Insertion path
@@ -143,19 +179,14 @@ class Cache {
   //
   // For example:
   //
-  //   PendingHandle* ph = cache_->Allocate("my entry", value_size, charge);
-  //   if (!ReadDataFromDisk(cache_->MutableValue(ph)).ok()) {
-  //     cache_->Free(ph);
+  //   auto ph(cache_->Allocate("my entry", value_size, charge));
+  //   if (!ReadDataFromDisk(cache_->MutableValue(&ph)).ok()) {
   //     ... error handling ...
   //     return;
   //   }
-  //   Handle* h = cache_->Insert(ph, my_eviction_callback);
+  //   UniqueHandle h(cache_->Insert(std::move(ph), my_eviction_callback));
   //   ...
-  //   cache_->Release(h);
-
-  // Opaque handle to an entry which is being prepared to be added to
-  // the cache.
-  struct PendingHandle { };
+  //   // 'h' is automatically released.
 
   // Indicates that the charge of an item in the cache should be calculated
   // based on its memory consumption.
@@ -165,44 +196,139 @@ class Cache {
   //
   // The provided 'key' is copied into the resulting handle object.
   // The allocated handle has enough space such that the value can
-  // be written into cache_->MutableValue(handle).
+  // be written into cache_->MutableValue(&handle).
   //
   // If 'charge' is not 'kAutomaticCharge', then the cache capacity will be charged
   // the explicit amount. This is useful when caching items that are small but need to
   // maintain a bounded count (eg file descriptors) rather than caring about their actual
-  // memory usage.
+  // memory usage. It is also useful when caching items for whom calculating
+  // memory usage is a complex affair (i.e. items containing pointers to
+  // additional heap allocations).
   //
   // Note that this does not mutate the cache itself: lookups will
   // not be able to find the provided key until it is inserted.
   //
-  // It is possible that this will return NULL if the cache is above its capacity
-  // and eviction fails to free up enough space for the requested allocation.
+  // It is possible that this will return a nullptr wrapped in a std::unique_ptr
+  // if the cache is above its capacity and eviction fails to free up enough
+  // space for the requested allocation.
   //
-  // NOTE: the returned memory is not automatically freed by the cache: the
-  // caller must either free it using Free(), or insert it using Insert().
-  virtual PendingHandle* Allocate(Slice key, int val_len, int charge) = 0;
+  // The returned handle owns the allocated memory.
+  virtual UniquePendingHandle Allocate(Slice key, int val_len, int charge) = 0;
 
-  // Default 'charge' should be kAutomaticCharge.
-  // (default arguments on virtual functions are prohibited)
-  PendingHandle* Allocate(Slice key, int val_len) {
+  // Default 'charge' should be kAutomaticCharge
+  // (default arguments on virtual functions are prohibited).
+  UniquePendingHandle Allocate(Slice key, int val_len) {
     return Allocate(key, val_len, kAutomaticCharge);
   }
 
-  virtual uint8_t* MutableValue(PendingHandle* handle) = 0;
+  virtual uint8_t* MutableValue(UniquePendingHandle* handle) = 0;
 
   // Commit a prepared entry into the cache.
   //
-  // Returns a handle that corresponds to the mapping.  The caller
-  // must call this->Release(handle) when the returned mapping is no
-  // longer needed. This method always succeeds and returns a non-null
-  // entry, since the space was reserved above.
+  // Returns a handle that corresponds to the mapping. This method always
+  // succeeds and returns a non-null entry, since the space was reserved above.
   //
   // The 'pending' entry passed here should have been allocated using
   // Cache::Allocate() above.
   //
   // If 'eviction_callback' is non-NULL, then it will be called when the
   // entry is later evicted or when the cache shuts down.
-  virtual Handle* Insert(PendingHandle* pending, EvictionCallback* eviction_callback) = 0;
+  virtual UniqueHandle Insert(UniquePendingHandle pending,
+                              EvictionCallback* eviction_callback) = 0;
+
+  // Forward declaration to simplify the layout of types/typedefs needed for the
+  // Invalidate() method while trying to adhere to the code style guide.
+  struct InvalidationControl;
+
+  // Invalidate cache's entries, effectively evicting non-valid ones from the
+  // cache. The invalidation process iterates over the cache's recency list(s),
+  // from best candidate for eviction to the worst.
+  //
+  // The provided control structure 'ctl' is responsible for the following:
+  //   * determine whether an entry is valid or not
+  //   * determine how to iterate over the entries in the cache's recency list
+  //
+  // NOTE: The invalidation process might hold a lock while iterating over
+  //       the cache's entries. Using proper IterationFunc might help to reduce
+  //       contention with the concurrent request for the cache's contents.
+  //       See the in-line documentation for IterationFunc for more details.
+  virtual size_t Invalidate(const InvalidationControl& ctl) = 0;
+
+  // Functor to define a criterion on a cache entry's validity. Upon call
+  // of Cache::Invalidate() method, if the functor returns 'false' for the
+  // specified key and value, the cache evicts the entry, otherwise the entry
+  // stays in the cache.
+  typedef std::function<bool(Slice /* key */,
+                             Slice /* value */)>
+      ValidityFunc;
+
+  // Functor to define whether to continue or stop iterating over the cache's
+  // entries based on the number of encountered invalid and valid entries
+  // during the Cache::Invalidate() call. If a cache contains multiple
+  // sub-caches (e.g., shards), those parameters are per sub-cache. For example,
+  // in case of multi-shard cache, when the 'iteration_func' returns 'false',
+  // the invalidation at current shard stops and switches to the next
+  // non-yet-processed shard, if any is present.
+  //
+  // The choice of the signature for the iteration functor is to allow for
+  // effective purging of non-valid (e.g., expired) entries in caches with
+  // the FIFO eviction policy (e.g., TTL caches).
+  //
+  // The first parameter of the functor is useful for short-circuiting
+  // the invalidation process once some valid entries have been encountered.
+  // For example, that's useful in case if the recency list has its entries
+  // ordered in FIFO-like order (e.g., TTL cache with FIFO eviction policy),
+  // so most-likely-invalid entries are in the very beginning of the list.
+  // In the latter case, once a valid (e.g., not yet expired) entry is
+  // encountered, there is no need to iterate any further: all the entries past
+  // the first valid one in the recency list should be valid as well.
+  //
+  // The second parameter is useful when the validity criterion is fuzzy,
+  // but there is a target number of entries to invalidate during each
+  // invocation of the Invalidate() method or there is some logic that reads
+  // the cache's metric(s) once the given number of entries have been evicted:
+  // e.g., compare the result memory footprint of the cache against a threshold
+  // to decide whether to continue invalidation of entries.
+  //
+  // Summing both parameters of the functor is useful when it's necessary to
+  // limit the number of entries processed per one invocation of the
+  // Invalidate() method. It makes sense in cases when a 'lazy' invalidation
+  // process is run by a periodic task along with a significant amount of
+  // concurrent requests to the cache, and the number of entries in the cache
+  // is huge. Given the fact that in most cases it's necessary to guard
+  // the access to the cache's recency list while iterating over it entries,
+  // limiting the number of entries to process at once allows for better control
+  // over the duration of the guarded/locked sections.
+  typedef std::function<bool(size_t /* valid_entries_num */,
+                             size_t /* invalid_entries_num */)>
+      IterationFunc;
+
+  // A helper function for 'validity_func' of the Invalidate() method:
+  // invalidate all entries.
+  static const ValidityFunc kInvalidateAllEntriesFunc;
+
+  // A helper function for 'iteration_func' of the Invalidate() method:
+  // examine all entries.
+  static const IterationFunc kIterateOverAllEntriesFunc;
+
+  // Control structure for the Invalidate() method. Combines the validity
+  // and the iteration functors.
+  struct InvalidationControl {
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    InvalidationControl(ValidityFunc vfunctor = kInvalidateAllEntriesFunc,
+                        IterationFunc ifunctor = kIterateOverAllEntriesFunc)
+        : validity_func(std::move(vfunctor)),
+          iteration_func(std::move(ifunctor)) {
+    }
+    const ValidityFunc validity_func;
+    const IterationFunc iteration_func;
+  };
+
+ protected:
+  // Release a mapping returned by a previous Lookup(), using raw handle.
+  // REQUIRES: handle must not have been released yet.
+  // REQUIRES: handle must have been returned by a method on *this.
+  virtual void Release(Handle* handle) = 0;
 
   // Free 'ptr', which must have been previously allocated using 'Allocate'.
   virtual void Free(PendingHandle* ptr) = 0;
@@ -211,6 +337,27 @@ class Cache {
   DISALLOW_COPY_AND_ASSIGN(Cache);
 };
 
-}  // namespace kudu
+// A template helper function to instantiate a cache of particular
+// 'eviction_policy' flavor, backed by the given storage 'mem_type',
+// where 'capacity' specifies the capacity of the result cache,
+// and 'id' specifies its identifier.
+template<Cache::EvictionPolicy eviction_policy = Cache::EvictionPolicy::LRU,
+         Cache::MemoryType mem_type = Cache::MemoryType::DRAM>
+Cache* NewCache(size_t capacity, const std::string& id);
+
+// Create a new FIFO cache with a fixed size capacity. This implementation
+// of Cache uses the first-in-first-out eviction policy and stored in DRAM.
+template<>
+Cache* NewCache<Cache::EvictionPolicy::FIFO,
+                Cache::MemoryType::DRAM>(size_t capacity, const std::string& id);
+
+// Create a new LRU cache with a fixed size capacity. This implementation
+// of Cache uses the least-recently-used eviction policy and stored in DRAM.
+template<>
+Cache* NewCache<Cache::EvictionPolicy::LRU,
+                Cache::MemoryType::DRAM>(size_t capacity, const std::string& id);
+
+// A helper method to output cache memory type into ostream.
+std::ostream& operator<<(std::ostream& os, Cache::MemoryType mem_type);
 
-#endif
+} // namespace kudu
diff --git a/be/src/kudu/util/cache_metrics.h b/be/src/kudu/util/cache_metrics.h
index 04a546b..f77b227 100644
--- a/be/src/kudu/util/cache_metrics.h
+++ b/be/src/kudu/util/cache_metrics.h
@@ -14,8 +14,8 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#ifndef KUDU_UTIL_CACHE_METRICS_H
-#define KUDU_UTIL_CACHE_METRICS_H
+
+#pragma once
 
 #include <cstdint>
 
@@ -25,8 +25,8 @@
 namespace kudu {
 
 struct CacheMetrics {
-  explicit CacheMetrics(const scoped_refptr<MetricEntity>& metric_entity);
-
+  virtual ~CacheMetrics() {
+  }
   scoped_refptr<Counter> inserts;
   scoped_refptr<Counter> lookups;
   scoped_refptr<Counter> evictions;
@@ -35,8 +35,7 @@ struct CacheMetrics {
   scoped_refptr<Counter> cache_misses;
   scoped_refptr<Counter> cache_misses_caching;
 
-  scoped_refptr<AtomicGauge<uint64_t> > cache_usage;
+  scoped_refptr<AtomicGauge<uint64_t>> cache_usage;
 };
 
 } // namespace kudu
-#endif /* KUDU_UTIL_CACHE_METRICS_H */
diff --git a/be/src/kudu/util/char_util-test.cc b/be/src/kudu/util/char_util-test.cc
new file mode 100644
index 0000000..b65e203
--- /dev/null
+++ b/be/src/kudu/util/char_util-test.cc
@@ -0,0 +1,126 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "kudu/util/char_util.h"
+
+#include <cstdint>
+#include <memory>
+
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+#include "kudu/util/debug/sanitizer_scopes.h"
+#include "kudu/util/env.h"
+#include "kudu/util/faststring.h"
+#include "kudu/util/init.h"
+#include "kudu/util/path_util.h"
+#include "kudu/util/slice.h"
+#include "kudu/util/status.h"
+#include "kudu/util/test_util.h"
+
+using std::unique_ptr;
+
+namespace kudu {
+
+class CharUtilTest : public KuduTest {
+ protected:
+  Slice data_utf8_;
+  Slice data_ascii_;
+
+  void SetUp() override {
+    // UTF8Truncate uses SSE4.1 instructions so we need to make sure the CPU
+    // running the test has these opcodes.
+    CHECK_OK(CheckCPUFlags());
+    ReadFileToString(env_, JoinPathSegments(GetTestExecutableDirectory(),
+                                           "testdata/char_truncate_utf8.txt"),
+                     &string_utf8_);
+    data_utf8_ = Slice(string_utf8_);
+    ReadFileToString(env_, JoinPathSegments(GetTestExecutableDirectory(),
+                                           "testdata/char_truncate_ascii.txt"),
+                     &string_ascii_);
+    data_ascii_ = Slice(string_ascii_);
+  }
+
+  unique_ptr<const uint8_t[]> Truncate(const Slice& slice, int length, Slice* result) {
+    *result = UTF8Truncate(slice, length);
+    return std::unique_ptr<const uint8_t[]>(result->data());
+  }
+
+  void StressTest(const Slice& slice, int length) {
+    // The memory accesses done in UTF8Truncate are quite slow with TSAN
+    // instrumentation, probably because so many of them are unaligned.
+    // Since this test is single-threaded, let's just disable TSAN in it.
+    debug::ScopedTSANIgnoreReadsAndWrites ignore_tsan;
+    for (int i = 0; i < kNumCycles_; ++i) {
+      Slice result;
+      auto ptr = Truncate(slice, length, &result);
+      ASSERT_FALSE(result.empty());
+    }
+  }
+
+ private:
+  const int kNumCycles_ = 1000000;
+  faststring string_utf8_;
+  faststring string_ascii_;
+};
+
+TEST_F(CharUtilTest, CorrectnessTestUtf8) {
+  Slice result;
+  auto ptr = Truncate(data_utf8_, 9756, &result);
+  ASSERT_EQ(10549, result.size());
+
+  ptr = Truncate(data_utf8_, 10549, &result);
+  ASSERT_EQ(10550, result.size());
+}
+
+TEST_F(CharUtilTest, CorrectnessTestAscii) {
+  Slice result;
+  auto ptr = Truncate(data_ascii_, 9756, &result);
+  ASSERT_EQ(9756, result.size());
+  ptr = Truncate(data_ascii_, 10549, &result);
+  ASSERT_EQ(10549, result.size());
+}
+
+TEST_F(CharUtilTest, CorrectnessTestIncompleteUtf8) {
+  Slice result;
+  Slice test_data = "aaaa\xf3";
+
+  auto ptr = Truncate(test_data, 5, &result);
+  ASSERT_EQ(test_data, result);
+}
+
+TEST_F(CharUtilTest, CorrectnessTestUtf8AndAscii) {
+  Slice result;
+  Slice data = "ááááááááááááááááááááááááááááááááaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+
+  auto ptr = Truncate(data, 64, &result);
+  ASSERT_EQ(data, result);
+
+  data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaáááááááááááááááááááááááááááááááá";
+  ptr = Truncate(data, 64, &result);
+  ASSERT_EQ(data, result);
+}
+
+TEST_F(CharUtilTest, StressTestUtf8) {
+  StressTest(data_utf8_, 9000);
+}
+
+TEST_F(CharUtilTest, StressTestAscii) {
+  StressTest(data_ascii_, 9000);
+}
+
+} // namespace kudu
diff --git a/be/src/kudu/util/char_util.cc b/be/src/kudu/util/char_util.cc
new file mode 100644
index 0000000..e864d87
--- /dev/null
+++ b/be/src/kudu/util/char_util.cc
@@ -0,0 +1,71 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "kudu/util/char_util.h"
+
+#include <emmintrin.h>
+#include <smmintrin.h>
+
+#include <algorithm>
+#include <cstring>
+
+namespace kudu {
+
+Slice UTF8Truncate(Slice val, size_t max_utf8_length) {
+  size_t num_utf8_chars = 0;
+  const uint8_t* str;
+  const uint8_t* start;
+  str = start = val.data();
+  size_t num_bytes = 0;
+  size_t size = val.size();
+
+  // Mask used to determine whether there are any non-ASCII characters in a
+  // 128-bit chunk
+  const __m128i mask = _mm_set1_epi32(0x80808080);
+
+  while (num_bytes < size) {
+    // If the next chunk of bytes are all ASCII we can fast path them.
+    if (size - num_bytes >= 16 &&
+        max_utf8_length - num_utf8_chars >= 16 &&
+        _mm_test_all_zeros(_mm_loadu_si128(reinterpret_cast<const __m128i*>(str)),
+                           mask) == 1) {
+      num_utf8_chars += 16;
+      num_bytes += 16;
+      str += 16;
+    } else if (size - num_bytes >= 8 &&
+               max_utf8_length - num_utf8_chars >= 8 &&
+               (*(reinterpret_cast<const int64_t*>(str)) & 0x8080808080808080) == 0) {
+      num_utf8_chars += 8;
+      num_bytes += 8;
+      str += 8;
+    } else {
+      num_utf8_chars += (*str++ & 0xc0) != 0x80;
+      num_bytes++;
+      if (num_utf8_chars > max_utf8_length) {
+        num_bytes--;
+        num_utf8_chars--;
+        break;
+      }
+    }
+  }
+  num_bytes = std::min<size_t>(size, num_bytes);
+  auto relocated = new uint8_t[num_bytes];
+  memcpy(relocated, val.data(), num_bytes);
+  return Slice(relocated, num_bytes);
+}
+
+} // namespace kudu
diff --git a/be/src/kudu/util/zlib.h b/be/src/kudu/util/char_util.h
similarity index 62%
copy from be/src/kudu/util/zlib.h
copy to be/src/kudu/util/char_util.h
index 35330fd..9fa0338 100644
--- a/be/src/kudu/util/zlib.h
+++ b/be/src/kudu/util/char_util.h
@@ -14,26 +14,26 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+
 #pragma once
 
-#include <iosfwd>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
 
 #include "kudu/util/slice.h"
-#include "kudu/util/status.h"
 
 namespace kudu {
-namespace zlib {
-
-// Zlib-compress the data in 'input', appending the result to 'out'.
-//
-// In case of an error, some data may still be appended to 'out'.
-Status Compress(Slice input, std::ostream* out);
 
-// Uncompress the zlib-compressed data in 'compressed', appending the result
-// to 'out'.
-//
-// In case of an error, some data may still be appended to 'out'.
-Status Uncompress(Slice compressed, std::ostream* out);
+  // Minimum and maxium length for VARCHAR [1,65535]
+  constexpr uint16_t kMinVarcharLength = 1;
+  constexpr uint16_t kMaxVarcharLength = std::numeric_limits<uint16_t>::max();
 
-} // namespace zlib
+  // Copy and truncate a slice. The Slice returned owns its memory.
+  //
+  // max_utf8_length is the number of UTF-8 characters/symbols (not bytes) to
+  // truncate to.
+  //
+  // The method doesn't validate the string is well-formed UTF-8.
+  Slice UTF8Truncate(Slice val, size_t max_utf8_length);
 } // namespace kudu
diff --git a/be/src/kudu/util/cloud/instance_detector-test.cc b/be/src/kudu/util/cloud/instance_detector-test.cc
new file mode 100644
index 0000000..6f3b2fa
--- /dev/null
+++ b/be/src/kudu/util/cloud/instance_detector-test.cc
@@ -0,0 +1,97 @@
+// Some portions Copyright (c) 2011 The LevelDB Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "kudu/util/cloud/instance_detector.h"
+
+#include <initializer_list>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include <gflags/gflags_declare.h>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/util/cloud/instance_metadata.h"
+#include "kudu/util/monotime.h"
+#include "kudu/util/status.h"
+
+DECLARE_uint32(cloud_metadata_server_request_timeout_ms);
+
+using std::string;
+using std::unique_ptr;
+using strings::Substitute;
+
+namespace kudu {
+namespace cloud {
+
+// Test the destruction of the object if not running the auto-detection.
+TEST(InstanceDetectorTest, NoDetectionRun) {
+  InstanceDetector detector;
+}
+
+// Basic scenario to verify the functionality of the InstanceDetector::Detect()
+// method.
+TEST(InstanceDetectorTest, Basic) {
+#ifdef THREAD_SANITIZER
+  // In case of TSAN builds, it takes longer to spawn threads and overall work
+  // with the sanitized version of libcurl while having a lot of concurrent
+  // activity. It doesn't make the metadata server taking more time to respond,
+  // but it takes longer to process a response: it's often turns into a time-out
+  // error even if the server responds in a timely manner.
+  FLAGS_cloud_metadata_server_request_timeout_ms = 10000;
+#endif
+  InstanceDetector detector(MonoDelta::FromMilliseconds(
+      FLAGS_cloud_metadata_server_request_timeout_ms));
+  unique_ptr<InstanceMetadata> metadata;
+  const auto s = detector.Detect(&metadata);
+
+  const auto s_aws = AwsInstanceMetadata().Init();
+  const auto s_azure = AzureInstanceMetadata().Init();
+  const auto s_gce = GceInstanceMetadata().Init();
+
+  if (s_aws.ok() || s_azure.ok() || s_gce.ok()) {
+    ASSERT_TRUE(s.ok()) << s.ToString();
+    ASSERT_NE(nullptr, metadata.get());
+    LOG(INFO) << Substitute("detected $0 environment, VM id: $1",
+                            TypeToString(metadata->type()), metadata->id());
+  } else {
+    ASSERT_TRUE(s.IsNotFound()) << s.ToString();
+    ASSERT_EQ(nullptr, metadata.get());
+    LOG(INFO) << "detected non-cloud environment";
+  }
+
+  if (s_aws.ok()) {
+    ASSERT_FALSE(s_azure.ok());
+    ASSERT_FALSE(s_gce.ok());
+    ASSERT_EQ(CloudType::AWS, metadata->type());
+  } else if (s_azure.ok()) {
+    ASSERT_FALSE(s_aws.ok());
+    ASSERT_FALSE(s_gce.ok());
+    ASSERT_EQ(CloudType::AZURE, metadata->type());
+  } else if (s_gce.ok()) {
+    ASSERT_FALSE(s_aws.ok());
+    ASSERT_FALSE(s_azure.ok());
+    ASSERT_EQ(CloudType::GCE, metadata->type());
+  }
+}
+
+// If the timeout for auto-detection is set too low, the detector should return
+// Status::TimedOut().
+TEST(InstanceDetectorTest, Timeout) {
+  // Try both no-time and very short interval.
+  for (const auto& interval : { MonoDelta::FromNanoseconds(0),
+                                MonoDelta::FromNanoseconds(1), } ) {
+    SCOPED_TRACE(Substitute("timeout interval '$0'", interval.ToString()));
+    InstanceDetector detector(interval);
+    unique_ptr<InstanceMetadata> metadata;
+    const auto s = detector.Detect(&metadata);
+    ASSERT_TRUE(s.IsTimedOut()) << s.ToString();
+    ASSERT_EQ(nullptr, metadata.get());
+  }
+}
+
+}  // namespace cloud
+}  // namespace kudu
diff --git a/be/src/kudu/util/cloud/instance_detector.cc b/be/src/kudu/util/cloud/instance_detector.cc
new file mode 100644
index 0000000..2142bd3
--- /dev/null
+++ b/be/src/kudu/util/cloud/instance_detector.cc
@@ -0,0 +1,127 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "kudu/util/cloud/instance_detector.h"
+
+#include <algorithm>
+#include <limits>
+#include <ostream>
+#include <string>
+
+#include <glog/logging.h>
+
+#include "kudu/gutil/strings/substitute.h"
+#include "kudu/util/cloud/instance_metadata.h"
+#include "kudu/util/monotime.h"
+#include "kudu/util/thread.h"
+
+using std::unique_ptr;
+using strings::Substitute;
+
+namespace kudu {
+namespace cloud {
+
+const size_t InstanceDetector::kNoIdx = std::numeric_limits<size_t>::max();
+
+InstanceDetector::InstanceDetector(MonoDelta timeout)
+    : timeout_(timeout),
+      cv_(&mutex_),
+      num_running_detectors_(0),
+      result_detector_idx_(kNoIdx) {
+  detectors_.push_back(
+    { unique_ptr<InstanceMetadata>(new AwsInstanceMetadata), nullptr });
+  detectors_.push_back(
+    { unique_ptr<InstanceMetadata>(new AzureInstanceMetadata), nullptr });
+  detectors_.push_back(
+    { unique_ptr<InstanceMetadata>(new GceInstanceMetadata), nullptr });
+}
+
+InstanceDetector::~InstanceDetector() {
+  for (auto& d : detectors_) {
+    if (d.runner) {
+      CHECK_OK(ThreadJoiner(d.runner.get()).Join());
+    }
+  }
+}
+
+Status InstanceDetector::Detect(unique_ptr<InstanceMetadata>* metadata) {
+  const auto deadline = MonoTime::Now() + timeout_;
+  {
+    // An extra sanity check.
+    MutexLock lock(mutex_);
+    CHECK_EQ(0, num_running_detectors_);
+    CHECK_EQ(kNoIdx, result_detector_idx_);
+    num_running_detectors_ = detectors_.size();
+  }
+
+  // Spawn multiple threads: one thread per known type of cloud instance.
+  for (auto idx = 0; idx < detectors_.size(); ++idx) {
+    auto& d = detectors_[idx];
+    CHECK(d.metadata);
+    CHECK(!d.runner);
+    scoped_refptr<Thread> runner;
+    RETURN_NOT_OK(Thread::Create("cloud detector", TypeToString(d.metadata->type()),
+        &InstanceDetector::GetInstanceInfo, this, d.metadata.get(), idx, &runner));
+    d.runner = std::move(runner);
+  }
+
+  // A cloud instance cannot be of many types: the first detector to update
+  // the 'result_detector_idx_' field wins the race and breaks the loop.
+  // Spurious wakeups are ignored by the virtue of checking the value of the
+  // 'result_detector_idx_' field.
+  {
+    MutexLock lock(mutex_);
+    while (result_detector_idx_ == kNoIdx && num_running_detectors_ > 0) {
+      if (!cv_.WaitUntil(deadline)) {
+        break;
+      }
+    }
+    if (deadline < MonoTime::Now()) {
+      return Status::TimedOut(
+          "could not retrieve instance metadata before the deadline");
+    }
+    if (result_detector_idx_ != kNoIdx) {
+      CHECK_LT(result_detector_idx_, detectors_.size());
+      *metadata = std::move(detectors_[result_detector_idx_].metadata);
+      return Status::OK();
+    }
+  }
+
+  return Status::NotFound("could not retrieve instance metadata");
+}
+
+void InstanceDetector::GetInstanceInfo(InstanceMetadata* imd, size_t idx) {
+  DCHECK(imd);
+  const auto s = imd->Init();
+  {
+    MutexLock lock(mutex_);
+    --num_running_detectors_;
+    if (s.ok()) {
+      CHECK_EQ(kNoIdx, result_detector_idx_)
+          << "conflicting cloud instance types";
+      result_detector_idx_ = idx;
+    }
+  }
+  cv_.Signal();
+  if (!s.ok()) {
+    LOG(WARNING) << Substitute("could not retrieve $0 instance metadata: $1",
+                               TypeToString(imd->type()), s.ToString());
+  }
+}
+
+} // namespace cloud
+} // namespace kudu
diff --git a/be/src/kudu/util/cloud/instance_detector.h b/be/src/kudu/util/cloud/instance_detector.h
new file mode 100644
index 0000000..d5a38ac
--- /dev/null
+++ b/be/src/kudu/util/cloud/instance_detector.h
@@ -0,0 +1,95 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+#include "kudu/gutil/port.h"
+#include "kudu/gutil/ref_counted.h"
+#include "kudu/util/cloud/instance_metadata.h"
+#include "kudu/util/condition_variable.h"
+#include "kudu/util/monotime.h"
+#include "kudu/util/mutex.h"
+#include "kudu/util/status.h"
+#include "kudu/util/thread.h"
+
+namespace kudu {
+
+namespace cloud {
+
+// Cloud instance detector. Provides an interface to retieve metadata about
+// machines/instances run by public cloud vendors.
+class InstanceDetector {
+ public:
+  // Instantiate the detector using the specified timeout for auto-detection of
+  // the type of the cloud environment it's run at.
+  explicit InstanceDetector(MonoDelta timeout = MonoDelta::FromSeconds(1));
+
+  // The destructor awaits for the detector threads to join (if any spawn).
+  virtual ~InstanceDetector();
+
+  // Perform the auto-detection of a cloud instance this object is running at.
+  // This method must not be invoked more than once.
+  // It's a synchronous call and it can take some time to complete (see
+  // the parameters of the constructor to specify timeout for the operation).
+  // On success, returns Status::OK() and provides the instance's metadata
+  // object via the 'metadata' output parameter. In case of a failure, no
+  // valid metadata is provided in the 'metadata' output parameter, and this
+  // method returns
+  //   * Status::NotFound() if the auto-detection didn't recognize any known
+  //                        instance types: the auto-detection was run in a
+  //                        non-cloud environment (most likely) or at a VM
+  //                        of unknown/not-yet-supported cloud type
+  //   * Status::TimedOut() if the specified auto-detection timeout was too
+  //                        short to identify at least one known cloud type;
+  //                        the auto-detection results should not be trusted
+  //
+  // TODO(aserbin): do we need async version of this method?
+  Status Detect(std::unique_ptr<InstanceMetadata>* metadata) WARN_UNUSED_RESULT;
+
+ private:
+  static const size_t kNoIdx;
+
+  // Get metadata for the specified instance. In case of success, store the
+  // specified index 'idx' in 'result_detector_idx_' field.
+  void GetInstanceInfo(InstanceMetadata* imd, size_t idx);
+
+  const MonoDelta timeout_;
+
+  // Mutex and associated condition variable.
+  Mutex mutex_;
+  ConditionVariable cv_;
+
+  struct DetectorInfo {
+    std::unique_ptr<InstanceMetadata> metadata;
+    scoped_refptr<Thread> runner;
+  };
+  std::vector<DetectorInfo> detectors_;
+
+  // Number of detector threads starting/running.
+  size_t num_running_detectors_;
+
+  // The index of the matched detector thread in the detectors_ container;
+  // access to the field is guarded by the 'mutex_'.
+  size_t result_detector_idx_;
+};
+
+} // namespace cloud
+} // namespace kudu
diff --git a/be/src/kudu/util/cloud/instance_metadata.cc b/be/src/kudu/util/cloud/instance_metadata.cc
new file mode 100644
index 0000000..b895181
--- /dev/null
+++ b/be/src/kudu/util/cloud/instance_metadata.cc
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 "kudu/util/cloud/instance_metadata.h"
+
+#include <cstdint>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+
+#include "kudu/gutil/macros.h"
+#include "kudu/util/curl_util.h"
+#include "kudu/util/faststring.h"
+#include "kudu/util/flag_tags.h"
+#include "kudu/util/monotime.h"
+#include "kudu/util/status.h"
+
+// The timeout should be high enough to work effectively, but as low as possible
+// to avoid slowing down detection of running in non-cloud environments. As of
+// now, the metadata servers of major public cloud providers is robust enough
+// to send the response in a fraction of a second.
+DEFINE_uint32(cloud_metadata_server_request_timeout_ms, 500,
+              "Timeout for HTTP/HTTPS requests to the instance metadata server "
+              "(in milliseconds)");
+TAG_FLAG(cloud_metadata_server_request_timeout_ms, advanced);
+TAG_FLAG(cloud_metadata_server_request_timeout_ms, runtime);
+
+// The flags below are very unlikely to be customized since they are a part
+// of the public API provided by the cloud providers. They are here for
+// the peace of mind to be able to adapt for the changes in the cloud
+// environment without the need to recompile the binaries.
+
+// See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/\
+//   ec2-instance-metadata.html#instancedata-data-retrieval
+// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/\
+//   ec2-instance-metadata.html#instancedata-data-categories
+DEFINE_string(cloud_aws_instance_id_url,
+              "http://169.254.169.254/latest/meta-data/instance-id",
+              "The URL to fetch the identifier of an AWS instance");
+TAG_FLAG(cloud_aws_instance_id_url, advanced);
+TAG_FLAG(cloud_aws_instance_id_url, runtime);
+
+// See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/set-time.html
+// for details.
+DEFINE_string(cloud_aws_ntp_server, "169.254.169.123",
+              "IP address/FQDN of the internal NTP server to use from within "
+              "an AWS instance");
+TAG_FLAG(cloud_aws_ntp_server, advanced);
+TAG_FLAG(cloud_aws_ntp_server, runtime);
+
+// See https://docs.microsoft.com/en-us/azure/virtual-machines/linux/ \
+//   instance-metadata-service#retrieving-metadata for details.
+DEFINE_string(cloud_azure_instance_id_url,
+              "http://169.254.169.254/metadata/instance/compute/vmId?"
+              "api-version=2018-10-01&format=text",
+              "The URL to fetch the identifier of an Azure instance");
+TAG_FLAG(cloud_azure_instance_id_url, advanced);
+TAG_FLAG(cloud_azure_instance_id_url, runtime);
+
+// See https://cloud.google.com/compute/docs/instances/managing-instances# \
+//   configure_ntp_for_your_instances for details.
+DEFINE_string(cloud_gce_ntp_server, "metadata.google.internal",
+              "IP address/FQDN of the internal NTP server to use from within "
+              "a GCE instance");
+TAG_FLAG(cloud_gce_ntp_server, advanced);
+TAG_FLAG(cloud_gce_ntp_server, runtime);
+
+// See https://cloud.google.com/compute/docs/storing-retrieving-metadata
+// for details.
+DEFINE_string(cloud_gce_instance_id_url,
+              "http://metadata.google.internal/computeMetadata/v1/instance/id",
+              "The URL to fetch the identifier of a GCE instance");
+TAG_FLAG(cloud_gce_instance_id_url, advanced);
+TAG_FLAG(cloud_gce_instance_id_url, runtime);
+
+using std::string;
+using std::vector;
+
+namespace kudu {
+namespace cloud {
+
+const char* TypeToString(CloudType type) {
+  static const char* const kTypeAws = "AWS";
+  static const char* const kTypeAzure = "Azure";
+  static const char* const kTypeGce = "GCE";
+  switch (type) {
+    case CloudType::AWS:
+      return kTypeAws;
+    case CloudType::AZURE:
+      return kTypeAzure;
+    case CloudType::GCE:
+      return kTypeGce;
+    default:
+      LOG(FATAL) << static_cast<uint16_t>(type) << ": unknown cloud type";
+      break;  // unreachable
+  }
+}
+
+InstanceMetadataBase::InstanceMetadataBase()
+    : is_initialized_(false) {
+}
+
+Status InstanceMetadataBase::Init() {
+  // As of now, fetching the instance identifier from metadata service is
+  // the criterion for successful initialization of the instance metadata.
+  DCHECK(!is_initialized_);
+  RETURN_NOT_OK(FetchInstanceId(&id_));
+  DCHECK(!id_.empty());
+  is_initialized_ = true;
+  return Status::OK();
+}
+
+const string& InstanceMetadataBase::id() const {
+  CHECK(is_initialized_);
+  return id_;
+}
+
+MonoDelta InstanceMetadataBase::request_timeout() const {
+  return MonoDelta::FromMilliseconds(
+      FLAGS_cloud_metadata_server_request_timeout_ms);
+}
+
+Status InstanceMetadataBase::Fetch(const string& url,
+                                   MonoDelta timeout,
+                                   const vector<string>& headers,
+                                   string* out) {
+  DCHECK(out);
+  EasyCurl curl;
+  curl.set_timeout(timeout);
+  faststring resp;
+  RETURN_NOT_OK(curl.FetchURL(url, &resp, headers));
+  *out = resp.ToString();
+  return Status::OK();
+}
+
+Status InstanceMetadataBase::FetchInstanceId(string* id) {
+  return Fetch(instance_id_url(), request_timeout(), request_headers(), id);
+}
+
+Status AwsInstanceMetadata::GetNtpServer(string* server) const {
+  DCHECK(server);
+  *server = FLAGS_cloud_aws_ntp_server;
+  return Status::OK();
+}
+
+const vector<string>& AwsInstanceMetadata::request_headers() const {
+  // EC2 doesn't require any specific headers supplied with a generic query
+  // to the metadata server.
+  static const vector<string> kRequestHeaders = {};
+  return kRequestHeaders;
+}
+
+const string& AwsInstanceMetadata::instance_id_url() const {
+  return FLAGS_cloud_aws_instance_id_url;
+}
+
+Status AzureInstanceMetadata::GetNtpServer(string* /* server */) const {
+  // An Azure instance doesn't have access to dedicated NTP servers: Azure
+  // doesn't provide such a service.
+  return Status::NotSupported("Azure doesn't provide a dedicated NTP server");
+}
+
+const vector<string>& AzureInstanceMetadata::request_headers() const {
+  static const vector<string> kRequestHeaders = { "Metadata:true" };
+  return kRequestHeaders;
+}
+
+const string& AzureInstanceMetadata::instance_id_url() const {
+  return FLAGS_cloud_azure_instance_id_url;
+}
+
+Status GceInstanceMetadata::GetNtpServer(string* server) const {
+  DCHECK(server);
+  *server = FLAGS_cloud_gce_ntp_server;
+  return Status::OK();
+}
+
+const vector<string>& GceInstanceMetadata::request_headers() const {
+  static const vector<string> kHeaders = { "Metadata-Flavor:Google" };
+  return kHeaders;
+}
+
+const string& GceInstanceMetadata::instance_id_url() const {
+  return FLAGS_cloud_gce_instance_id_url;
+}
+
+} // namespace cloud
+} // namespace kudu
diff --git a/be/src/kudu/util/cloud/instance_metadata.h b/be/src/kudu/util/cloud/instance_metadata.h
new file mode 100644
index 0000000..55a4300
--- /dev/null
+++ b/be/src/kudu/util/cloud/instance_metadata.h
@@ -0,0 +1,166 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "kudu/gutil/port.h"
+#include "kudu/util/status.h"
+
+namespace kudu {
+class MonoDelta;
+}  // namespace kudu
+
+namespace kudu {
+namespace cloud {
+
+enum class CloudType {
+  AWS,
+  AZURE,
+  GCE,
+};
+
+const char* TypeToString(CloudType type);
+
+// Generic interface to collect and access metadata of a public cloud instance.
+// Concrete classes implementing this interface use stable APIs to retrieve
+// corresponding information (published by corresponding cloud providers).
+class InstanceMetadata {
+ public:
+  virtual ~InstanceMetadata() {}
+
+  // Initialize the object, collecting information about a cloud instance.
+  // It's a synchronous call and it can take some time to complete.
+  // If the basic information has been retrieved successfully, returns
+  // Status::OK(), otherwise returns non-OK status to reflect the error
+  // encountered.
+  virtual Status Init() WARN_UNUSED_RESULT = 0;
+
+  // Get the type of the cloud instance.
+  virtual CloudType type() const = 0;
+
+  // Get identifier of the cloud instance.
+  virtual const std::string& id() const = 0;
+
+  // Get the internal NTP server accessible from within the instance.
+  // On success, returns Status::OK() and populates the output parameter
+  // 'server' with IP address or FQDN of the NTP server available from within
+  // the instance. Returns
+  //   * Status::NotSupported() if the cloud platform doesn't provide internal
+  //                            NTP service for its instances
+  //   * Status::IllegalState() if the metadata object requires initialization,
+  //                            but it hasn't been initialized yet
+  virtual Status GetNtpServer(std::string* server) const WARN_UNUSED_RESULT = 0;
+};
+
+// The common base class to work with the instance's metadata using the metadata
+// server HTTP-based API. That's the ubiquitous way of accessing metadata from
+// within a cloud instance (e.g., exists in AWS, GCE, DigitalOcean).
+class InstanceMetadataBase : public InstanceMetadata {
+ public:
+  InstanceMetadataBase();
+  ~InstanceMetadataBase() = default;
+
+  Status Init() override WARN_UNUSED_RESULT;
+  const std::string& id() const override;
+
+ protected:
+  // Fetch data from specified URL. Targeted for fetching information from
+  // the instance's metadata server.
+  static Status Fetch(const std::string& url,
+                      MonoDelta timeout,
+                      const std::vector<std::string>& headers,
+                      std::string* out);
+
+  // The timeout used for HTTP requests sent to the metadata server. The base
+  // implementation assumes the metadata server is robust enough to respond
+  // in a fraction of a second. If not, override this method accordingly.
+  virtual MonoDelta request_timeout() const;
+
+  // Return HTTP header fields to supply with requests to the metadata server.
+  // Metadata servers might have specific requirements on expected headers.
+  virtual const std::vector<std::string>& request_headers() const = 0;
+
+  // Return metadata server's URL used to retrieve instance identifier.
+  // It's assumed the server replies with plain text, where the string
+  // contains just the identifier.
+  virtual const std::string& instance_id_url() const = 0;
+
+ private:
+  // Fetch cloud instance identifier from the metadata server. Returns
+  // Status::OK() in case of success, populating the output parameter 'id' with
+  // the identifier of the instance. Returns non-OK status in case of errors.
+  Status FetchInstanceId(std::string* id);
+
+  // Instance identifier; valid only after successful initialization.
+  std::string id_;
+
+  // Whether this object has been initialized.
+  bool is_initialized_;
+};
+
+// More information on the metadata server for EC2 cloud instances:
+//   https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ \
+//     ec2-instance-metadata.html
+class AwsInstanceMetadata : public InstanceMetadataBase {
+ public:
+  AwsInstanceMetadata() = default;
+  ~AwsInstanceMetadata() = default;
+
+  CloudType type() const override { return CloudType::AWS; }
+  Status GetNtpServer(std::string* server) const override WARN_UNUSED_RESULT;
+
+ protected:
+  const std::vector<std::string>& request_headers() const override;
+  const std::string& instance_id_url() const override;
+};
+
+// More information on the metadata server for Azure cloud instances:
+//   https://docs.microsoft.com/en-us/azure/virtual-machines/linux/ \
+//     instance-metadata-service
+class AzureInstanceMetadata : public InstanceMetadataBase {
+ public:
+  AzureInstanceMetadata() = default;
+  ~AzureInstanceMetadata() = default;
+
+  CloudType type() const override { return CloudType::AZURE; }
+  Status GetNtpServer(std::string* server) const override WARN_UNUSED_RESULT;
+
+ protected:
+  const std::vector<std::string>& request_headers() const override;
+  const std::string& instance_id_url() const override;
+};
+
+// More information on the metadata server for GCE cloud instances:
+//   https://cloud.google.com/compute/docs/storing-retrieving-metadata
+class GceInstanceMetadata : public InstanceMetadataBase {
+ public:
+  GceInstanceMetadata() = default;
+  ~GceInstanceMetadata() = default;
+
+  CloudType type() const override { return CloudType::GCE; }
+  Status GetNtpServer(std::string* server) const override WARN_UNUSED_RESULT;
+
+ protected:
+  const std::vector<std::string>& request_headers() const override;
+  const std::string& instance_id_url() const override;
+};
+
+} // namespace cloud
+} // namespace kudu
diff --git a/be/src/kudu/util/compression/compression-test.cc b/be/src/kudu/util/compression/compression-test.cc
index 6b46a4f..9aa8635 100644
--- a/be/src/kudu/util/compression/compression-test.cc
+++ b/be/src/kudu/util/compression/compression-test.cc
@@ -15,28 +15,36 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <algorithm>
 #include <cstdint>
 #include <cstdlib>
 #include <cstring>
+#include <ostream>
+#include <string>
 #include <vector>
 
+#include <glog/logging.h>
 #include <gtest/gtest.h>
 
 #include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/util/compression/compression.pb.h"
 #include "kudu/util/compression/compression_codec.h"
+#include "kudu/util/random.h"
+#include "kudu/util/random_util.h"
 #include "kudu/util/slice.h"
+#include "kudu/util/stopwatch.h"
 #include "kudu/util/test_macros.h"
 #include "kudu/util/test_util.h"
 
 namespace kudu {
 
+using std::string;
 using std::vector;
 
 class TestCompression : public KuduTest {};
 
 static void TestCompressionCodec(CompressionType compression) {
-  const int kInputSize = 64;
+  constexpr int kInputSize = 64;
 
   const CompressionCodec* codec;
   uint8_t ibuffer[kInputSize];
@@ -60,15 +68,77 @@ static void TestCompressionCodec(CompressionType compression) {
   ASSERT_EQ(0, memcmp(ibuffer, ubuffer, kInputSize));
 
   // Compress slices and uncompress
-  vector<Slice> v;
-  v.emplace_back(ibuffer, 1);
-  for (int i = 1; i <= kInputSize; i += 7)
-    v.emplace_back(ibuffer + i, 7);
-  ASSERT_OK(codec->Compress(Slice(ibuffer, kInputSize), cbuffer.get(), &compressed));
+  vector<Slice> islices;
+  constexpr int kStep = 7;
+  for (int i = 0; i < kInputSize; i += kStep)
+    islices.emplace_back(ibuffer + i, std::min(kStep, kInputSize - i));
+  ASSERT_OK(codec->Compress(islices, cbuffer.get(), &compressed));
   ASSERT_OK(codec->Uncompress(Slice(cbuffer.get(), compressed), ubuffer, kInputSize));
   ASSERT_EQ(0, memcmp(ibuffer, ubuffer, kInputSize));
 }
 
+static void Benchmark(Random random, CompressionType compression) {
+  constexpr int kMaterialCount = 16;
+  constexpr int kInputSize = 8;
+  constexpr int kSliceCount = 1024;
+
+  // Prepare materials.
+  vector<string> materials;
+  materials.reserve(kMaterialCount);
+  for (int i = 0; i < kMaterialCount; ++i) {
+    materials.emplace_back(RandomString(kInputSize, &random));
+  }
+
+  // Prepare input slices.
+  vector<Slice> islices;
+  islices.reserve(kSliceCount);
+  for (int i = 0; i < kSliceCount; ++i) {
+    islices.emplace_back(Slice(materials[random.Uniform(kMaterialCount)]));
+  }
+
+  // Get the specified compression codec.
+  const CompressionCodec* codec;
+  GetCompressionCodec(compression, &codec);
+
+  // Allocate the compression buffer.
+  size_t max_compressed = codec->MaxCompressedLength(kSliceCount * kInputSize);
+  gscoped_array<uint8_t> cbuffer(new uint8_t[max_compressed]);
+
+  // Execute Compress.
+  size_t compressed;
+  {
+    uint64_t total_len = 0;
+    uint64_t compressed_len = 0;
+    Stopwatch sw;
+    sw.start();
+    while (sw.elapsed().wall_seconds() < 3) {
+      codec->Compress(islices, cbuffer.get(), &compressed);
+      total_len += kSliceCount * kInputSize;
+      compressed_len += compressed;
+    }
+    sw.stop();
+    double mbps = (total_len >> 20) / sw.elapsed().user_cpu_seconds();
+    LOG(INFO) << CompressionType_Name(compression) << " compress throughput: "
+              << mbps << " MB/sec, ratio: " << static_cast<double>(compressed_len) / total_len;
+  }
+
+  // Execute Uncompress.
+  {
+    uint8_t ubuffer[kSliceCount * kInputSize];
+    uint64_t total_len = 0;
+    Stopwatch sw;
+    sw.start();
+    while (sw.elapsed().wall_seconds() < 3) {
+      codec->Uncompress(Slice(cbuffer.get(), compressed), ubuffer, kSliceCount * kInputSize);
+      total_len += kSliceCount * kInputSize;
+    }
+    sw.stop();
+    double mbps = (total_len >> 20) / sw.elapsed().user_cpu_seconds();
+    LOG(INFO) << CompressionType_Name(compression) << " uncompress throughput: "
+              << mbps << " MB/sec";
+  }
+}
+
 TEST_F(TestCompression, TestNoCompressionCodec) {
   const CompressionCodec* codec;
   ASSERT_OK(GetCompressionCodec(NO_COMPRESSION, &codec));
@@ -76,15 +146,16 @@ TEST_F(TestCompression, TestNoCompressionCodec) {
 }
 
 TEST_F(TestCompression, TestSnappyCompressionCodec) {
-  TestCompressionCodec(SNAPPY);
-}
-
-TEST_F(TestCompression, TestLz4CompressionCodec) {
-  TestCompressionCodec(LZ4);
+  for (auto type : { SNAPPY, LZ4, ZLIB }) {
+    NO_FATALS(TestCompressionCodec(type));
+  }
 }
 
-TEST_F(TestCompression, TestZlibCompressionCodec) {
-  TestCompressionCodec(ZLIB);
+TEST_F(TestCompression, TestSimpleBenchmark) {
+  Random r(SeedRandom());
+  for (auto type : { SNAPPY, LZ4, ZLIB }) {
+    NO_FATALS(Benchmark(r, type));
+  }
 }
 
 } // namespace kudu
diff --git a/be/src/kudu/util/compression/compression_codec.cc b/be/src/kudu/util/compression/compression_codec.cc
index 2359066..dc69311 100644
--- a/be/src/kudu/util/compression/compression_codec.cc
+++ b/be/src/kudu/util/compression/compression_codec.cc
@@ -23,7 +23,6 @@
 #include <vector>
 
 #include <glog/logging.h>
-#define LZ4_DISABLE_DEPRECATE_WARNINGS
 #include <lz4.h>
 #include <snappy-sinksource.h>
 #include <snappy.h>
@@ -135,7 +134,7 @@ class SnappyCodec : public CompressionCodec {
 
   Status Uncompress(const Slice& compressed,
                     uint8_t *uncompressed,
-                    size_t uncompressed_length) const OVERRIDE {
+                    size_t /*uncompressed_length*/) const OVERRIDE {
     bool success = snappy::RawUncompress(reinterpret_cast<const char *>(compressed.data()),
                                          compressed.size(), reinterpret_cast<char *>(uncompressed));
     return success ? Status::OK() : Status::Corruption("unable to uncompress the buffer");
@@ -158,9 +157,9 @@ class Lz4Codec : public CompressionCodec {
 
   Status Compress(const Slice& input,
                   uint8_t *compressed, size_t *compressed_length) const OVERRIDE {
-    int n = LZ4_compress(reinterpret_cast<const char *>(input.data()),
-                         reinterpret_cast<char *>(compressed), input.size());
-    *compressed_length = n;
+    *compressed_length = LZ4_compress(reinterpret_cast<const char *>(input.data()),
+                                      reinterpret_cast<char *>(compressed),
+                                      input.size());
     return Status::OK();
   }
 
@@ -179,9 +178,10 @@ class Lz4Codec : public CompressionCodec {
   Status Uncompress(const Slice& compressed,
                     uint8_t *uncompressed,
                     size_t uncompressed_length) const OVERRIDE {
-    int n = LZ4_decompress_fast(reinterpret_cast<const char *>(compressed.data()),
-                                reinterpret_cast<char *>(uncompressed), uncompressed_length);
-    if (n != compressed.size()) {
+    int n = LZ4_decompress_safe(reinterpret_cast<const char *>(compressed.data()),
+                                reinterpret_cast<char *>(uncompressed),
+                                compressed.size(), uncompressed_length);
+    if (n != uncompressed_length) {
       return Status::Corruption(
         StringPrintf("unable to uncompress the buffer. error near %d, buffer", -n),
                      KUDU_REDACT(compressed.ToDebugString(100)));
@@ -276,7 +276,7 @@ CompressionType GetCompressionCodecType(const std::string& name) {
     return LZ4;
   if (uname == "ZLIB")
     return ZLIB;
-  if (uname == "NONE")
+  if (uname == "NO_COMPRESSION")
     return NO_COMPRESSION;
 
   LOG(WARNING) << "Unable to recognize the compression codec '" << name
diff --git a/be/src/kudu/util/countdown_latch-test.cc b/be/src/kudu/util/countdown_latch-test.cc
index adb2623..32a673f 100644
--- a/be/src/kudu/util/countdown_latch-test.cc
+++ b/be/src/kudu/util/countdown_latch-test.cc
@@ -15,10 +15,11 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <memory>
+
 #include <boost/bind.hpp> // IWYU pragma: keep
 #include <gtest/gtest.h>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/util/countdown_latch.h"
 #include "kudu/util/monotime.h"
@@ -40,7 +41,7 @@ static void DecrementLatch(CountDownLatch* latch, int amount) {
 // as 1 by one.
 TEST(TestCountDownLatch, TestLatch) {
 
-  gscoped_ptr<ThreadPool> pool;
+  std::unique_ptr<ThreadPool> pool;
   ASSERT_OK(ThreadPoolBuilder("cdl-test").set_max_threads(1).Build(&pool));
 
   CountDownLatch latch(1000);
diff --git a/be/src/kudu/util/countdown_latch.h b/be/src/kudu/util/countdown_latch.h
index 9a8000d..3a34639 100644
--- a/be/src/kudu/util/countdown_latch.h
+++ b/be/src/kudu/util/countdown_latch.h
@@ -75,7 +75,7 @@ class CountDownLatch {
     }
   }
 
-  // Waits for the count on the latch to reach zero, or until 'until' time is reached.
+  // Waits for the count on the latch to reach zero, or until 'when' time is reached.
   // Returns true if the count became zero, false otherwise.
   bool WaitUntil(const MonoTime& when) const {
     ThreadRestrictions::AssertWaitAllowed();
diff --git a/be/src/kudu/util/version_util-test.cc b/be/src/kudu/util/curl_util-test.cc
similarity index 50%
copy from be/src/kudu/util/version_util-test.cc
copy to be/src/kudu/util/curl_util-test.cc
index 54e8e76..7f750e7 100644
--- a/be/src/kudu/util/version_util-test.cc
+++ b/be/src/kudu/util/curl_util-test.cc
@@ -14,53 +14,47 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#include "kudu/util/version_util.h"
 
-#include <string>
-#include <vector>
+#include "kudu/util/curl_util.h"
+
+#include <memory>
 
 #include <gtest/gtest.h>
 
+#include "kudu/util/debug/sanitizer_scopes.h"
+#include "kudu/util/faststring.h"
+#include "kudu/util/monotime.h"
 #include "kudu/util/status.h"
 #include "kudu/util/test_macros.h"
-#include "kudu/util/test_util.h"
-
-using std::string;
-using std::vector;
+#include "kudu/util/threadpool.h"
 
 namespace kudu {
 
-class VersionUtilTest : public KuduTest {};
-
-TEST_F(VersionUtilTest, TestVersion) {
-  const vector<Version> good_test_cases = {
-    { "0.0.0", 0, 0, 0, "" },
-    { "1.0.0", 1, 0, 0, "" },
-    { "1.1.0", 1, 1, 0, "" },
-    { "1.1.1", 1, 1, 1, "" },
-    { "1.10.100-1000", 1, 10, 100, "1000" },
-    { "1.2.3-SNAPSHOT", 1, 2, 3, "SNAPSHOT" },
-  };
+// When using a thread sanitizer, there will be a data race when timeout.
+TEST(CurlUtilTest, TestTimeout) {
+  debug::ScopedTSANIgnoreReadsAndWrites ignore_tsan;
+  EasyCurl curl;
+  faststring dst;
+  curl.set_timeout(MonoDelta::FromMilliseconds(1));
+  Status s = curl.FetchURL("http://not_exist_host:12345", &dst);
+  ASSERT_TRUE(s.IsTimedOut());
+}
 
-  Version v;
-  for (const auto& test_case : good_test_cases) {
-    ASSERT_OK(ParseVersion(test_case.raw_version, &v));
-    EXPECT_EQ(test_case, v);
+TEST(CurlUtilTest, NonSharedObjectsBetweenThreads) {
+  const int kThreadCount = 8;
+  std::unique_ptr<ThreadPool> pool;
+  ThreadPoolBuilder("curl-util-test")
+      .set_min_threads(kThreadCount)
+      .set_max_threads(kThreadCount)
+      .Build(&pool);
+
+  for (int i = 0; i < kThreadCount; i++) {
+    ASSERT_OK(pool->SubmitFunc([&]() {
+      EasyCurl curl;
+    }));
   }
 
-  const vector<string> bad_test_cases = {
-    "",
-    "foo",
-    "foo.1.0",
-    "1.bar.0",
-    "1.0.foo",
-    "1.0foo.bar",
-    "foo5-1.4.3",
-  };
-
-  for (const auto& test_case : bad_test_cases) {
-    ASSERT_TRUE(ParseVersion(test_case, &v).IsInvalidArgument());
-  }
+  pool->Shutdown();
 }
 
 } // namespace kudu
diff --git a/be/src/kudu/util/curl_util.cc b/be/src/kudu/util/curl_util.cc
index 4eddb64..aeb61dd 100644
--- a/be/src/kudu/util/curl_util.cc
+++ b/be/src/kudu/util/curl_util.cc
@@ -19,7 +19,10 @@
 
 #include <cstddef>
 #include <cstdint>
+#include <mutex>
 #include <ostream>
+#include <string>
+#include <vector>
 
 #include <curl/curl.h>
 #include <glog/logging.h>
@@ -29,6 +32,9 @@
 #include "kudu/util/faststring.h"
 #include "kudu/util/scoped_cleanup.h"
 
+using std::string;
+using std::vector;
+
 namespace kudu {
 
 namespace {
@@ -37,6 +43,9 @@ inline Status TranslateError(CURLcode code) {
   if (code == CURLE_OK) {
     return Status::OK();
   }
+  if (code == CURLE_OPERATION_TIMEDOUT) {
+    return Status::TimedOut("curl timeout", curl_easy_strerror(code));
+  }
   return Status::NetworkError("curl error", curl_easy_strerror(code));
 }
 
@@ -55,7 +64,13 @@ EasyCurl::EasyCurl() {
   // Use our own SSL initialization, and disable curl's.
   // Both of these calls are idempotent.
   security::InitializeOpenSSL();
-  CHECK_EQ(0, curl_global_init(CURL_GLOBAL_DEFAULT & ~CURL_GLOBAL_SSL));
+  // curl_global_init() is not thread safe and multiple calls have the
+  // same effect as one call.
+  // See more details: https://curl.haxx.se/libcurl/c/curl_global_init.html
+  static std::once_flag once;
+  std::call_once(once, []() {
+    CHECK_EQ(0, curl_global_init(CURL_GLOBAL_DEFAULT & ~CURL_GLOBAL_SSL));
+  });
   curl_ = curl_easy_init();
   CHECK(curl_) << "Could not init curl";
 }
@@ -64,21 +79,21 @@ EasyCurl::~EasyCurl() {
   curl_easy_cleanup(curl_);
 }
 
-Status EasyCurl::FetchURL(const std::string& url, faststring* dst,
-                          const std::vector<std::string>& headers) {
+Status EasyCurl::FetchURL(const string& url, faststring* dst,
+                          const vector<string>& headers) {
   return DoRequest(url, nullptr, dst, headers);
 }
 
-Status EasyCurl::PostToURL(const std::string& url,
-                           const std::string& post_data,
+Status EasyCurl::PostToURL(const string& url,
+                           const string& post_data,
                            faststring* dst) {
   return DoRequest(url, &post_data, dst);
 }
 
-Status EasyCurl::DoRequest(const std::string& url,
-                           const std::string* post_data,
+Status EasyCurl::DoRequest(const string& url,
+                           const string* post_data,
                            faststring* dst,
-                           const std::vector<std::string>& headers) {
+                           const vector<string>& headers) {
   CHECK_NOTNULL(dst)->clear();
 
   if (!verify_peer_) {
@@ -88,6 +103,20 @@ Status EasyCurl::DoRequest(const std::string& url,
         curl_, CURLOPT_SSL_VERIFYPEER, 0)));
   }
 
+  if (use_spnego_) {
+    RETURN_NOT_OK(TranslateError(curl_easy_setopt(
+        curl_, CURLOPT_HTTPAUTH, CURLAUTH_NEGOTIATE)));
+    // It's necessary to pass an empty user/password to trigger the authentication
+    // code paths in curl, even though SPNEGO doesn't use them.
+    RETURN_NOT_OK(TranslateError(curl_easy_setopt(
+        curl_, CURLOPT_USERPWD, ":")));
+  }
+
+  if (verbose_) {
+    RETURN_NOT_OK(TranslateError(curl_easy_setopt(
+        curl_, CURLOPT_VERBOSE, 1)));
+  }
+
   // Add headers if specified.
   struct curl_slist* curl_headers = nullptr;
   auto clean_up_curl_slist = MakeScopedCleanup([&]() {
@@ -111,6 +140,15 @@ Status EasyCurl::DoRequest(const std::string& url,
                                                   post_data->c_str())));
   }
 
+  // Done after CURLOPT_POSTFIELDS in case that resets the method (the docs[1]
+  // are unclear on whether that happens).
+  //
+  // 1. https://curl.haxx.se/libcurl/c/CURLOPT_POSTFIELDS.html
+  if (!custom_method_.empty()) {
+    RETURN_NOT_OK(TranslateError(curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST,
+                                                  custom_method_.c_str())));
+  }
+
   RETURN_NOT_OK(TranslateError(curl_easy_setopt(curl_, CURLOPT_HTTPAUTH, CURLAUTH_ANY)));
   if (timeout_.Initialized()) {
     RETURN_NOT_OK(TranslateError(curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1)));
@@ -118,12 +156,14 @@ Status EasyCurl::DoRequest(const std::string& url,
         timeout_.ToMilliseconds())));
   }
   RETURN_NOT_OK(TranslateError(curl_easy_perform(curl_)));
-  long rc; // NOLINT(*) curl wants a long
-  RETURN_NOT_OK(TranslateError(curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &rc)));
-  if (rc != 200) {
-    return Status::RemoteError(strings::Substitute("HTTP $0", rc));
-  }
+  long val; // NOLINT(*) curl wants a long
+  RETURN_NOT_OK(TranslateError(curl_easy_getinfo(curl_, CURLINFO_NUM_CONNECTS, &val)));
+  num_connects_ = val;
 
+  RETURN_NOT_OK(TranslateError(curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &val)));
+  if (val != 200) {
+    return Status::RemoteError(strings::Substitute("HTTP $0", val));
+  }
   return Status::OK();
 }
 
diff --git a/be/src/kudu/util/curl_util.h b/be/src/kudu/util/curl_util.h
index cccd2db..2289e84 100644
--- a/be/src/kudu/util/curl_util.h
+++ b/be/src/kudu/util/curl_util.h
@@ -18,6 +18,7 @@
 #define KUDU_UTIL_CURL_UTIL_H
 
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "kudu/gutil/macros.h"
@@ -67,6 +68,26 @@ class EasyCurl {
     timeout_ = t;
   }
 
+  void set_use_spnego(bool use_spnego) {
+    use_spnego_ = use_spnego;
+  }
+
+  // Enable verbose mode for curl. This dumps debugging output to stderr, so
+  // is only really useful in the context of tests.
+  void set_verbose(bool v) {
+    verbose_ = v;
+  }
+
+  // Overrides curl's HTTP method handling with a custom method string.
+  void set_custom_method(std::string m) {
+    custom_method_ = std::move(m);
+  }
+
+  // Returns the number of new connections created to achieve the previous transfer.
+  int num_connects() const {
+    return num_connects_;
+  }
+
  private:
   // Do a request. If 'post_data' is non-NULL, does a POST.
   // Otherwise, does a GET.
@@ -76,14 +97,22 @@ class EasyCurl {
                    const std::vector<std::string>& headers = {});
   CURL* curl_;
 
+  std::string custom_method_;
+
   // Whether to verify the server certificate.
   bool verify_peer_ = true;
 
   // Whether to return the HTTP headers with the response.
   bool return_headers_ = false;
 
+  bool use_spnego_ = false;
+
+  bool verbose_ = false;
+
   MonoDelta timeout_;
 
+  int num_connects_ = 0;
+
   DISALLOW_COPY_AND_ASSIGN(EasyCurl);
 };
 
diff --git a/be/src/kudu/util/debug/trace_event_impl.cc b/be/src/kudu/util/debug/trace_event_impl.cc
index 155faf7..0f1da6e 100644
--- a/be/src/kudu/util/debug/trace_event_impl.cc
+++ b/be/src/kudu/util/debug/trace_event_impl.cc
@@ -12,6 +12,7 @@
 #include <cinttypes>
 #include <cstdlib>
 #include <cstring>
+#include <functional>
 #include <list>
 #include <sstream>
 #include <type_traits>
@@ -36,7 +37,6 @@
 #include "kudu/gutil/strings/util.h"
 #include "kudu/gutil/sysinfo.h"
 #include "kudu/gutil/walltime.h"
-
 #include "kudu/util/atomic.h"
 #include "kudu/util/debug/trace_event.h"
 #include "kudu/util/debug/trace_event_synthetic_delay.h"
@@ -565,7 +565,7 @@ void TraceEvent::Initialize(
     unsigned char flags) {
   timestamp_ = timestamp;
   thread_timestamp_ = thread_timestamp;
-  duration_ = -1;;
+  duration_ = -1;
   id_ = id;
   category_group_enabled_ = category_group_enabled;
   name_ = name;
@@ -639,7 +639,7 @@ void TraceEvent::Initialize(
 void TraceEvent::Reset() {
   // Only reset fields that won't be initialized in Initialize(), or that may
   // hold references to other objects.
-  duration_ = -1;;
+  duration_ = -1;
   parameter_copy_storage_ = nullptr;
   for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i)
     convertable_values_[i] = nullptr;
@@ -675,6 +675,7 @@ void JsonEscape(StringPiece s, string* out) {
         break;
       case '\n':
         out->append("\\n");
+        break;
       case '\r':
         out->append("\\r");
         break;
@@ -905,6 +906,7 @@ void TraceResultBuffer::Collect(
 //
 ////////////////////////////////////////////////////////////////////////////////
 class TraceBucketData;
+
 typedef Callback<void(TraceBucketData*)> TraceSampleCallback;
 
 class TraceBucketData {
@@ -1356,10 +1358,10 @@ void TraceLog::SetEnabled(const CategoryFilter& category_filter,
           "bucket2",
           Bind(&TraceSamplingThread::DefaultSamplingCallback));
 
-      Status s = Thread::Create("tracing", "sampler",
-                                &TraceSamplingThread::ThreadMain,
-                                sampling_thread_.get(),
-                                &sampling_thread_handle_);
+      Status s = Thread::CreateWithFlags(
+          "tracing", "sampler",
+          std::bind(&TraceSamplingThread::ThreadMain,sampling_thread_.get()),
+          Thread::NO_STACK_WATCHDOG, &sampling_thread_handle_);
       if (!s.ok()) {
         LOG(DFATAL) << "failed to create trace sampling thread: " << s.ToString();
       }
@@ -1930,7 +1932,7 @@ std::string TraceLog::EventToConsoleMessage(unsigned char phase,
   // TRACE_EVENT_PHASE_BEGIN or TRACE_EVENT_END.
   DCHECK(phase != TRACE_EVENT_PHASE_COMPLETE);
 
-  MicrosecondsInt64 duration;
+  MicrosecondsInt64 duration = 0;
   int thread_id = trace_event ?
       trace_event->thread_id() : Thread::UniqueThreadId();
   if (phase == TRACE_EVENT_PHASE_END) {
diff --git a/be/src/kudu/util/debug/trace_logging.h b/be/src/kudu/util/debug/trace_logging.h
index 1a3b39e..2f0fec9 100644
--- a/be/src/kudu/util/debug/trace_logging.h
+++ b/be/src/kudu/util/debug/trace_logging.h
@@ -73,11 +73,16 @@
     } ) ? static_cast<void>(0) :                                      \
           google::LogMessageVoidify() & VLOG_AND_TRACE_INTERNAL(category, vlevel) // NOLINT(*)
 
+#define VLOG_AND_TRACE_WITH_PREFIX(category, vlevel) \
+  VLOG_AND_TRACE(category, vlevel) << LogPrefix()
 
 #define LOG_AND_TRACE(category, severity) \
   kudu::debug::TraceGLog(__FILE__, __LINE__, category, \
                         google::GLOG_ ## severity, /* send_to_log= */true).stream()
 
+#define LOG_AND_TRACE_WITH_PREFIX(category, severity) \
+  LOG_AND_TRACE(category, severity) << LogPrefix()
+
 namespace kudu {
 namespace debug {
 
diff --git a/be/src/kudu/util/easy_json.cc b/be/src/kudu/util/easy_json.cc
index 9057b50..42d0324 100644
--- a/be/src/kudu/util/easy_json.cc
+++ b/be/src/kudu/util/easy_json.cc
@@ -26,6 +26,7 @@
 #include <rapidjson/rapidjson.h>
 #include <rapidjson/stringbuffer.h>
 #include <rapidjson/writer.h>
+// IWYU pragma: no_include <rapidjson/encodings.h>
 
 using rapidjson::SizeType;
 using rapidjson::Value;
@@ -82,18 +83,17 @@ EasyJson& EasyJson::operator=(T val) {
   *value_ = val;
   return *this;
 }
-template EasyJson& EasyJson::operator=<bool>(bool val);
-template EasyJson& EasyJson::operator=<int32_t>(int32_t val);
-template EasyJson& EasyJson::operator=<int64_t>(int64_t val);
-template EasyJson& EasyJson::operator=<uint32_t>(uint32_t val);
-template EasyJson& EasyJson::operator=<uint64_t>(uint64_t val);
-template EasyJson& EasyJson::operator=<double>(double val);
-template<> EasyJson& EasyJson::operator=<const char*>(const char* val) {
+template EasyJson& EasyJson::operator=(bool val);
+template EasyJson& EasyJson::operator=(int32_t val);
+template EasyJson& EasyJson::operator=(int64_t val);
+template EasyJson& EasyJson::operator=(uint32_t val);
+template EasyJson& EasyJson::operator=(uint64_t val);
+template EasyJson& EasyJson::operator=(double val);
+template<> EasyJson& EasyJson::operator=(const char* val) {
   value_->SetString(val, alloc_->allocator());
   return *this;
 }
-template<> EasyJson& EasyJson::operator=<EasyJson::ComplexTypeInitializer>(
-    EasyJson::ComplexTypeInitializer val) {
+template<> EasyJson& EasyJson::operator=(EasyJson::ComplexTypeInitializer val) {
   if (val == kObject) {
     value_->SetObject();
   } else if (val == kArray) {
@@ -123,16 +123,15 @@ template<typename T>
 EasyJson EasyJson::Set(const string& key, T val) {
   return (Get(key) = val);
 }
-template EasyJson EasyJson::Set<bool>(const string& key, bool val);
-template EasyJson EasyJson::Set<int32_t>(const string& key, int32_t val);
-template EasyJson EasyJson::Set<int64_t>(const string& key, int64_t val);
-template EasyJson EasyJson::Set<uint32_t>(const string& key, uint32_t val);
-template EasyJson EasyJson::Set<uint64_t>(const string& key, uint64_t val);
-template EasyJson EasyJson::Set<double>(const string& key, double val);
-template EasyJson EasyJson::Set<const char*>(const string& key, const char* val);
-template EasyJson EasyJson::Set<EasyJson::ComplexTypeInitializer>(
-    const string& key,
-    EasyJson::ComplexTypeInitializer val);
+template EasyJson EasyJson::Set(const string& key, bool val);
+template EasyJson EasyJson::Set(const string& key, int32_t val);
+template EasyJson EasyJson::Set(const string& key, int64_t val);
+template EasyJson EasyJson::Set(const string& key, uint32_t val);
+template EasyJson EasyJson::Set(const string& key, uint64_t val);
+template EasyJson EasyJson::Set(const string& key, double val);
+template EasyJson EasyJson::Set(const string& key, const char* val);
+template EasyJson EasyJson::Set(const string& key,
+                                EasyJson::ComplexTypeInitializer val);
 
 EasyJson EasyJson::Set(int index, const string& val) {
   return (Get(index) = val);
@@ -141,16 +140,15 @@ template<typename T>
 EasyJson EasyJson::Set(int index, T val) {
   return (Get(index) = val);
 }
-template EasyJson EasyJson::Set<bool>(int index, bool val);
-template EasyJson EasyJson::Set<int32_t>(int index, int32_t val);
-template EasyJson EasyJson::Set<int64_t>(int index, int64_t val);
-template EasyJson EasyJson::Set<uint32_t>(int index, uint32_t val);
-template EasyJson EasyJson::Set<uint64_t>(int index, uint64_t val);
-template EasyJson EasyJson::Set<double>(int index, double val);
-template EasyJson EasyJson::Set<const char*>(int index, const char* val);
-template EasyJson EasyJson::Set<EasyJson::ComplexTypeInitializer>(
-    int index,
-    EasyJson::ComplexTypeInitializer val);
+template EasyJson EasyJson::Set(int index, bool val);
+template EasyJson EasyJson::Set(int index, int32_t val);
+template EasyJson EasyJson::Set(int index, int64_t val);
+template EasyJson EasyJson::Set(int index, uint32_t val);
+template EasyJson EasyJson::Set(int index, uint64_t val);
+template EasyJson EasyJson::Set(int index, double val);
+template EasyJson EasyJson::Set(int index, const char* val);
+template EasyJson EasyJson::Set(int index,
+                                EasyJson::ComplexTypeInitializer val);
 
 EasyJson EasyJson::PushBack(const string& val) {
   if (!value_->IsArray()) {
@@ -168,13 +166,13 @@ EasyJson EasyJson::PushBack(T val) {
   value_->PushBack(val, alloc_->allocator());
   return EasyJson(&(*value_)[value_->Size() - 1], alloc_);
 }
-template EasyJson EasyJson::PushBack<bool>(bool val);
-template EasyJson EasyJson::PushBack<int32_t>(int32_t val);
-template EasyJson EasyJson::PushBack<int64_t>(int64_t val);
-template EasyJson EasyJson::PushBack<uint32_t>(uint32_t val);
-template EasyJson EasyJson::PushBack<uint64_t>(uint64_t val);
-template EasyJson EasyJson::PushBack<double>(double val);
-template<> EasyJson EasyJson::PushBack<const char*>(const char* val) {
+template EasyJson EasyJson::PushBack(bool val);
+template EasyJson EasyJson::PushBack(int32_t val);
+template EasyJson EasyJson::PushBack(int64_t val);
+template EasyJson EasyJson::PushBack(uint32_t val);
+template EasyJson EasyJson::PushBack(uint64_t val);
+template EasyJson EasyJson::PushBack(double val);
+template<> EasyJson EasyJson::PushBack(const char* val) {
   if (!value_->IsArray()) {
     value_->SetArray();
   }
@@ -182,8 +180,7 @@ template<> EasyJson EasyJson::PushBack<const char*>(const char* val) {
   value_->PushBack(push_val, alloc_->allocator());
   return EasyJson(&(*value_)[value_->Size() - 1], alloc_);
 }
-template<> EasyJson EasyJson::PushBack<EasyJson::ComplexTypeInitializer>(
-    EasyJson::ComplexTypeInitializer val) {
+template<> EasyJson EasyJson::PushBack(EasyJson::ComplexTypeInitializer val) {
   if (!value_->IsArray()) {
     value_->SetArray();
   }
diff --git a/be/src/kudu/util/env-test.cc b/be/src/kudu/util/env-test.cc
index 1c7f899..49556ef 100644
--- a/be/src/kudu/util/env-test.cc
+++ b/be/src/kudu/util/env-test.cc
@@ -167,7 +167,7 @@ class TestEnv : public KuduTest {
     unique_ptr<uint8_t[]> scratch(new uint8_t[n]);
     Slice s(scratch.get(), n);
     ASSERT_OK(raf->Read(offset, s));
-    ASSERT_NO_FATAL_FAILURE(VerifyTestData(s, offset));
+    NO_FATALS(VerifyTestData(s, offset));
   }
 
   void TestAppendV(size_t num_slices, size_t slice_size, size_t iterations,
@@ -213,8 +213,8 @@ class TestEnv : public KuduTest {
         if (!fast) {
           // Verify as write. Note: this requires that file is pre-allocated, otherwise
           // the Read() fails with EINVAL.
-          ASSERT_NO_FATAL_FAILURE(ReadAndVerifyTestData(raf.get(), num_slices * slice_size * i,
-                                                        num_slices * slice_size));
+          NO_FATALS(ReadAndVerifyTestData(raf.get(), num_slices * slice_size * i,
+                                          num_slices * slice_size));
         }
       }
     }
@@ -226,8 +226,8 @@ class TestEnv : public KuduTest {
       ASSERT_OK(env_util::OpenFileForRandom(env_, kTestPath, &raf));
     }
     for (int i = 0; i < iterations; i++) {
-      ASSERT_NO_FATAL_FAILURE(ReadAndVerifyTestData(raf.get(), num_slices * slice_size * i,
-                                                    num_slices * slice_size));
+      NO_FATALS(ReadAndVerifyTestData(raf.get(), num_slices * slice_size * i,
+                                      num_slices * slice_size));
     }
   }
 
@@ -474,7 +474,7 @@ TEST_F(TestEnv, TestReadFully) {
   Env* env = Env::Default();
 
   WriteTestFile(env, kTestPath, kFileSize);
-  ASSERT_NO_FATAL_FAILURE();
+  NO_FATALS();
 
   // Reopen for read
   shared_ptr<RandomAccessFile> raf;
@@ -571,15 +571,15 @@ TEST_F(TestEnv, TestIOVMax) {
 TEST_F(TestEnv, TestAppendV) {
   WritableFileOptions opts;
   LOG(INFO) << "Testing AppendV() only, NO pre-allocation";
-  ASSERT_NO_FATAL_FAILURE(TestAppendV(2000, 1024, 5, true, false, opts));
+  NO_FATALS(TestAppendV(2000, 1024, 5, true, false, opts));
 
   if (!fallocate_supported_) {
     LOG(INFO) << "fallocate not supported, skipping preallocated runs";
   } else {
     LOG(INFO) << "Testing AppendV() only, WITH pre-allocation";
-    ASSERT_NO_FATAL_FAILURE(TestAppendV(2000, 1024, 5, true, true, opts));
+    NO_FATALS(TestAppendV(2000, 1024, 5, true, true, opts));
     LOG(INFO) << "Testing AppendV() together with Append() and Read(), WITH pre-allocation";
-    ASSERT_NO_FATAL_FAILURE(TestAppendV(128, 4096, 5, false, true, opts));
+    NO_FATALS(TestAppendV(128, 4096, 5, false, true, opts));
   }
 }
 
@@ -592,7 +592,7 @@ TEST_F(TestEnv, TestGetExecutablePath) {
 TEST_F(TestEnv, TestOpenEmptyRandomAccessFile) {
   Env* env = Env::Default();
   string test_file = GetTestPath("test_file");
-  ASSERT_NO_FATAL_FAILURE(WriteTestFile(env, test_file, 0));
+  NO_FATALS(WriteTestFile(env, test_file, 0));
   unique_ptr<RandomAccessFile> readable_file;
   ASSERT_OK(env->NewRandomAccessFile(test_file, &readable_file));
   uint64_t size;
@@ -612,7 +612,7 @@ TEST_F(TestEnv, TestOverwrite) {
 
   // File exists, try to overwrite (and fail).
   WritableFileOptions opts;
-  opts.mode = Env::CREATE_NON_EXISTING;
+  opts.mode = Env::MUST_CREATE;
   Status s = env_util::OpenFileForWrite(opts,
                                         env_, test_path, &writer);
   ASSERT_TRUE(s.IsAlreadyPresent());
@@ -634,7 +634,7 @@ TEST_F(TestEnv, TestReopen) {
 
   // Reopen it and append to it.
   WritableFileOptions reopen_opts;
-  reopen_opts.mode = Env::OPEN_EXISTING;
+  reopen_opts.mode = Env::MUST_EXIST;
   ASSERT_OK(env_util::OpenFileForWrite(reopen_opts,
                                        env_, test_path, &writer));
   ASSERT_EQ(first.length(), writer->Size());
@@ -771,10 +771,15 @@ TEST_F(TestEnv, TestWalkBadPermissions) {
     PCHECK(chmod(kTestPath.c_str(), stat_buf.st_mode) == 0);
   });
 
-  // A walk on a directory without execute permission should fail.
+  // A walk on a directory without execute permission should fail,
+  // unless the calling process has super-user's effective ID.
   Status s = env_->Walk(kTestPath, Env::PRE_ORDER, Bind(&NoopTestWalkCb));
-  ASSERT_TRUE(s.IsIOError());
-  ASSERT_STR_CONTAINS(s.ToString(), "One or more errors occurred");
+  if (geteuid() == 0) {
+    ASSERT_TRUE(s.ok()) << s.ToString();
+  } else {
+    ASSERT_TRUE(s.IsIOError()) << s.ToString();
+    ASSERT_STR_CONTAINS(s.ToString(), "One or more errors occurred");
+  }
 }
 
 static Status TestWalkErrorCb(int* num_calls,
@@ -836,7 +841,11 @@ TEST_F(TestEnv, TestGlobPermissionDenied) {
     });
   vector<string> matches;
   Status s = env_->Glob(JoinPathSegments(dir, "*"), &matches);
-  ASSERT_STR_MATCHES(s.ToString(), "IO error: glob failed for /.*: Permission denied");
+  if (geteuid() == 0) {
+    ASSERT_TRUE(s.ok()) << s.ToString();
+  } else {
+    ASSERT_STR_MATCHES(s.ToString(), "IO error: glob failed for /.*: Permission denied");
+  }
 }
 
 TEST_F(TestEnv, TestGetBlockSize) {
@@ -922,16 +931,31 @@ TEST_F(TestEnv, TestRWFile) {
 
   // Make sure we can't overwrite it.
   RWFileOptions opts;
-  opts.mode = Env::CREATE_NON_EXISTING;
+  opts.mode = Env::MUST_CREATE;
   ASSERT_TRUE(env_->NewRWFile(opts, GetTestPath("foo"), &file).IsAlreadyPresent());
 
   // Reopen it without truncating the existing data.
-  opts.mode = Env::OPEN_EXISTING;
+  opts.mode = Env::MUST_EXIST;
   ASSERT_OK(env_->NewRWFile(opts, GetTestPath("foo"), &file));
   uint8_t scratch4[kNewTestData.length()];
   Slice result4(scratch4, kNewTestData.length());
   ASSERT_OK(file->Read(0, result4));
   ASSERT_EQ(result4, kNewTestData);
+
+  // Test CREATE_OR_OPEN semantics on a new file.
+  const string bar_path = GetTestPath("bar");
+  unique_ptr<RWFile> file_two;
+  opts.mode = Env::CREATE_OR_OPEN;
+  ASSERT_FALSE(env_->FileExists(bar_path));
+  ASSERT_OK(env_->NewRWFile(opts, bar_path, &file_two));
+  ASSERT_TRUE(env_->FileExists(bar_path));
+  ASSERT_OK(file_two->Write(0, kTestData));
+  ASSERT_OK(file_two->Size(&sz));
+  ASSERT_EQ(kTestData.length(), sz);
+
+  ASSERT_OK(env_->NewRWFile(opts, bar_path, &file_two));
+  ASSERT_OK(file_two->Size(&sz));
+  ASSERT_EQ(kTestData.length(), sz);
 }
 
 TEST_F(TestEnv, TestCanonicalize) {
diff --git a/be/src/kudu/util/env.cc b/be/src/kudu/util/env.cc
index 90755e0..3bc5ee7 100644
--- a/be/src/kudu/util/env.cc
+++ b/be/src/kudu/util/env.cc
@@ -18,16 +18,7 @@ namespace kudu {
 Env::~Env() {
 }
 
-SequentialFile::~SequentialFile() {
-}
-
-RandomAccessFile::~RandomAccessFile() {
-}
-
-WritableFile::~WritableFile() {
-}
-
-RWFile::~RWFile() {
+File::~File() {
 }
 
 FileLock::~FileLock() {
diff --git a/be/src/kudu/util/env.h b/be/src/kudu/util/env.h
index 2822994..90fb7aa 100644
--- a/be/src/kudu/util/env.h
+++ b/be/src/kudu/util/env.h
@@ -52,15 +52,17 @@ class Env {
  public:
   // Governs if/how the file is created.
   //
-  // enum value                      | file exists       | file does not exist
-  // --------------------------------+-------------------+--------------------
-  // CREATE_IF_NON_EXISTING_TRUNCATE | opens + truncates | creates
-  // CREATE_NON_EXISTING             | fails             | creates
-  // OPEN_EXISTING                   | opens             | fails
-  enum CreateMode {
-    CREATE_IF_NON_EXISTING_TRUNCATE,
-    CREATE_NON_EXISTING,
-    OPEN_EXISTING
+  // enum value                   | file exists       | file does not exist
+  // -----------------------------+-------------------+--------------------
+  // CREATE_OR_OPEN_WITH_TRUNCATE | opens + truncates | creates
+  // CREATE_OR_OPEN               | opens             | creates
+  // MUST_CREATE                  | fails             | creates
+  // MUST_EXIST                   | opens             | fails
+  enum OpenMode {
+    CREATE_OR_OPEN_WITH_TRUNCATE,
+    CREATE_OR_OPEN,
+    MUST_CREATE,
+    MUST_EXIST
   };
 
   Env() { }
@@ -366,16 +368,22 @@ class Env {
   static const char* const kInjectedFailureStatusMsg;
 
  private:
-  // No copying allowed
-  Env(const Env&);
-  void operator=(const Env&);
+  DISALLOW_COPY_AND_ASSIGN(Env);
+};
+
+// A bare-bones abstraction common to all file implementations.
+class File {
+ public:
+  virtual ~File();
+
+  // Returns the filename provided at construction time.
+  virtual const std::string& filename() const = 0;
 };
 
 // A file abstraction for reading sequentially through a file
-class SequentialFile {
+class SequentialFile : public File {
  public:
   SequentialFile() { }
-  virtual ~SequentialFile();
 
   // Read up to "result.size" bytes from the file.
   // Sets "result.data" to the data that was read.
@@ -394,16 +402,17 @@ class SequentialFile {
   //
   // REQUIRES: External synchronization
   virtual Status Skip(uint64_t n) = 0;
-
-  // Returns the filename provided when the SequentialFile was constructed.
-  virtual const std::string& filename() const = 0;
 };
 
 // A file abstraction for randomly reading the contents of a file.
-class RandomAccessFile {
+//
+// Note: this abstraction is safe to use in FileCache, which means all
+// implementations must ensure that any mutable state changes brought on by
+// instance destruction and recreation (i.e. triggered by cache eviction and
+// reloading events) do not affect correctness.
+class RandomAccessFile : public File {
  public:
   RandomAccessFile() { }
-  virtual ~RandomAccessFile();
 
   // Read "result.size" bytes from the file starting at "offset".
   // Copies the resulting data into "result.data".
@@ -433,9 +442,6 @@ class RandomAccessFile {
   // Returns the size of the file
   virtual Status Size(uint64_t *size) const = 0;
 
-  // Returns the filename provided when the RandomAccessFile was constructed.
-  virtual const std::string& filename() const = 0;
-
   // Returns the approximate memory usage of this RandomAccessFile including
   // the object itself.
   virtual size_t memory_footprint() const = 0;
@@ -447,11 +453,11 @@ struct WritableFileOptions {
   bool sync_on_close;
 
   // See CreateMode for details.
-  Env::CreateMode mode;
+  Env::OpenMode mode;
 
   WritableFileOptions()
     : sync_on_close(false),
-      mode(Env::CREATE_IF_NON_EXISTING_TRUNCATE) { }
+      mode(Env::CREATE_OR_OPEN_WITH_TRUNCATE) { }
 };
 
 // Options specified when a file is opened for random access.
@@ -462,7 +468,7 @@ struct RandomAccessFileOptions {
 // A file abstraction for sequential writing.  The implementation
 // must provide buffering since callers may append small fragments
 // at a time to the file.
-class WritableFile {
+class WritableFile : public File {
  public:
   enum FlushMode {
     FLUSH_SYNC,
@@ -470,7 +476,6 @@ class WritableFile {
   };
 
   WritableFile() { }
-  virtual ~WritableFile();
 
   virtual Status Append(const Slice& data) = 0;
 
@@ -505,13 +510,8 @@ class WritableFile {
 
   virtual uint64_t Size() const = 0;
 
-  // Returns the filename provided when the WritableFile was constructed.
-  virtual const std::string& filename() const = 0;
-
  private:
-  // No copying allowed
-  WritableFile(const WritableFile&);
-  void operator=(const WritableFile&);
+  DISALLOW_COPY_AND_ASSIGN(WritableFile);
 };
 
 // Creation-time options for RWFile
@@ -520,11 +520,11 @@ struct RWFileOptions {
   bool sync_on_close;
 
   // See CreateMode for details.
-  Env::CreateMode mode;
+  Env::OpenMode mode;
 
   RWFileOptions()
     : sync_on_close(false),
-      mode(Env::CREATE_IF_NON_EXISTING_TRUNCATE) { }
+      mode(Env::CREATE_OR_OPEN_WITH_TRUNCATE) { }
 };
 
 // A file abstraction for both reading and writing. No notion of a built-in
@@ -535,17 +535,19 @@ struct RWFileOptions {
 // noted otherwise) bearing in mind the usual filesystem coherency guarantees
 // (e.g. two threads that write concurrently to the same file offset will
 // probably yield garbage).
-class RWFile {
+//
+// Note: this abstraction is safe to use in FileCache, which means all
+// implementations must ensure that any mutable state changes brought on by
+// instance destruction and recreation (i.e. triggered by cache eviction and
+// reloading events) do not affect correctness.
+class RWFile : public File {
  public:
   enum FlushMode {
     FLUSH_SYNC,
     FLUSH_ASYNC
   };
 
-  RWFile() {
-  }
-
-  virtual ~RWFile();
+  RWFile() { }
 
   // Read "result.size" bytes from the file starting at "offset".
   // Copies the resulting data into "result.data".
@@ -647,9 +649,6 @@ class RWFile {
   typedef std::map<uint64_t, uint64_t> ExtentMap;
   virtual Status GetExtentMap(ExtentMap* out) const = 0;
 
-  // Returns the filename provided when the RWFile was constructed.
-  virtual const std::string& filename() const = 0;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(RWFile);
 };
@@ -660,9 +659,7 @@ class FileLock {
   FileLock() { }
   virtual ~FileLock();
  private:
-  // No copying allowed
-  FileLock(const FileLock&);
-  void operator=(const FileLock&);
+  DISALLOW_COPY_AND_ASSIGN(FileLock);
 };
 
 // A utility routine: write "data" to the named file.
diff --git a/be/src/kudu/util/env_posix.cc b/be/src/kudu/util/env_posix.cc
index fe47bfd..bca9f88 100644
--- a/be/src/kudu/util/env_posix.cc
+++ b/be/src/kudu/util/env_posix.cc
@@ -29,6 +29,7 @@
 #include <ostream>
 #include <string>
 #include <type_traits>
+#include <utility>
 #include <vector>
 
 #include <gflags/gflags.h>
@@ -124,8 +125,8 @@ typedef struct xfs_flock64 {
 #define MAYBE_RETURN_EIO(filename_expr, error_expr) do { \
   const string& f_ = (filename_expr); \
   MAYBE_RETURN_FAILURE(FLAGS_env_inject_eio, \
-      ShouldInject(f_, FLAGS_env_inject_eio_globs) ? (error_expr) : Status::OK()) \
-} while (0);
+      ShouldInject(f_, FLAGS_env_inject_eio_globs) ? (error_expr) : Status::OK()); \
+} while (0)
 
 bool ShouldInject(const string& candidate, const string& glob_patterns) {
   // Never inject on /proc/ file accesses regardless of the configured flag,
@@ -296,10 +297,10 @@ class ScopedFdCloser {
   }
 
  private:
-  int fd_;
+  const int fd_;
 };
 
-Status IOError(const std::string& context, int err_number) {
+Status IOError(const string& context, int err_number) {
   switch (err_number) {
     case ENOENT:
       return Status::NotFound(context, ErrnoToString(err_number), err_number);
@@ -340,18 +341,21 @@ Status DoSync(int fd, const string& filename) {
   return Status::OK();
 }
 
-Status DoOpen(const string& filename, Env::CreateMode mode, int* fd) {
+Status DoOpen(const string& filename, Env::OpenMode mode, int* fd) {
   MAYBE_RETURN_EIO(filename, IOError(Env::kInjectedFailureStatusMsg, EIO));
   ThreadRestrictions::AssertIOAllowed();
   int flags = O_RDWR;
   switch (mode) {
-    case Env::CREATE_IF_NON_EXISTING_TRUNCATE:
+    case Env::CREATE_OR_OPEN_WITH_TRUNCATE:
       flags |= O_CREAT | O_TRUNC;
       break;
-    case Env::CREATE_NON_EXISTING:
+    case Env::CREATE_OR_OPEN:
+      flags |= O_CREAT;
+      break;
+    case Env::MUST_CREATE:
       flags |= O_CREAT | O_EXCL;
       break;
-    case Env::OPEN_EXISTING:
+    case Env::MUST_EXIST:
       break;
     default:
       return Status::NotSupported(Substitute("Unknown create mode $0", mode));
@@ -520,6 +524,7 @@ const char* ResourceLimitTypeToString(Env::ResourceLimitType t) {
       return "running threads per effective uid";
     default: LOG(FATAL) << "Unknown resource limit type";
   }
+  __builtin_unreachable();
 }
 
 int ResourceLimitTypeToUnixRlimit(Env::ResourceLimitType t) {
@@ -528,6 +533,7 @@ int ResourceLimitTypeToUnixRlimit(Env::ResourceLimitType t) {
     case Env::ResourceLimitType::RUNNING_THREADS_PER_EUID: return RLIMIT_NPROC;
     default: LOG(FATAL) << "Unknown resource limit type: " << t;
   }
+  __builtin_unreachable();
 }
 
 #ifdef __APPLE__
@@ -539,18 +545,19 @@ const char* ResourceLimitTypeToMacosRlimit(Env::ResourceLimitType t) {
       return "kern.maxprocperuid";
     default: LOG(FATAL) << "Unknown resource limit type: " << t;
   }
+  __builtin_unreachable();
 }
 #endif
 
 class PosixSequentialFile: public SequentialFile {
  private:
-  std::string filename_;
-  FILE* file_;
+  const string filename_;
+  FILE* const file_;
 
  public:
-  PosixSequentialFile(std::string fname, FILE* f)
+  PosixSequentialFile(string fname, FILE* f)
       : filename_(std::move(fname)), file_(f) {}
-  virtual ~PosixSequentialFile() {
+  ~PosixSequentialFile() {
     int err;
     RETRY_ON_EINTR(err, fclose(file_));
     if (PREDICT_FALSE(err != 0)) {
@@ -593,13 +600,13 @@ class PosixSequentialFile: public SequentialFile {
 // pread() based random-access
 class PosixRandomAccessFile: public RandomAccessFile {
  private:
-  std::string filename_;
-  int fd_;
+  const string filename_;
+  const int fd_;
 
  public:
-  PosixRandomAccessFile(std::string fname, int fd)
+  PosixRandomAccessFile(string fname, int fd)
       : filename_(std::move(fname)), fd_(fd) {}
-  virtual ~PosixRandomAccessFile() {
+  ~PosixRandomAccessFile() {
     int err;
     RETRY_ON_EINTR(err, close(fd_));
     if (PREDICT_FALSE(err != 0)) {
@@ -640,19 +647,18 @@ class PosixRandomAccessFile: public RandomAccessFile {
 // order to further improve Sync() performance.
 class PosixWritableFile : public WritableFile {
  public:
-  PosixWritableFile(std::string fname, int fd, uint64_t file_size,
+  PosixWritableFile(string fname, int fd, uint64_t file_size,
                     bool sync_on_close)
       : filename_(std::move(fname)),
         fd_(fd),
         sync_on_close_(sync_on_close),
         filesize_(file_size),
         pre_allocated_size_(0),
-        pending_sync_(false) {}
+        pending_sync_(false),
+        closed_(false) {}
 
   ~PosixWritableFile() {
-    if (fd_ >= 0) {
-      WARN_NOT_OK(Close(), "Failed to close " + filename_);
-    }
+    WARN_NOT_OK(Close(), "Failed to close " + filename_);
   }
 
   virtual Status Append(const Slice& data) OVERRIDE {
@@ -694,6 +700,9 @@ class PosixWritableFile : public WritableFile {
   }
 
   virtual Status Close() OVERRIDE {
+    if (closed_) {
+      return Status::OK();
+    }
     TRACE_EVENT1("io", "PosixWritableFile::Close", "path", filename_);
     ThreadRestrictions::AssertIOAllowed();
     MAYBE_RETURN_EIO(filename_, IOError(Env::kInjectedFailureStatusMsg, EIO));
@@ -728,7 +737,7 @@ class PosixWritableFile : public WritableFile {
       }
     }
 
-    fd_ = -1;
+    closed_ = true;
     return s;
   }
 
@@ -772,13 +781,14 @@ class PosixWritableFile : public WritableFile {
   virtual const string& filename() const OVERRIDE { return filename_; }
 
  private:
-  const std::string filename_;
-  int fd_;
-  bool sync_on_close_;
+  const string filename_;
+  const int fd_;
+  const bool sync_on_close_;
+
   uint64_t filesize_;
   uint64_t pre_allocated_size_;
-
   bool pending_sync_;
+  bool closed_;
 };
 
 class PosixRWFile : public RWFile {
@@ -1033,7 +1043,7 @@ class PosixRWFile : public RWFile {
... 15090 lines suppressed ...


[impala] 04/04: IMPALA-9335 (part 2): Fix rebased KRPC to compile

Posted by jo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

joemcdonnell pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit 19a4d8fe794c9b17e69d6c65473f9a68084916bb
Author: Thomas Tauber-Marshall <tm...@cloudera.com>
AuthorDate: Wed Jan 22 17:10:09 2020 -0800

    IMPALA-9335 (part 2): Fix rebased KRPC to compile
    
    This patch applies various fixes to Impala and to the copied Kudu
    source code in be/src/kudu/* to allow everything to compile.
    
    Some highlights of the changes made:
    - Various Kudu files were removed from compilation due to issues like
      relying on libraries that Impala does not provide. The linking of
      some executable is also changed for similar reasons.
    - The Kudu Cache implementation changed to support unique_ptr,
      allowing us to remove various uses of MakeScopeExitTrigger.
    - Some flags that have a DEFINE in both Kudu and Impala are modified
      to change one of the DEFINEs to a DECLARE.
    
    This patch was in part based on the patches that were applied the last
    time we rebased the Kudu code in IMPALA-7006, and I ensured that all
    changes from those commits that are still relevant were included here.
    
    I also went through all commits that have been applied to the
    be/src/kudu directory since the last rebase and ensured that all
    relevant changes from those are included here.
    
    Testing:
    - Passed an exhaustive DEBUG build and a core ASAN build.
    
    Change-Id: I1eb4caf927c729109426fb50a28b5e15d6ac46cb
    Reviewed-on: http://gerrit.cloudera.org:8080/15144
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
    Reviewed-by: Joe McDonnell <jo...@cloudera.com>
---
 be/src/kudu/rpc/CMakeLists.txt                | 17 ++++---
 be/src/kudu/rpc/transfer.cc                   |  4 +-
 be/src/kudu/security/CMakeLists.txt           |  9 ++++
 be/src/kudu/security/test/mini_kdc.cc         |  4 +-
 be/src/kudu/util/CMakeLists.txt               | 68 +++++++++++++++------------
 be/src/kudu/util/flags.cc                     |  5 +-
 be/src/kudu/util/kudu_export.h                | 62 ++++++++++++++++++++++++
 be/src/kudu/util/logging.cc                   | 13 +++--
 be/src/kudu/util/logging.h                    |  2 +-
 be/src/rpc/impala-service-pool.cc             |  7 ++-
 be/src/rpc/impala-service-pool.h              |  4 +-
 be/src/runtime/io/data-cache.cc               | 48 +++++++++----------
 be/src/runtime/io/data-cache.h                |  5 +-
 be/src/util/webserver.cc                      |  2 +
 bin/rat_exclude_files.txt                     |  2 +
 cmake_modules/kudu_cmake_fns.txt              |  9 ++++
 tests/custom_cluster/test_restart_services.py |  2 +-
 17 files changed, 177 insertions(+), 86 deletions(-)

diff --git a/be/src/kudu/rpc/CMakeLists.txt b/be/src/kudu/rpc/CMakeLists.txt
index 1190968..b666de5 100644
--- a/be/src/kudu/rpc/CMakeLists.txt
+++ b/be/src/kudu/rpc/CMakeLists.txt
@@ -90,13 +90,16 @@ ADD_EXPORTABLE_LIBRARY(krpc
   DEPS ${KRPC_LIBS})
 
 ### RPC generator tool
-add_executable(protoc-gen-krpc protoc-gen-krpc.cc)
-target_link_libraries(protoc-gen-krpc
-    ${KUDU_BASE_LIBS}
-    rpc_header_proto
-    protoc
-    protobuf
-    gutil)
+add_executable(protoc-gen-krpc protoc-gen-krpc.cc
+  # Impala - add stub for kudu::VersionInfo
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../common/kudu_version.cc
+  # Impala - add definition for any flag names shared between Impala / Kudu.
+  # TODO: Consider either removing code that depends on these flags, or namespacing them
+  # somehow.
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../common/global-flags.cc)
+# IMPALA-8642: kudu_version.cc depends on gen-cpp/Status_types.h in target thrift-deps
+add_dependencies(protoc-gen-krpc thrift-deps)
+target_link_libraries(protoc-gen-krpc gutil glog gflags protoc protobuf rpc_header_proto ${KUDU_BASE_LIBS})
 
 #### RPC test
 PROTOBUF_GENERATE_CPP(
diff --git a/be/src/kudu/rpc/transfer.cc b/be/src/kudu/rpc/transfer.cc
index b268e77..674a9f6 100644
--- a/be/src/kudu/rpc/transfer.cc
+++ b/be/src/kudu/rpc/transfer.cc
@@ -41,7 +41,9 @@ DEFINE_bool(rpc_max_message_size_enable_validation, true,
             "This is a test-only flag.");
 TAG_FLAG(rpc_max_message_size_enable_validation, unsafe);
 
-DEFINE_int64(rpc_max_message_size, (50 * 1024 * 1024),
+// Hidden in Impala because we require a particular value and override the user specified
+// value anyways, see RpcMgr::Init() and IMPALA-4874.
+DEFINE_int64_hidden(rpc_max_message_size, (50 * 1024 * 1024),
              "The maximum size of a message that any RPC that the server will accept. "
              "Must be at least 1MB.");
 TAG_FLAG(rpc_max_message_size, advanced);
diff --git a/be/src/kudu/security/CMakeLists.txt b/be/src/kudu/security/CMakeLists.txt
index 7abaabb..ed087c0 100644
--- a/be/src/kudu/security/CMakeLists.txt
+++ b/be/src/kudu/security/CMakeLists.txt
@@ -96,6 +96,15 @@ ADD_EXPORTABLE_LIBRARY(security
   SRCS ${SECURITY_SRCS}
   DEPS ${SECURITY_LIBS})
 
+# Since Kudu tests are explicitly disabled, we want to expose some of their sources
+# to Impala using another variable.
+set(SECURITY_TEST_SRCS_FOR_IMPALA test/mini_kdc.cc)
+add_library(security-test-for-impala ${SECURITY_TEST_SRCS_FOR_IMPALA})
+target_link_libraries(security-test-for-impala
+  gutil
+  kudu_test_util
+  kudu_util
+  security)
 
 ##############################
 # mini_kdc
diff --git a/be/src/kudu/security/test/mini_kdc.cc b/be/src/kudu/security/test/mini_kdc.cc
index 1662770..63e834b 100644
--- a/be/src/kudu/security/test/mini_kdc.cc
+++ b/be/src/kudu/security/test/mini_kdc.cc
@@ -63,7 +63,9 @@ MiniKdc::MiniKdc(MiniKdcOptions options)
     options_.realm = "KRBTEST.COM";
   }
   if (options_.data_root.empty()) {
-    options_.data_root = JoinPathSegments(GetTestDataDirectory(), "krb5kdc");
+    // We hardcode "/tmp" here since the original function which initializes a random test
+    // directory (GetTestDataDirectory()), depends on gmock.
+    options_.data_root = JoinPathSegments("/tmp", "krb5kdc");
   }
   if (options_.ticket_lifetime.empty()) {
     options_.ticket_lifetime = "24h";
diff --git a/be/src/kudu/util/CMakeLists.txt b/be/src/kudu/util/CMakeLists.txt
index 4c576bb..7f894bd 100644
--- a/be/src/kudu/util/CMakeLists.txt
+++ b/be/src/kudu/util/CMakeLists.txt
@@ -151,7 +151,7 @@ set(UTIL_SRCS
   block_bloom_filter.cc
   bloom_filter.cc
   cache.cc
-  char_util.cc
+  #char_util.cc
   coding.cc
   condition_variable.cc
   cow_object.cc
@@ -167,7 +167,7 @@ set(UTIL_SRCS
   errno.cc
   faststring.cc
   fault_injection.cc
-  file_cache.cc
+  #file_cache.cc
   file_cache_metrics.cc
   flags.cc
   flag_tags.cc
@@ -190,7 +190,7 @@ set(UTIL_SRCS
   memory/overwrite.cc
   mem_tracker.cc
   metrics.cc
-  minidump.cc
+  #minidump.cc
   monotime.cc
   mutex.cc
   net/dns_resolver.cc
@@ -229,11 +229,13 @@ set(UTIL_SRCS
   trace_metrics.cc
   user.cc
   url-coding.cc
-  version_info.cc
+  # Remove from compilation, as it depends on generated method calls. Replaced by
+  # kudu_version.cc in Impala's common library.
+  #version_info.cc
   version_util.cc
   web_callback_registry.cc
   website_util.cc
-  yamlreader.cc
+  #yamlreader.cc
   zlib.cc
 )
 
@@ -277,7 +279,7 @@ set(UTIL_LIBS
   pb_util_proto
   protobuf
   version_info_proto
-  yaml
+  #yaml
   zlib)
 
 if(NOT APPLE)
@@ -320,27 +322,29 @@ ADD_EXPORTABLE_LIBRARY(kudu_util_compression
 # Define LZ4_DISABLE_DEPRECATE_WARNINGS to mute warnings like:
 # "'int LZ4_compress(const char*, char*, int)' is deprecated".
 target_compile_definitions(kudu_util_compression PUBLIC LZ4_DISABLE_DEPRECATE_WARNINGS)
-target_compile_definitions(kudu_util_compression_exported PUBLIC LZ4_DISABLE_DEPRECATE_WARNINGS)
+#target_compile_definitions(kudu_util_compression_exported PUBLIC LZ4_DISABLE_DEPRECATE_WARNINGS)
 
 #######################################
 # kudu_curl_util
 #######################################
-add_library(kudu_curl_util
-  curl_util.cc)
-target_link_libraries(kudu_curl_util
-  security
-  ${CURL_LIBRARIES}
-  glog
-  gutil)
+# Impala doesn't have curl in its toolchain. It relies instead on the system curl, which
+# is too old on some OSes we compile on and doesn't include macros Kudu needs.
+#add_library(kudu_curl_util
+#  curl_util.cc)
+#target_link_libraries(kudu_curl_util
+#  security
+#  ${CURL_LIBRARIES}
+#  glog
+#  gutil)
 
 #######################################
 # kudu_cloud_util
 #######################################
-add_library(kudu_cloud_util
-  cloud/instance_detector.cc
-  cloud/instance_metadata.cc)
-target_link_libraries(kudu_cloud_util
-  kudu_curl_util)
+#add_library(kudu_cloud_util
+#  cloud/instance_detector.cc
+#  cloud/instance_metadata.cc)
+#target_link_libraries(kudu_cloud_util
+#  kudu_curl_util)
 
 # See the comment in sanitizer_options.cc for details on this library's usage.
 # The top-level CMakeLists sets a ${SANITIZER_OPTIONS_OVERRIDE} variable which
@@ -375,7 +379,9 @@ add_library(kudu_test_util
 target_link_libraries(kudu_test_util
   gflags
   glog
-  gmock
+  # Impala doesn't have gmock in its toolchain
+  gtest
+  #gmock
   kudu_util)
 
 #######################################
@@ -404,7 +410,7 @@ endif()
 #######################################
 
 add_executable(protoc-gen-insertions protoc-gen-insertions.cc)
-target_link_libraries(protoc-gen-insertions gutil protobuf protoc ${KUDU_BASE_LIBS})
+target_link_libraries(protoc-gen-insertions gutil glog gflags protoc protobuf ${KUDU_BASE_LIBS})
 
 #######################################
 # Unit tests
@@ -565,17 +571,17 @@ endif()
 #######################################
 # curl_util-test
 #######################################
-ADD_KUDU_TEST(curl_util-test)
-if(NOT NO_TESTS)
-  target_link_libraries(curl_util-test
-    kudu_curl_util)
-endif()
+#ADD_KUDU_TEST(curl_util-test)
+#if(NOT NO_TESTS)
+#  target_link_libraries(curl_util-test
+#    kudu_curl_util)
+#endif()
 
 #######################################
 # instance_detector-test
 #######################################
-ADD_KUDU_TEST(cloud/instance_detector-test)
-if(NOT NO_TESTS)
-  target_link_libraries(instance_detector-test
-    kudu_cloud_util)
-endif()
+#ADD_KUDU_TEST(cloud/instance_detector-test)
+#if(NOT NO_TESTS)
+#  target_link_libraries(instance_detector-test
+#    kudu_cloud_util)
+#endif()
diff --git a/be/src/kudu/util/flags.cc b/be/src/kudu/util/flags.cc
index b4eaf7e..97519c8 100644
--- a/be/src/kudu/util/flags.cc
+++ b/be/src/kudu/util/flags.cc
@@ -73,9 +73,8 @@ DEFINE_bool(dump_metrics_json, false,
 TAG_FLAG(dump_metrics_json, hidden);
 
 #ifdef TCMALLOC_ENABLED
-DEFINE_bool(enable_process_lifetime_heap_profiling, false, "Enables heap "
-    "profiling for the lifetime of the process. Profile output will be stored in the "
-    "directory specified by -heap_profile_path.");
+// Defined in Impala in common/global-flags.cc
+DECLARE_bool(enable_process_lifetime_heap_profiling);
 TAG_FLAG(enable_process_lifetime_heap_profiling, stable);
 TAG_FLAG(enable_process_lifetime_heap_profiling, advanced);
 
diff --git a/be/src/kudu/util/kudu_export.h b/be/src/kudu/util/kudu_export.h
new file mode 100644
index 0000000..3cbdf11
--- /dev/null
+++ b/be/src/kudu/util/kudu_export.h
@@ -0,0 +1,62 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+// This file is generated during Kudu's build. Instead of recreating the necessary steps
+// in Impala's build process, we copy it into our repository. See
+// kudu/client/CMakeLists.txt in Kudu's repository for details.
+
+#ifndef KUDU_EXPORT_H
+#define KUDU_EXPORT_H
+
+#ifdef KUDU_STATIC_DEFINE
+#  define KUDU_EXPORT
+#  define KUDU_NO_EXPORT
+#else
+#  ifndef KUDU_EXPORT
+#    ifdef kudu_client_exported_EXPORTS
+        /* We are building this library */
+#      define KUDU_EXPORT __attribute__((visibility("default")))
+#    else
+        /* We are using this library */
+#      define KUDU_EXPORT __attribute__((visibility("default")))
+#    endif
+#  endif
+
+#  ifndef KUDU_NO_EXPORT
+#    define KUDU_NO_EXPORT __attribute__((visibility("hidden")))
+#  endif
+#endif
+
+#ifndef KUDU_DEPRECATED
+#  define KUDU_DEPRECATED __attribute__ ((__deprecated__))
+#endif
+
+#ifndef KUDU_DEPRECATED_EXPORT
+#  define KUDU_DEPRECATED_EXPORT KUDU_EXPORT KUDU_DEPRECATED
+#endif
+
+#ifndef KUDU_DEPRECATED_NO_EXPORT
+#  define KUDU_DEPRECATED_NO_EXPORT KUDU_NO_EXPORT KUDU_DEPRECATED
+#endif
+
+#if 0 /* DEFINE_NO_DEPRECATED */
+#  ifndef KUDU_NO_DEPRECATED
+#    define KUDU_NO_DEPRECATED
+#  endif
+#endif
+
+#endif
diff --git a/be/src/kudu/util/logging.cc b/be/src/kudu/util/logging.cc
index 36850c3..e366abc 100644
--- a/be/src/kudu/util/logging.cc
+++ b/be/src/kudu/util/logging.cc
@@ -50,9 +50,8 @@
 #include "kudu/util/signal.h"
 #include "kudu/util/status.h"
 
-DEFINE_string(log_filename, "",
-    "Prefix of log filename - "
-    "full path is <log_dir>/<log_filename>.[INFO|WARN|ERROR|FATAL]");
+// Defined in Impala.
+DECLARE_string(log_filename);
 TAG_FLAG(log_filename, stable);
 
 DEFINE_bool(log_async, true,
@@ -65,9 +64,8 @@ DEFINE_int32(log_async_buffer_bytes_per_level, 2 * 1024 * 1024,
              "level. Only relevant when --log_async is enabled.");
 TAG_FLAG(log_async_buffer_bytes_per_level, hidden);
 
-DEFINE_int32(max_log_files, 10,
-    "Maximum number of log files to retain per severity level. The most recent "
-    "log files are retained. If set to 0, all log files are retained.");
+// Defined in Impala.
+DECLARE_int32(max_log_files);
 TAG_FLAG(max_log_files, runtime);
 TAG_FLAG(max_log_files, experimental);
 
@@ -275,7 +273,8 @@ void InitGoogleLoggingSafe(const char* arg) {
   IgnoreSigPipe();
 
   // For minidump support. Must be called before logging threads started.
-  CHECK_OK(BlockSigUSR1());
+  // Disabled by Impala, which does not link Kudu's minidump library.
+  //CHECK_OK(BlockSigUSR1());
 
   if (FLAGS_log_async) {
     EnableAsyncLogging();
diff --git a/be/src/kudu/util/logging.h b/be/src/kudu/util/logging.h
index f8b03b5..6db2bba 100644
--- a/be/src/kudu/util/logging.h
+++ b/be/src/kudu/util/logging.h
@@ -162,7 +162,7 @@ class ScopedDisableRedaction {
       &google::LogMessage::SendToLog).stream()
 
 #define KLOG_EVERY_N_SECS(severity, n_secs) \
-  static logging::LogThrottler LOG_THROTTLER;  \
+  static ::kudu::logging::LogThrottler LOG_THROTTLER;                   \
   KLOG_EVERY_N_SECS_THROTTLER(severity, n_secs, LOG_THROTTLER, "no-tag")
 
 #define WARN_NOT_OK_EVERY_N_SECS(to_call, warning_prefix, n_secs) do {                 \
diff --git a/be/src/rpc/impala-service-pool.cc b/be/src/rpc/impala-service-pool.cc
index 74fe2df..a4f28cb 100644
--- a/be/src/rpc/impala-service-pool.cc
+++ b/be/src/rpc/impala-service-pool.cc
@@ -43,11 +43,10 @@
 #include "common/names.h"
 #include "common/status.h"
 
-METRIC_DEFINE_histogram(server, impala_incoming_queue_time,
-    "RPC Queue Time",
+METRIC_DEFINE_histogram(server, impala_incoming_queue_time, "RPC Queue Time",
     kudu::MetricUnit::kMicroseconds,
     "Number of microseconds incoming RPC requests spend in the worker queue",
-    60000000LU, 3);
+    kudu::MetricLevel::kInfo, 60000000LU, 3);
 
 using namespace rapidjson;
 
@@ -149,7 +148,7 @@ kudu::rpc::RpcMethodInfo* ImpalaServicePool::LookupMethod(
 }
 
 kudu::Status ImpalaServicePool::QueueInboundCall(
-    gscoped_ptr<kudu::rpc::InboundCall> call) {
+    std::unique_ptr<kudu::rpc::InboundCall> call) {
   kudu::rpc::InboundCall* c = call.release();
 
   vector<uint32_t> unsupported_features;
diff --git a/be/src/rpc/impala-service-pool.h b/be/src/rpc/impala-service-pool.h
index 7771358..01b4301 100644
--- a/be/src/rpc/impala-service-pool.h
+++ b/be/src/rpc/impala-service-pool.h
@@ -62,8 +62,8 @@ class ImpalaServicePool : public kudu::rpc::RpcService {
   virtual kudu::rpc::RpcMethodInfo* LookupMethod(const kudu::rpc::RemoteMethod& method)
     override;
 
-  virtual kudu::Status
-      QueueInboundCall(gscoped_ptr<kudu::rpc::InboundCall> call) OVERRIDE;
+  virtual kudu::Status QueueInboundCall(
+      std::unique_ptr<kudu::rpc::InboundCall> call) OVERRIDE;
 
   const std::string service_name() const;
 
diff --git a/be/src/runtime/io/data-cache.cc b/be/src/runtime/io/data-cache.cc
index e4d01a2..3e5a8fc 100644
--- a/be/src/runtime/io/data-cache.cc
+++ b/be/src/runtime/io/data-cache.cc
@@ -439,12 +439,14 @@ struct DataCache::CacheKey {
   faststring key_;
 };
 
-DataCache::Partition::Partition(const string& path, int64_t capacity,
-    int max_opened_files)
-  : path_(path), capacity_(max<int64_t>(capacity, PAGE_SIZE)),
+DataCache::Partition::Partition(
+    const string& path, int64_t capacity, int max_opened_files)
+  : path_(path),
+    capacity_(max<int64_t>(capacity, PAGE_SIZE)),
     max_opened_files_(max_opened_files),
-    meta_cache_(NewLRUCache(kudu::DRAM_CACHE, capacity_, path_)) {
-}
+    meta_cache_(
+        kudu::NewCache<kudu::Cache::EvictionPolicy::LRU, kudu::Cache::MemoryType::DRAM>(
+            capacity_, path_)) {}
 
 DataCache::Partition::~Partition() {
   if (!closed_) ReleaseResources();
@@ -550,18 +552,15 @@ int64_t DataCache::Partition::Lookup(const CacheKey& cache_key, int64_t bytes_to
     uint8_t* buffer) {
   DCHECK(!closed_);
   Slice key = cache_key.ToSlice();
-  kudu::Cache::Handle* handle =
-      meta_cache_->Lookup(key, kudu::Cache::EXPECT_IN_CACHE);
-
+  kudu::Cache::UniqueHandle handle(
+      meta_cache_->Lookup(key, kudu::Cache::EXPECT_IN_CACHE));
 
-  if (handle == nullptr) {
+  if (handle.get() == nullptr) {
     if (tracer_ != nullptr) {
       tracer_->Trace(Tracer::MISS, cache_key, bytes_to_read, /*entry_len=*/-1);
     }
     return 0;
   }
-  auto handle_release =
-      MakeScopeExitTrigger([this, &handle]() { meta_cache_->Release(handle); });
 
   // Read from the backing file.
   CacheEntry entry(meta_cache_->Value(handle));
@@ -589,7 +588,7 @@ int64_t DataCache::Partition::Lookup(const CacheKey& cache_key, int64_t bytes_to
 }
 
 bool DataCache::Partition::HandleExistingEntry(const Slice& key,
-    kudu::Cache::Handle* handle, const uint8_t* buffer, int64_t buffer_len) {
+    const kudu::Cache::UniqueHandle& handle, const uint8_t* buffer, int64_t buffer_len) {
   // Unpack the cache entry.
   CacheEntry entry(meta_cache_->Value(handle));
 
@@ -612,12 +611,9 @@ bool DataCache::Partition::InsertIntoCache(const Slice& key, CacheFile* cache_fi
   const int64_t charge_len = BitUtil::RoundUp(buffer_len, PAGE_SIZE);
 
   // Allocate a cache handle
-  kudu::Cache::PendingHandle* pending_handle =
-      meta_cache_->Allocate(key, sizeof(CacheEntry), charge_len);
-  if (UNLIKELY(pending_handle == nullptr)) return false;
-  auto release_pending_handle = MakeScopeExitTrigger([this, &pending_handle]() {
-    if (pending_handle != nullptr) meta_cache_->Free(pending_handle);
-  });
+  kudu::Cache::UniquePendingHandle pending_handle(
+      meta_cache_->Allocate(key, sizeof(CacheEntry), charge_len));
+  if (UNLIKELY(pending_handle.get() == nullptr)) return false;
 
   // Compute checksum if necessary.
   int64_t checksum = FLAGS_data_cache_checksum ? Checksum(buffer, buffer_len) : 0;
@@ -631,9 +627,8 @@ bool DataCache::Partition::InsertIntoCache(const Slice& key, CacheFile* cache_fi
 
   // Insert the new entry into the cache.
   CacheEntry entry(cache_file, insertion_offset, buffer_len, checksum);
-  memcpy(meta_cache_->MutableValue(pending_handle), &entry, sizeof(CacheEntry));
-  kudu::Cache::Handle* handle = meta_cache_->Insert(pending_handle, this);
-  meta_cache_->Release(handle);
+  memcpy(meta_cache_->MutableValue(&pending_handle), &entry, sizeof(CacheEntry));
+  kudu::Cache::UniqueHandle handle(meta_cache_->Insert(std::move(pending_handle), this));
   pending_handle = nullptr;
   ImpaladMetrics::IO_MGR_REMOTE_DATA_CACHE_TOTAL_BYTES->Increment(charge_len);
   return true;
@@ -648,11 +643,12 @@ bool DataCache::Partition::Store(const CacheKey& cache_key, const uint8_t* buffe
   if (charge_len > capacity_) return false;
 
   // Check for existing entry.
-  kudu::Cache::Handle* handle = meta_cache_->Lookup(key, kudu::Cache::EXPECT_IN_CACHE);
-  if (handle != nullptr) {
-    auto handle_release =
-        MakeScopeExitTrigger([this, &handle]() { meta_cache_->Release(handle); });
-    if (HandleExistingEntry(key, handle, buffer, buffer_len)) return false;
+  {
+    kudu::Cache::UniqueHandle handle(
+        meta_cache_->Lookup(key, kudu::Cache::EXPECT_IN_CACHE));
+    if (handle.get() != nullptr) {
+      if (HandleExistingEntry(key, handle, buffer, buffer_len)) return false;
+    }
   }
 
   CacheFile* cache_file;
diff --git a/be/src/runtime/io/data-cache.h b/be/src/runtime/io/data-cache.h
index 30cdaf8..fde1e97 100644
--- a/be/src/runtime/io/data-cache.h
+++ b/be/src/runtime/io/data-cache.h
@@ -324,8 +324,9 @@ class DataCache {
     /// Returns true iff the existing entry already covers the range of 'buffer' so no
     /// work needs to be done. Returns false otherwise. In which case, the existing entry
     /// will be overwritten.
-    bool HandleExistingEntry(const kudu::Slice& key, kudu::Cache::Handle* handle,
-        const uint8_t* buffer, int64_t buffer_len);
+    bool HandleExistingEntry(const kudu::Slice& key,
+        const kudu::Cache::UniqueHandle& handle, const uint8_t* buffer,
+        int64_t buffer_len);
 
     /// Helper function to insert a new entry with key 'key' into the LRU cache.
     /// The content in 'buffer' of length 'buffer_len' in bytes will be written to
diff --git a/be/src/util/webserver.cc b/be/src/util/webserver.cc
index deaf822..65f7555 100644
--- a/be/src/util/webserver.cc
+++ b/be/src/util/webserver.cc
@@ -165,6 +165,8 @@ string HttpStatusCodeToString(HttpStatusCode code) {
       return "200 OK";
     case HttpStatusCode::BadRequest:
       return "400 Bad Request";
+    case HttpStatusCode::AuthenticationRequired:
+      return "401 Authentication Required";
     case HttpStatusCode::NotFound:
       return "404 Not Found";
     case HttpStatusCode::LengthRequired:
diff --git a/bin/rat_exclude_files.txt b/bin/rat_exclude_files.txt
index 29798f8..c92142b 100644
--- a/bin/rat_exclude_files.txt
+++ b/bin/rat_exclude_files.txt
@@ -54,6 +54,7 @@ be/src/kudu/util/array_view.h
 be/src/kudu/util/cache-test.cc
 be/src/kudu/util/cache.cc
 be/src/kudu/util/cache.h
+be/src/kudu/util/cloud/instance_detector-test.cc
 be/src/kudu/util/coding.cc
 be/src/kudu/util/coding.h
 be/src/kudu/util/condition_variable.cc
@@ -102,6 +103,7 @@ shell/packaging/README.md
 testdata/*.csv
 testdata/*.sql
 testdata/*.test
+be/src/kudu/util/testdata/*.txt
 be/src/testutil/*.pem
 *.json
 fe/src/test/resources/*.xml
diff --git a/cmake_modules/kudu_cmake_fns.txt b/cmake_modules/kudu_cmake_fns.txt
index 20e281d..2c5250b 100644
--- a/cmake_modules/kudu_cmake_fns.txt
+++ b/cmake_modules/kudu_cmake_fns.txt
@@ -131,3 +131,12 @@ function(ADD_THIRDPARTY_LIB LIB_NAME)
       PROPERTIES IMPORTED_LINK_INTERFACE_LIBRARIES "${ARG_DEPS}")
   endif()
 endfunction()
+
+
+# This macro initializes KUDU_MIN_TEST_LIBS to KUDU_MIN_TEST_LIBS and
+# appends the passed list of libraries to the end. This ensures that
+# KUDU_MIN_TEST_LIBS is linked first.
+macro(SET_KUDU_TEST_LINK_LIBS)
+  set(KUDU_TEST_LINK_LIBS ${KUDU_MIN_TEST_LIBS})
+  list(APPEND KUDU_TEST_LINK_LIBS ${ARGN})
+endmacro()
diff --git a/tests/custom_cluster/test_restart_services.py b/tests/custom_cluster/test_restart_services.py
index ea92561..0b22141 100644
--- a/tests/custom_cluster/test_restart_services.py
+++ b/tests/custom_cluster/test_restart_services.py
@@ -250,7 +250,7 @@ class TestGracefulShutdown(CustomClusterTestSuite, HS2TestSuite):
         ":shutdown('e6c00ca5cd67b567eb96c6ecfb26f05')")
     assert "Could not find IPv4 address for:" in str(ex)
     ex = self.execute_query_expect_failure(self.client, ":shutdown('localhost:100000')")
-    assert "Invalid port:" in str(ex)
+    assert "invalid port:" in str(ex)
     assert ("This may be because the port specified is wrong.") not in str(ex)
 
     # Test that pointing to the wrong thrift service (the HS2 port) fails gracefully-ish.


[impala] 01/04: IMPALA-9336: [DOCS] Primary and foreign key constraint syntax

Posted by jo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

joemcdonnell pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit 4024d827a3e901fa22f340118e248e9cf192fe39
Author: Kris Hahn <kh...@cloudera.com>
AuthorDate: Fri Jan 31 15:29:11 2020 -0800

    IMPALA-9336: [DOCS] Primary and foreign key constraint syntax
    
    CREATE TABLE syntax for primary key and foreign keys spec
    
    Change-Id: Iee12da322fbdab7c671c17ceb8436bc3ace2b820
    Reviewed-on: http://gerrit.cloudera.org:8080/15146
    Reviewed-by: Thomas Tauber-Marshall <tm...@cloudera.com>
    Tested-by: Thomas Tauber-Marshall <tm...@cloudera.com>
---
 docs/topics/impala_create_table.xml | 39 +++++++++++++++++++++++++++++++------
 1 file changed, 33 insertions(+), 6 deletions(-)

diff --git a/docs/topics/impala_create_table.xml b/docs/topics/impala_create_table.xml
index fc50df7..3cbcc25 100644
--- a/docs/topics/impala_create_table.xml
+++ b/docs/topics/impala_create_table.xml
@@ -85,6 +85,7 @@ under the License.
 
 <codeblock>CREATE [EXTERNAL] TABLE [IF NOT EXISTS] [<varname>db_name</varname>.]<varname>table_name</varname>
   (<varname>col_name</varname> <varname>data_type</varname>
+    [<varname>constraint_specification</varname>]
     [COMMENT '<varname>col_comment</varname>']
     [, ...]
   )
@@ -141,6 +142,13 @@ array_type: ARRAY &lt; <varname>primitive_or_complex_type</varname> &gt;
 
 map_type: MAP &lt; <varname>primitive_type</varname>, <varname>primitive_or_complex_type</varname> &gt;
 </ph>
+
+constraint_specification:
+  PRIMARY KEY (<varname>col_name</varname>, ...) [DISABLE] [NOVALIDATE] [RELY], [<varname>foreign_key_specification</varname>, ...]
+
+foreign_key_specification:
+  FOREIGN KEY (<varname>col_name</varname>, ...) REFERENCES table_name(<varname>col_name</varname>, ...) [DISABLE] [NOVALIDATE] [RELY]
+
 row_format:
   DELIMITED [FIELDS TERMINATED BY '<varname>char</varname>' [ESCAPED BY '<varname>char</varname>']]
   [LINES TERMINATED BY '<varname>char</varname>']
@@ -275,12 +283,31 @@ AS
       the source table, query, or data file.
     </p>
 
-    <p>
-      With the basic <codeph>CREATE TABLE</codeph> syntax, you must list one or more columns,
-      its name, type, and optionally a comment, in addition to any columns used as partitioning
-      keys. There is one exception where the column list is not required: when creating an Avro
-      table with the <codeph>STORED AS AVRO</codeph> clause, you can omit the list of columns
-      and specify the same metadata as part of the <codeph>TBLPROPERTIES</codeph> clause.
+    <p> With the basic <codeph>CREATE TABLE</codeph> syntax, you must list one or more columns, its
+      name, type, optionally constraints, and optionally a comment, in addition to any columns used
+      as partitioning keys. There is one exception where the column list is not required: when
+      creating an Avro table with the <codeph>STORED AS AVRO</codeph> clause, you can omit the list
+      of columns and specify the same metadata as part of the <codeph>TBLPROPERTIES</codeph> clause. </p>
+
+    <p rev="3.4.0">
+      <b>Constraints:</b>
+    </p>
+    <p>Constraints are advisory and intended for estimating cardinality during query planning in a
+      future release; there is no attempt to enforce constraints. Add primary and foreign key
+      information after column definitions. Do not include a constraint name; the constraint name is
+      generated internally as a UUID. The following constraint states are supported: <ul
+        id="ul_gbz_3kl_4kb">
+        <li>DISABLE</li>
+        <li>NOVALIDATE</li>
+        <li>RELY</li>
+      </ul>The ENABLE, VALIDATE, and NORELY options are not supported. The foreign key must be
+      defined as the primary key in the referenced table. </p>
+    <p> Constraint examples: <codeblock>CREATE TABLE pk(col1 INT, col2 STRING, PRIMARY KEY(col1, col2));</codeblock>
+      <codeblock>CREATE TABLE fk(id INT, col1 INT, col2 STRING, PRIMARY KEY(id),
+  FOREIGN KEY(col1, col2) REFERENCES pk(col1, col2));</codeblock>
+      <codeblock>CREATE TABLE pk(id INT, PRIMARY KEY(id) DISABLE, NOVALIDATE, RELY);</codeblock>
+      <codeblock>CREATE TABLE fk(id INT, col1 INT, col2 STRING, PRIMARY KEY(id),
+  FOREIGN KEY(col1, col2) REFERENCES pk(col1, col2));</codeblock>
     </p>
 
     <p conref="../shared/impala_common.xml#common/complex_types_blurb"/>