diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..c6d4e7b553
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.sw*
+build/
+gen-cpp2/
diff --git a/CMake/FindDoubleConversion.cmake b/CMake/FindDoubleConversion.cmake
new file mode 100644
index 0000000000..b4a5d92255
--- /dev/null
+++ b/CMake/FindDoubleConversion.cmake
@@ -0,0 +1,36 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Finds libdouble-conversion.
+#
+# This module defines:
+# DOUBLE_CONVERSION_INCLUDE_DIR
+# DOUBLE_CONVERSION_LIBRARY
+#
+
+find_path(DOUBLE_CONVERSION_INCLUDE_DIR double-conversion/double-conversion.h)
+find_library(DOUBLE_CONVERSION_LIBRARY NAMES double-conversion)
+
+include(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(
+    DOUBLE_CONVERSION DEFAULT_MSG
+    DOUBLE_CONVERSION_LIBRARY DOUBLE_CONVERSION_INCLUDE_DIR)
+
+if (NOT DOUBLE_CONVERSION_FOUND)
+  message(STATUS "Using third-party bundled double-conversion")
+else()
+  message(STATUS "Found double-conversion: ${DOUBLE_CONVERSION_LIBRARY}")
+endif (NOT DOUBLE_CONVERSION_FOUND)
+
+mark_as_advanced(DOUBLE_CONVERSION_INCLUDE_DIR DOUBLE_CONVERSION_LIBRARY)
diff --git a/CMake/FindFBThrift.cmake b/CMake/FindFBThrift.cmake
new file mode 100644
index 0000000000..5f0fb769bf
--- /dev/null
+++ b/CMake/FindFBThrift.cmake
@@ -0,0 +1,101 @@
+#
+# - Try to find Facebook fbthrift library
+# This will define
+# FBTHRIFT_FOUND
+# FBTHRIFT_INCLUDE_DIR
+# FBTHRIFT_LIBRARIES
+#
+
+find_package(OpenSSL REQUIRED)
+
+find_path(
+    FBTHRIFT_INCLUDE_DIR
+    NAMES "thrift/lib/cpp2/Thrift.h"
+    HINTS
+        "/usr/local/include"
+)
+find_file(
+    FBTHRIFT_LIBRARY_FILE
+    NAMES "ThriftLibrary.cmake"
+    HINTS
+        "/usr/local/include/thrift"
+)
+
+find_file(
+    THRIFT1
+    NAMES "thrift1"
+    HINTS
+        "/usr/local/bin"
+)
+
+find_library(
+    FBTHRIFT_CORE_LIBRARY
+    NAMES thrift-core
+    HINTS
+        "/usr/local/lib"
+)
+
+find_library(
+    THRIFTCPP2 
+    NAMES thriftcpp2
+    HINTS
+        "/usr/local/lib"
+)
+
+find_library(
+    FBTHRIFT_PROTOCOL_LIBRARY
+    NAMES thriftprotocol
+    HINTS
+        "/usr/local/lib"
+)
+find_library(
+    FBTHRIFT_PROTOCOL_LIBRARY_IMPL
+    NAMES protocol
+    HINTS
+        "/usr/local/lib"
+)
+
+find_library(
+    FBTHRIFT_FROZEN2_LIBRARY
+    NAMES thriftfrozen2
+    HINTS
+        "/usr/local/lib"
+)
+
+find_library(
+    FBTHRIFT_METADATA_LIBRARY
+    NAMES thriftmetadata
+    HINTS
+        "/usr/local/lib"
+)
+
+find_library(
+    FBTHRIFT_TRANSPORT_LIBRARY
+    NAMES transport
+    HINTS
+        "/usr/local/lib"
+)
+set(FBTHRIFT_LIBRARIES
+    ${FBTHRIFT_CORE_LIBRARY}
+    ${THRIFTCPP2}
+    ${FBTHRIFT_FROZEN2_LIBRARY}
+    ${OPENSSL_LIBRARIES}
+    ${FBTHRIFT_METADATA_LIBRARY}
+    ${FBTHRIFT_PROTOCOL_LIBRARY}
+    ${FBTHRIFT_PROTOCOL_LIBRARY_IMPL}
+    ${FBTHRIFT_TRANSPORT_LIBRARY}
+
+
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(
+    FBTHRIFT DEFAULT_MSG FBTHRIFT_INCLUDE_DIR FBTHRIFT_LIBRARIES)
+include(${FBTHRIFT_LIBRARY_FILE})
+mark_as_advanced(FBTHRIFT_INCLUDE_DIR FBTHRIFT_LIBRARIES FBTHRIFT_FOUND FBTHRIFT_LIBRARY_FILE)
+
+if(FBTHRIFT_FOUND AND NOT FBTHRIFT_FIND_QUIETLY)
+    message(STATUS "FBTHRIFT: ${FBTHRIFT_INCLUDE_DIR}")
+    message(STATUS "FBTHRIFT: ${THRIFTCPP2}")
+
+endif()
diff --git a/CMake/FindFmt.cmake b/CMake/FindFmt.cmake
new file mode 100644
index 0000000000..3e14a9c2c0
--- /dev/null
+++ b/CMake/FindFmt.cmake
@@ -0,0 +1,44 @@
+find_path(fmt_INCLUDE_DIR NAMES fmt/format.h)
+
+if(fmt_INCLUDE_DIR)
+  set(_fmt_version_file "${fmt_INCLUDE_DIR}/fmt/core.h")
+  if(NOT EXISTS "${_fmt_version_file}")
+    set(_fmt_version_file "${fmt_INCLUDE_DIR}/fmt/format.h")
+  endif()
+  if(EXISTS "${_fmt_version_file}")
+    # parse "#define FMT_VERSION 40100" to 4.1.0
+    file(STRINGS "${_fmt_version_file}" fmt_VERSION_LINE
+      REGEX "^#define[ \t]+FMT_VERSION[ \t]+[0-9]+$")
+    string(REGEX REPLACE "^#define[ \t]+FMT_VERSION[ \t]+([0-9]+)$"
+      "\\1" fmt_VERSION "${fmt_VERSION_LINE}")
+    foreach(ver "fmt_VERSION_PATCH" "fmt_VERSION_MINOR" "fmt_VERSION_MAJOR")
+      math(EXPR ${ver} "${fmt_VERSION} % 100")
+      math(EXPR fmt_VERSION "(${fmt_VERSION} - ${${ver}}) / 100")
+    endforeach()
+    set(fmt_VERSION
+      "${fmt_VERSION_MAJOR}.${fmt_VERSION_MINOR}.${fmt_VERSION_PATCH}")
+  endif()
+endif()
+
+find_library(fmt_LIBRARY NAMES fmt)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(fmt
+  REQUIRED_VARS fmt_INCLUDE_DIR fmt_LIBRARY
+  VERSION_VAR fmt_VERSION)
+mark_as_advanced(
+  fmt_INCLUDE_DIR
+  fmt_LIBRARY
+  fmt_VERSION_MAJOR
+  fmt_VERSION_MINOR
+  fmt_VERSION_PATCH
+  fmt_VERSION_STRING)
+
+if(fmt_FOUND AND NOT (TARGET fmt::fmt))
+  add_library(fmt::fmt UNKNOWN IMPORTED)
+  set_target_properties(fmt::fmt PROPERTIES
+    INTERFACE_INCLUDE_DIRECTORIES "${fmt_INCLUDE_DIR}"
+    IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
+    IMPORTED_LOCATION "${fmt_LIBRARY}")
+endif()
+
diff --git a/CMake/FindGflags.cmake b/CMake/FindGflags.cmake
new file mode 100755
index 0000000000..246ceacdd0
--- /dev/null
+++ b/CMake/FindGflags.cmake
@@ -0,0 +1,81 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+# Find libgflags.
+# There's a lot of compatibility cruft going on in here, both
+# to deal with changes across the FB consumers of this and also
+# to deal with variances in behavior of cmake itself.
+#
+# Since this file is named FindGflags.cmake the cmake convention
+# is for the module to export both GFLAGS_FOUND and Gflags_FOUND.
+# The convention expected by consumers is that we export the
+# following variables, even though these do not match the cmake
+# conventions:
+#
+#  LIBGFLAGS_INCLUDE_DIR - where to find gflags/gflags.h, etc.
+#  LIBGFLAGS_LIBRARY     - List of libraries when using libgflags.
+#  LIBGFLAGS_FOUND       - True if libgflags found.
+#
+# We need to be able to locate gflags both from an installed
+# cmake config file and just from the raw headers and libs, so
+# test for the former and then the latter, and then stick
+# the results together and export them into the variables
+# listed above.
+#
+# For forwards compatibility, we export the following variables:
+#
+#  gflags_INCLUDE_DIR - where to find gflags/gflags.h, etc.
+#  gflags_TARGET / GFLAGS_TARGET / gflags_LIBRARIES
+#                     - List of libraries when using libgflags.
+#  gflags_FOUND       - True if libgflags found.
+#
+
+IF (LIBGFLAGS_INCLUDE_DIR)
+  # Already in cache, be silent
+  SET(Gflags_FIND_QUIETLY TRUE)
+ENDIF ()
+
+find_package(gflags CONFIG QUIET)
+if (gflags_FOUND)
+  if (NOT Gflags_FIND_QUIETLY)
+    message(STATUS "Found gflags from package config ${gflags_CONFIG}")
+  endif()
+  # Re-export the config-specified libs with our local names
+  set(LIBGFLAGS_LIBRARY ${gflags_LIBRARIES})
+  set(LIBGFLAGS_INCLUDE_DIR ${gflags_INCLUDE_DIR})
+  set(LIBGFLAGS_FOUND ${gflags_FOUND})
+  # cmake module compat
+  set(GFLAGS_FOUND ${gflags_FOUND})
+  set(Gflags_FOUND ${gflags_FOUND})
+else()
+  FIND_PATH(LIBGFLAGS_INCLUDE_DIR gflags/gflags.h)
+
+  FIND_LIBRARY(LIBGFLAGS_LIBRARY_DEBUG NAMES gflagsd gflags_staticd)
+  FIND_LIBRARY(LIBGFLAGS_LIBRARY_RELEASE NAMES gflags gflags_static)
+
+  INCLUDE(SelectLibraryConfigurations)
+  SELECT_LIBRARY_CONFIGURATIONS(LIBGFLAGS)
+
+  # handle the QUIETLY and REQUIRED arguments and set LIBGFLAGS_FOUND to TRUE if
+  # all listed variables are TRUE
+  INCLUDE(FindPackageHandleStandardArgs)
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(gflags DEFAULT_MSG LIBGFLAGS_LIBRARY LIBGFLAGS_INCLUDE_DIR)
+  # cmake module compat
+  set(Gflags_FOUND ${GFLAGS_FOUND})
+  # compat with some existing FindGflags consumers
+  set(LIBGFLAGS_FOUND ${GFLAGS_FOUND})
+
+  # Compat with the gflags CONFIG based detection
+  set(gflags_FOUND ${GFLAGS_FOUND})
+  set(gflags_INCLUDE_DIR ${LIBGFLAGS_INCLUDE_DIR})
+  set(gflags_LIBRARIES ${LIBGFLAGS_LIBRARY})
+  set(GFLAGS_TARGET ${LIBGFLAGS_LIBRARY})
+  set(gflags_TARGET ${LIBGFLAGS_LIBRARY})
+
+  MARK_AS_ADVANCED(LIBGFLAGS_LIBRARY LIBGFLAGS_INCLUDE_DIR)
+endif()
+
+# Compat with the gflags CONFIG based detection
+if (LIBGFLAGS_FOUND AND NOT TARGET gflags)
+  add_library(gflags UNKNOWN IMPORTED)
+  set_target_properties(gflags PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${LIBGFLAGS_INCLUDE_DIR}")
+  set_target_properties(gflags PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION "${LIBGFLAGS_LIBRARY}")
+endif()
diff --git a/CMake/FindGlog.cmake b/CMake/FindGlog.cmake
new file mode 100644
index 0000000000..ee581529a1
--- /dev/null
+++ b/CMake/FindGlog.cmake
@@ -0,0 +1,22 @@
+#
+# Find libglog
+#
+#  LIBGLOG_INCLUDE_DIR - where to find glog/logging.h, etc.
+#  LIBGLOG_LIBRARY     - List of libraries when using libglog.
+#  LIBGLOG_FOUND       - True if libglog found.
+#
+
+find_path(LIBGLOG_INCLUDE_DIR glog/logging.h)
+find_library(LIBGLOG_LIBRARY glog)
+
+set(LIBGLOG_LIBRARIES ${LIBGLOG_LIBRARY})
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(
+        LIBGLOG DEFAULT_MSG LIBGLOG_INCLUDE_DIR LIBGLOG_LIBRARIES)
+
+mark_as_advanced(LIBGLOG_INCLUDE_DIR LIBGLOG_LIBRARIES LIBGLOG_FOUND)
+
+if(LIBGLOG_FOUND AND NOT LIBGLOG_FIND_QUIETLY)
+    message(STATUS "LIBGLOG: ${LIBGLOG_LIBRARIES}")
+endif()
diff --git a/CMake/FindGmock.cmake b/CMake/FindGmock.cmake
new file mode 100644
index 0000000000..f27b4ce9e1
--- /dev/null
+++ b/CMake/FindGmock.cmake
@@ -0,0 +1,128 @@
+# Locate the Google C++ Mocking Framework.
+# (This file is almost an identical copy of the original FindGTest.cmake file,
+#  feel free to use it as it is or modify it for your own needs.)
+#
+#
+# Defines the following variables:
+#
+#   GMOCK_FOUND - Found the Google Testing framework
+#   GMOCK_INCLUDE_DIRS - Include directories
+#
+# Also defines the library variables below as normal
+# variables. These contain debug/optimized keywords when
+# a debugging library is found.
+#
+#   GMOCK_BOTH_LIBRARIES - Both libgmock & libgmock-main
+#   GMOCK_LIBRARIES - libgmock
+#   GMOCK_MAIN_LIBRARIES - libgmock-main
+#
+# Accepts the following variables as input:
+#
+#   GMOCK_ROOT - (as a CMake or environment variable)
+#                The root directory of the gmock install prefix
+#
+#   GMOCK_MSVC_SEARCH - If compiling with MSVC, this variable can be set to
+#                       "MD" or "MT" to enable searching a gmock build tree
+#                       (defaults: "MD")
+#
+#-----------------------
+# Example Usage:
+#
+#    find_package(GMock REQUIRED)
+#    include_directories(${GMOCK_INCLUDE_DIRS})
+#
+#    add_executable(foo foo.cc)
+#    target_link_libraries(foo ${GMOCK_BOTH_LIBRARIES})
+#
+#=============================================================================
+# This file is released under the MIT licence:
+#
+# Copyright (c) 2011 Matej Svec
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#=============================================================================
+
+
+function(_gmock_append_debugs _endvar _library)
+  if(${_library} AND ${_library}_DEBUG)
+      set(_output optimized ${${_library}} debug ${${_library}_DEBUG})
+        else()
+	    set(_output ${${_library}})
+	      endif()
+	        set(${_endvar} ${_output} PARENT_SCOPE)
+		endfunction()
+
+function(_gmock_find_library _name)
+  find_library(${_name}
+      NAMES ${ARGN}
+          HINTS
+	        $ENV{GMOCK_ROOT}
+		      ${GMOCK_ROOT}
+		          PATH_SUFFIXES ${_gmock_libpath_suffixes}
+			    )
+			      mark_as_advanced(${_name})
+			      endfunction()
+
+
+if(NOT DEFINED GMOCK_MSVC_SEARCH)
+  set(GMOCK_MSVC_SEARCH MD)
+  endif()
+
+set(_gmock_libpath_suffixes lib)
+if(MSVC)
+  if(GMOCK_MSVC_SEARCH STREQUAL "MD")
+      list(APPEND _gmock_libpath_suffixes
+            msvc/gmock-md/Debug
+	          msvc/gmock-md/Release)
+		    elseif(GMOCK_MSVC_SEARCH STREQUAL "MT")
+		        list(APPEND _gmock_libpath_suffixes
+			      msvc/gmock/Debug
+			            msvc/gmock/Release)
+				      endif()
+				      endif()
+
+find_path(GMOCK_INCLUDE_DIR gmock/gmock.h
+  HINTS
+      $ENV{GMOCK_ROOT}/include
+          ${GMOCK_ROOT}/include
+	  )
+	  mark_as_advanced(GMOCK_INCLUDE_DIR)
+
+if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD")
+  # The provided /MD project files for Google Mock add -md suffixes to the
+    # library names.
+      _gmock_find_library(GMOCK_LIBRARY            gmock-md  gmock)
+        _gmock_find_library(GMOCK_LIBRARY_DEBUG      gmock-mdd gmockd)
+	  _gmock_find_library(GMOCK_MAIN_LIBRARY       gmock_main-md  gmock_main)
+	    _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_main-mdd gmock_maind)
+	    else()
+	      _gmock_find_library(GMOCK_LIBRARY            gmock)
+	        _gmock_find_library(GMOCK_LIBRARY_DEBUG      gmockd)
+		  _gmock_find_library(GMOCK_MAIN_LIBRARY       gmock_main)
+		    _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_maind)
+		    endif()
+
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMock DEFAULT_MSG GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY)
+
+if(GMOCK_FOUND)
+  set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR})
+    _gmock_append_debugs(GMOCK_LIBRARIES      GMOCK_LIBRARY)
+      _gmock_append_debugs(GMOCK_MAIN_LIBRARIES GMOCK_MAIN_LIBRARY)
+        set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARIES} ${GMOCK_MAIN_LIBRARIES})
+	endif()
\ No newline at end of file
diff --git a/CMake/FindLibEvent.cmake b/CMake/FindLibEvent.cmake
new file mode 100644
index 0000000000..c85ad2adf9
--- /dev/null
+++ b/CMake/FindLibEvent.cmake
@@ -0,0 +1,38 @@
+# - Find LibEvent (a cross event library)
+# This module defines
+# LIBEVENT_INCLUDE_DIR, where to find LibEvent headers
+# LIBEVENT_LIB, LibEvent libraries
+# LibEvent_FOUND, If false, do not try to use libevent
+
+set(LibEvent_EXTRA_PREFIXES /usr/local /opt/local "$ENV{HOME}")
+foreach(prefix ${LibEvent_EXTRA_PREFIXES})
+  list(APPEND LibEvent_INCLUDE_PATHS "${prefix}/include")
+    list(APPEND LibEvent_LIB_PATHS "${prefix}/lib")
+    endforeach()
+
+find_path(LIBEVENT_INCLUDE_DIR event.h PATHS ${LibEvent_INCLUDE_PATHS})
+find_library(LIBEVENT_LIB NAMES event PATHS ${LibEvent_LIB_PATHS})
+find_library(LIBEVENT_PTHREAD_LIB NAMES event_pthreads PATHS ${LibEvent_LIB_PATHS})
+
+if (LIBEVENT_LIB AND LIBEVENT_INCLUDE_DIR AND LIBEVENT_PTHREAD_LIB)
+  set(LibEvent_FOUND TRUE)
+    set(LIBEVENT_LIB ${LIBEVENT_LIB} ${LIBEVENT_PTHREAD_LIB})
+    else ()
+      set(LibEvent_FOUND FALSE)
+      endif ()
+
+if (LibEvent_FOUND)
+  if (NOT LibEvent_FIND_QUIETLY)
+      message(STATUS "Found libevent: ${LIBEVENT_LIB}")
+        endif ()
+	else ()
+	  if (LibEvent_FIND_REQUIRED)
+	      message(FATAL_ERROR "Could NOT find libevent and libevent_pthread.")
+	        endif ()
+		  message(STATUS "libevent and libevent_pthread NOT found.")
+		  endif ()
+
+mark_as_advanced(
+    LIBEVENT_LIB
+        LIBEVENT_INCLUDE_DIR
+	  )
diff --git a/CMake/FindLibiberty.cmake b/CMake/FindLibiberty.cmake
new file mode 100644
index 0000000000..32f9d54edb
--- /dev/null
+++ b/CMake/FindLibiberty.cmake
@@ -0,0 +1,29 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+find_path(LIBIBERTY_INCLUDE_DIR NAMES libiberty.h PATH_SUFFIXES libiberty)
+mark_as_advanced(LIBIBERTY_INCLUDE_DIR)
+
+find_library(LIBIBERTY_LIBRARY NAMES iberty)
+mark_as_advanced(LIBIBERTY_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(
+  LIBIBERTY
+  REQUIRED_VARS LIBIBERTY_LIBRARY LIBIBERTY_INCLUDE_DIR)
+
+if(LIBIBERTY_FOUND)
+  set(LIBIBERTY_LIBRARIES ${LIBIBERTY_LIBRARY})
+  set(LIBIBERTY_INCLUDE_DIRS ${LIBIBERTY_INCLUDE_DIR})
+endif()
diff --git a/CMake/FindRT.cmake b/CMake/FindRT.cmake
new file mode 100644
index 0000000000..17d5df81db
--- /dev/null
+++ b/CMake/FindRT.cmake
@@ -0,0 +1,20 @@
+# Try to find real time libraries
+# Once done, this will define
+#
+# RT_FOUND - system has rt library
+# RT_LIBRARIES - rt libraries directory
+#
+# Source: https://gitlab.cern.ch/dss/eos/commit/44070e575faaa46bd998708ef03eedb381506ff0
+#
+
+if(RT_LIBRARIES)
+    set(RT_FIND_QUIETLY TRUE)
+endif(RT_LIBRARIES)
+
+find_library(RT_LIBRARY rt)
+set(RT_LIBRARIES ${RT_LIBRARY})
+# handle the QUIETLY and REQUIRED arguments and set
+# RT_FOUND to TRUE if all listed variables are TRUE
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(rt DEFAULT_MSG RT_LIBRARY)
+mark_as_advanced(RT_LIBRARY)
diff --git a/CMake/ThriftLibrary.cmake b/CMake/ThriftLibrary.cmake
new file mode 100644
index 0000000000..0b98a46db4
--- /dev/null
+++ b/CMake/ThriftLibrary.cmake
@@ -0,0 +1,283 @@
+# Copyright (c) Facebook, Inc. and its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Requirements:
+# Please provide the following two variables before using these macros:
+#   ${THRIFT1} - path/to/bin/thrift1
+#   ${THRIFTCPP2} - path/to/lib/thriftcpp2
+#
+
+#
+# thrift_object
+# This creates a object that will contain the source files and all the proper
+# dependencies to generate and compile thrift generated files
+#
+# Params:
+#   @file_name - The name of the thrift file
+#   @services  - A list of services that are declared in the thrift file
+#   @language  - The generator to use (cpp or cpp2)
+#   @options   - Extra options to pass to the generator
+#   @file_path - The directory where the thrift file lives
+#   @output_path - The directory where the thrift objects will be built
+#   @include_prefix - The last part of output_path, relative include prefix
+#
+# Output:
+#  A object file named `${file-name}-${language}-obj` to include into your
+#  project's library
+#
+# Notes:
+# If any of the fields is empty, it is still required to provide an empty string
+#
+# Usage:
+#   thrift_object(
+#     #file_name
+#     #services
+#     #language
+#     #options
+#     #file_path
+#     #output_path
+#     #include_prefix
+#   )
+#   add_library(somelib $<TARGET_OBJECTS:${file_name}-${language}-obj> ...)
+#
+
+macro(thrift_object
+  file_name
+  services
+  language
+  options
+  file_path
+  output_path
+  include_prefix
+)
+  thrift_generate(
+    "${file_name}"
+    "${services}"
+    "${language}"
+    "${options}"
+    "${file_path}"
+    "${output_path}"
+    "${include_prefix}"
+    "${ARGN}"
+  )
+  bypass_source_check(${${file_name}-${language}-SOURCES})
+  add_library(
+    "${file_name}-${language}-obj"
+    OBJECT
+    ${${file_name}-${language}-SOURCES}
+  )
+  add_dependencies(
+    "${file_name}-${language}-obj"
+    "${file_name}-${language}-target"
+  )
+  message("Thrift will create the Object file : ${file_name}-${language}-obj")
+endmacro()
+
+# thrift_library
+# Same as thrift object in terms of usage but creates the library instead of
+# object so that you can use to link against your library instead of including
+# all symbols into your library
+#
+# Params:
+#   @file_name - The name of the thrift file
+#   @services  - A list of services that are declared in the thrift file
+#   @language  - The generator to use (cpp or cpp2)
+#   @options   - Extra options to pass to the generator
+#   @file_path - The directory where the thrift file lives
+#   @output_path - The directory where the thrift objects will be built
+#   @include_prefix - The last part of output_path, relative include prefix
+#
+# Output:
+#  A library file named `${file-name}-${language}` to link against your
+#  project's library
+#
+# Notes:
+# If any of the fields is empty, it is still required to provide an empty string
+#
+# Usage:
+#   thrift_library(
+#     #file_name
+#     #services
+#     #language
+#     #options
+#     #file_path
+#     #output_path
+#     #include_prefix
+#   )
+#   add_library(somelib ...)
+#   target_link_libraries(somelibe ${file_name}-${language} ...)
+#
+
+macro(thrift_library
+  file_name
+  services
+  language
+  options
+  file_path
+  output_path
+  include_prefix
+)
+  thrift_object(
+    "${file_name}"
+    "${services}"
+    "${language}"
+    "${options}"
+    "${file_path}"
+    "${output_path}"
+    "${include_prefix}"
+    "${ARGN}"
+  )
+  add_library(
+    "${file_name}-${language}"
+    $<TARGET_OBJECTS:${file_name}-${language}-obj>
+  )
+  target_link_libraries("${file_name}-${language}" ${THRIFTCPP2})
+  message("Thrift will create the Library file : ${file_name}-${language}")
+endmacro()
+
+#
+# bypass_source_check
+# This tells cmake to ignore if it doesn't see the following sources in
+# the library that will be installed. Thrift files are generated at compile
+# time so they do not exist at source check time
+#
+# Params:
+#   @sources - The list of files to ignore in source check
+#
+
+macro(bypass_source_check sources)
+  set_source_files_properties(
+    ${sources}
+    PROPERTIES GENERATED TRUE
+  )
+endmacro()
+
+#
+# thrift_generate
+# This is used to codegen thrift files using the thrift compiler
+# Params:
+#   @file_name - The name of tge thrift file
+#   @services  - A list of services that are declared in the thrift file
+#   @language  - The generator to use (cpp, cpp2 or py3)
+#   @options   - Extra options to pass to the generator
+#   @output_path - The directory where the thrift file lives
+#
+# Output:
+#  file-language-target     - A custom target to add a dependenct
+#  ${file-language-HEADERS} - The generated Header Files.
+#  ${file-language-SOURCES} - The generated Source Files.
+#
+# Notes:
+# If any of the fields is empty, it is still required to provide an empty string
+#
+# When using file_language-SOURCES it should always call:
+#   bypass_source_check(${file_language-SOURCES})
+# This will prevent cmake from complaining about missing source files
+#
+
+macro(thrift_generate
+  file_name
+  services
+  language
+  options
+  file_path
+  output_path
+  include_prefix
+)
+  cmake_parse_arguments(THRIFT_GENERATE   # Prefix
+    "" # Options
+    "" # One Value args
+    "THRIFT_INCLUDE_DIRECTORIES" # Multi-value args
+    "${ARGN}")
+
+  set(thrift_include_directories)
+  foreach(dir ${THRIFT_GENERATE_THRIFT_INCLUDE_DIRECTORIES})
+    list(APPEND thrift_include_directories "-I" "${dir}")
+  endforeach()
+
+  set("${file_name}-${language}-HEADERS"
+    ${output_path}/gen-${language}/${file_name}_constants.h
+    ${output_path}/gen-${language}/${file_name}_data.h
+    ${output_path}/gen-${language}/${file_name}_metadata.h
+    ${output_path}/gen-${language}/${file_name}_types.h
+    ${output_path}/gen-${language}/${file_name}_types.tcc
+  )
+  set("${file_name}-${language}-SOURCES"
+    ${output_path}/gen-${language}/${file_name}_constants.cpp
+    ${output_path}/gen-${language}/${file_name}_data.cpp
+    ${output_path}/gen-${language}/${file_name}_types.cpp
+  )
+  if(NOT "${options}" MATCHES "no_metadata")
+    set("${file_name}-${language}-SOURCES"
+      ${${file_name}-${language}-SOURCES}
+      ${output_path}/gen-${language}/${file_name}_metadata.cpp
+    )
+  endif()
+  foreach(service ${services})
+    set("${file_name}-${language}-HEADERS"
+      ${${file_name}-${language}-HEADERS}
+      ${output_path}/gen-${language}/${service}.h
+      ${output_path}/gen-${language}/${service}.tcc
+      ${output_path}/gen-${language}/${service}AsyncClient.h
+      ${output_path}/gen-${language}/${service}_custom_protocol.h
+    )
+    set("${file_name}-${language}-SOURCES"
+      ${${file_name}-${language}-SOURCES}
+      ${output_path}/gen-${language}/${service}.cpp
+      ${output_path}/gen-${language}/${service}AsyncClient.cpp
+    )
+  endforeach()
+  if("${include_prefix}" STREQUAL "")
+    set(include_prefix_text "")
+  else()
+    set(include_prefix_text "include_prefix=${include_prefix}")
+    if(NOT "${options}" STREQUAL "")
+      set(include_prefix_text ",${include_prefix_text}")
+    endif()
+  endif()
+  set(gen_language ${language})
+  if("${language}" STREQUAL "cpp2")
+    set(gen_language "mstch_cpp2")
+  elseif("${language}" STREQUAL "py3")
+    set(gen_language "mstch_py3")
+    file(WRITE "${output_path}/gen-${language}/${file_name}/__init__.py")
+  endif()
+  add_custom_command(
+    OUTPUT ${${file_name}-${language}-HEADERS}
+      ${${file_name}-${language}-SOURCES}
+    COMMAND ${THRIFT1}
+      --gen "${gen_language}:${options}${include_prefix_text}"
+      -o ${output_path}
+      ${thrift_include_directories}
+      "${file_path}/${file_name}.thrift"
+    DEPENDS
+      ${THRIFT1}
+      "${file_path}/${file_name}.thrift"
+    COMMENT "Generating ${file_name} files. Output: ${output_path}"
+  )
+  add_custom_target(
+    ${file_name}-${language}-target ALL
+    DEPENDS ${${language}-${language}-HEADERS}
+      ${${file_name}-${language}-SOURCES}
+  )
+  install(
+    DIRECTORY gen-${language}
+    DESTINATION include/${include_prefix}
+    FILES_MATCHING PATTERN "*.h")
+  install(
+    DIRECTORY gen-${language}
+    DESTINATION include/${include_prefix}
+    FILES_MATCHING PATTERN "*.tcc")
+endmacro()
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000000..78234ba73b
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,125 @@
+cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
+
+# package information
+set(PACKAGE_NAME      "cachelib")
+
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_VERBOSE_MAKEFILE ON)
+
+project(${PACKAGE_NAME} C CXX)
+
+set(BIN_INSTALL_DIR bin CACHE STRING
+    "The subdirectory where the compiler binary should be installed")
+set(INCLUDE_INSTALL_DIR include CACHE STRING
+    "The subdirectory where include files should be installed")
+set(LIB_INSTALL_DIR lib CACHE STRING
+    "The subdirectory where libraries should be installed")
+set(CMAKE_INSTALL_DIR lib/cmake/${PACKAGE_NAME} CACHE STRING
+    "The subdirectory where CMake package config files should be installed")
+
+SET(CMAKE_CXX_FLAGS "-O2")
+SET(CMAKE_C_FLAGS "-O2")
+SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -ggdb")
+SET(CMAKE_C_FLAGS_DEBUG "-O0 -g")
+
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+#link_directories("/usr/local/include")
+
+# includes
+set(CMAKE_MODULE_PATH
+  "${CMAKE_CURRENT_SOURCE_DIR}/CMake"
+  # for in-fbsource builds
+  "${CMAKE_CURRENT_SOURCE_DIR}/../opensource/fbcode_builder/CMake"
+  # For shipit-transformed builds
+  "${CMAKE_CURRENT_SOURCE_DIR}/build/fbcode_builder/CMake"
+  ${CMAKE_MODULE_PATH})
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMake")
+
+# Find required dependencies
+
+find_package(fizz CONFIG REQUIRED)
+find_package(wangle CONFIG REQUIRED)
+find_package(LibEvent REQUIRED)
+find_package(RT REQUIRED)
+find_package(fmt CONFIG REQUIRED)
+find_package(Gflags REQUIRED)
+find_package(yarpl CONFIG REQUIRED)
+find_package(rsocket CONFIG REQUIRED)
+find_package(FBThrift CONFIG REQUIRED)
+set(FBTHRIFT_LIBRARIES FBThrift::thriftcpp2)
+find_file(
+    FBTHRIFT_LIBRARY_FILE
+    NAMES "ThriftLibrary.cmake"
+    HINTS
+        "/usr/local/include/thrift"
+)
+include(${FBTHRIFT_LIBRARY_FILE})
+find_file(
+    THRIFT1
+    NAMES "thrift1"
+    HINTS
+        "/usr/local/bin"
+)
+find_package(Boost REQUIRED)
+find_package (Threads REQUIRED)
+find_package (Libiberty REQUIRED)
+find_package(Folly CONFIG REQUIRED)
+set(FOLLY_LIBRARIES Folly::folly)
+# Glog FBThrift ${CMAKE_DL_LIBS} Threads Gflags fmt)
+
+#set(GTEST_ROOT "/usr/local/" CACHE PATH "Path to googletest")
+enable_testing()
+find_package(GTest REQUIRED)
+find_package(Gmock REQUIRED)
+find_package(glog REQUIRED)
+
+# So that all subsequent directories have access to folly and thrift
+include_directories(${FOLLY_INCLUDE_DIR} ${FBTHRIFT_INCLUDE_DIR} ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS} ${fmt_INCLUDE_DIR})
+
+# Download and unpack googletest at configure time
+#configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
+#execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
+#        RESULT_VARIABLE result
+#        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
+#if(result)
+#    message(FATAL_ERROR "CMake step for googletest failed: ${result}")
+#endif()
+#execute_process(COMMAND ${CMAKE_COMMAND} --build .
+#        RESULT_VARIABLE result
+#        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
+#if(result)
+#    message(FATAL_ERROR "Build step for googletest failed: ${result}")
+#endif()
+#
+## Prevent overriding the parent project's compiler/linker
+## settings on Windows
+#set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+#
+## Add googletest directly to our build. This defines
+## the gtest and gtest_main targets.
+#add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
+#        ${CMAKE_CURRENT_BINARY_DIR}/googletest-build
+#        EXCLUDE_FROM_ALL)
+
+
+# Now simply link your own targets against gtest, gmock,
+# etc. as appropriate
+
+add_subdirectory(cachelib/shm)
+add_subdirectory(cachelib/navy)
+add_subdirectory(cachelib/common)
+add_subdirectory(cachelib/allocator)
+#add_subdirectory(cachelib/benchmarks)
+add_subdirectory(cachelib/datatype)
+add_subdirectory(cachelib/compact_cache)
+add_subdirectory(cachelib/cachebench)
+add_subdirectory(cachelib/external/memcached)
diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in
new file mode 100644
index 0000000000..f5865d1fc2
--- /dev/null
+++ b/CMakeLists.txt.in
@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 3.5.1 FATAL_ERROR)
+project(googletest-download NONE)
+
+include(ExternalProject)
+ExternalProject_Add(googletest
+    GIT_REPOSITORY https://github.com/google/googletest.git
+    GIT_TAG release-1.10.0 #change to master when VERSION_CHANGE bug fixed
+    SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src"
+    BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build"
+    CONFIGURE_COMMAND ""
+    BUILD_COMMAND ""
+    INSTALL_COMMAND ""
+    TEST_COMMAND ""
+)
diff --git a/README.md b/README.md
index ad7d395cba..32faa36fc0 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,5 @@
 # CacheLib
+
+To build:
+
+mkdir build; cd build && cmake .. && make
diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt
new file mode 100644
index 0000000000..ee2a9c342d
--- /dev/null
+++ b/benchmarks/CMakeLists.txt
@@ -0,0 +1,57 @@
+project (cachelot_benchmarks)
+
+cmake_minimum_required (VERSION 2.8.3)
+set (CMAKE_CXX_STANDARD 17)
+
+set (BOOST_MIN_VERSION, "1.53.0")
+set (USED_COMPONENTS unit_test_framework system program_options)
+set (Boost_USE_MULTITHREADED ON)
+set (Boost_USE_STATIC_LIBS ON)
+add_definitions (-DBOOST_ALL_NO_LIB) # disable boost auto-linking feature as it does not work with all compilers
+find_package (Boost ${BOOST_MIN_VERSION} COMPONENTS ${USED_COMPONENTS} REQUIRED)
+include_directories (${INCLUDE_DIRECTORIES} ${Boost_INCLUDE_DIRS})
+
+
+
+find_library(CACHELOT_LIB cachelot HINTS /home/daniel/cachelot/lib/Release)
+
+
+###########################################################################
+#       Platform setup -- required!
+###########################################################################
+if (CMAKE_SIZEOF_VOID_P EQUAL 4)
+  set (CACHELOT_PLATFORM_BITS 32)
+  add_definitions ("-DCACHELOT_PLATFORM_BITS=32")
+elseif (CMAKE_SIZEOF_VOID_P EQUAL 8)
+  set (CACHELOT_PLATFORM_BITS 64)
+  add_definitions ("-DCACHELOT_PLATFORM_BITS=64")
+else ()
+  message (FATAL_ERROR "CMake reports unsupported pointer size: \"${CMAKE_SIZEOF_VOID_P}\"")
+endif ()
+message (STATUS "Platform: ${CMAKE_SYSTEM_NAME} x${CACHELOT_PLATFORM_BITS}")
+if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
+    add_definition (-D_WIN32_WINNT=0x05010000) # set NTDDI_WINXP as a target win version
+endif ()
+string (REGEX MATCH "arm.*" ARM_CPU "${CMAKE_SYSTEM_PROCESSOR}")
+string (REGEX MATCH "x86_64" x86_64_CPU "${CMAKE_SYSTEM_PROCESSOR}")
+
+
+###########################################################################
+#       config.h -- required!
+###########################################################################
+include (CheckCXXSymbolExists)
+check_cxx_symbol_exists (aligned_alloc stdlib.h HAVE_ALIGNED_ALLOC)
+check_cxx_symbol_exists (posix_memalign stdlib.h HAVE_POSIX_MEMALIGN)
+configure_file ("/home/daniel/cachelot/src/cachelot/config.h.in" "/home/daniel/cachelot/src/cachelot/config.h")
+add_definitions (-DHAVE_CONFIG_H=1)
+
+
+### Cache benchmark
+add_executable(cachelot_test cachelot.cpp)
+
+include_directories("/home/daniel/cachelot/src/")
+#target_link_directories("/home/daniel/cachelot/lib/Release")
+
+
+
+target_link_libraries (cachelot_test ${Boost_LIBRARIES} ${CACHELOT_LIB})
diff --git a/benchmarks/cachelot.cpp b/benchmarks/cachelot.cpp
new file mode 100644
index 0000000000..11cda43266
--- /dev/null
+++ b/benchmarks/cachelot.cpp
@@ -0,0 +1,162 @@
+#include <cachelot/common.h>
+#include <cachelot/cache.h>
+#include <cachelot/random.h>
+#include <cachelot/hash_fnv1a.h>
+#include <cachelot/stats.h>
+
+#include <iostream>
+#include <iomanip>
+
+using namespace cachelot;
+
+using std::chrono::microseconds;
+using std::chrono::milliseconds;
+using std::chrono::seconds;
+using std::chrono::minutes;
+using std::chrono::hours;
+
+constexpr size_t num_items = 1000000;
+// Daniel at Ben: the cache memory size must be a power of two for some reason
+constexpr size_t cache_memory = 512 * Megabyte;
+constexpr size_t page_size = 4 * Megabyte;
+constexpr size_t hash_initial = 131072;
+constexpr uint8 min_key_len = 14;
+constexpr uint8 max_key_len = 40;
+constexpr uint32 min_value_len = 12;
+constexpr uint32 max_value_len = 45;
+
+namespace {
+
+    // Hash function
+    static auto calc_hash = fnv1a<cache::Cache::hash_type>::hasher();
+
+    static struct stats_type {
+        uint64 num_get = 0;
+        uint64 num_set = 0;
+        uint64 num_del = 0;
+        uint64 num_cache_hit = 0;
+        uint64 num_cache_miss = 0;
+        uint64 num_error = 0;
+    } bench_stats;
+
+    inline void reset_stats() {
+        new (&bench_stats)stats_type();
+    }
+
+}
+
+typedef std::tuple<string, string> kv_type;
+typedef std::vector<kv_type> array_type;
+typedef array_type::const_iterator iterator;
+
+
+// TODO: BEN: does this do what we want?
+class CacheWrapper {
+public:
+    CacheWrapper() : m_cache(cache::Cache::Create(cache_memory, page_size, hash_initial, true)) {}
+
+    void set(iterator it) {
+        slice k (std::get<0>(*it).c_str(), std::get<0>(*it).size());
+        slice v (std::get<1>(*it).c_str(), std::get<1>(*it).size());
+        cache::ItemPtr item = nullptr;
+        try {
+            item = m_cache.create_item(k, calc_hash(k), v.length(), /*flags*/0, cache::Item::infinite_TTL);
+            item->assign_value(v);
+            m_cache.do_set(item);
+            bench_stats.num_set += 1;
+        } catch (const std::exception &) {
+            bench_stats.num_error += 1;
+        }
+    }
+
+    void get(iterator it) {
+        slice k (std::get<0>(*it).c_str(), std::get<0>(*it).size());
+        auto found_item = m_cache.do_get(k, calc_hash(k));
+        bench_stats.num_get += 1;
+        auto & counter = found_item ? bench_stats.num_cache_hit : bench_stats.num_cache_miss;
+        counter += 1;
+    }
+
+    void del(iterator it) {
+        slice k (std::get<0>(*it).c_str(), std::get<0>(*it).size());
+        bool found = m_cache.do_delete(k, calc_hash(k));
+        bench_stats.num_del += 1;
+        auto & counter = found ? bench_stats.num_cache_hit : bench_stats.num_cache_miss;
+        counter += 1;
+    }
+private:
+    cache::Cache m_cache;
+};
+
+
+array_type data_array;
+std::unique_ptr<CacheWrapper> csh;
+
+inline iterator random_pick() {
+    debug_assert(data_array.size() > 0);
+    static random_int<array_type::size_type> rndelem(0, data_array.size() - 1);
+    auto at = rndelem();
+    return data_array.begin() + at;
+}
+
+
+static void generate_test_data() {
+    data_array.reserve(num_items);
+    for (auto n=num_items; n > 0; --n) {
+        kv_type kv(random_string(min_key_len, max_key_len),
+                   random_string(min_value_len, max_value_len));
+        data_array.emplace_back(kv);
+    }
+}
+
+
+static void warmup() {
+    for (auto n=hash_initial; n > 0; --n) {
+        csh->set(random_pick());
+    }
+}
+
+
+auto chance = random_int<size_t>(1, 100);
+
+int main(int /*argc*/, char * /*argv*/[]) {
+    csh.reset(new CacheWrapper());
+    generate_test_data();
+    warmup();
+    reset_stats();
+    auto start_time = std::chrono::high_resolution_clock::now();
+    for (int i=0; i<3; ++i) {
+        for (iterator kv = data_array.begin(); kv < data_array.end(); ++kv) {
+            csh->set(kv);
+            if (chance() > 70) {
+                csh->del(random_pick());
+            }
+            if (chance() > 30) {
+                csh->get(random_pick());
+            }
+        }
+    }
+
+
+
+
+    // JUST ANALYSIS stuff
+
+    auto time_passed = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - start_time);
+    const double sec = time_passed.count() / 1000000000;
+    std::cout << std::fixed << std::setprecision(3);
+    std::cout << "Time spent: " << sec << "s" << std::endl;
+    std::cout << "get:        " << bench_stats.num_get << std::endl;
+    std::cout << "set:        " << bench_stats.num_set << std::endl;
+    std::cout << "del:        " << bench_stats.num_del << std::endl;
+    std::cout << "cache_hit:  " << bench_stats.num_cache_hit << std::endl;
+    std::cout << "cache_miss: " << bench_stats.num_cache_miss << std::endl;
+    std::cout << "error:      " << bench_stats.num_error << std::endl;
+    const double RPS = (bench_stats.num_get + bench_stats.num_set + bench_stats.num_del) / sec;
+    std::cout << "rps:        " << RPS << std::endl;
+    std::cout << "avg. cost:  " << static_cast<unsigned>(1000000000 / RPS) << "ns" << std::endl;
+    std::cout << std::endl;
+    PrintStats();
+
+    return 0;
+}
diff --git a/cachelib/allocator/CCacheManager.h b/cachelib/allocator/CCacheManager.h
index 8fe50e86e3..c97d6f5f74 100644
--- a/cachelib/allocator/CCacheManager.h
+++ b/cachelib/allocator/CCacheManager.h
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "cachelib/allocator/CCacheAllocator.h"
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 
 namespace facebook {
 namespace cachelib {
diff --git a/cachelib/allocator/CMakeLists.txt b/cachelib/allocator/CMakeLists.txt
new file mode 100644
index 0000000000..670c0212cb
--- /dev/null
+++ b/cachelib/allocator/CMakeLists.txt
@@ -0,0 +1,247 @@
+thrift_object("datastruct_objects" "" "cpp2" "" "${CMAKE_CURRENT_SOURCE_DIR}/datastruct/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/datastruct/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/datastruct/serialize" THRIFT_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR})
+thrift_object("allocator_objects" "" "cpp2" "" "${CMAKE_CURRENT_SOURCE_DIR}/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/serialize" THRIFT_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR})
+thrift_object("memory_objects" "" "cpp2" "" "${CMAKE_CURRENT_SOURCE_DIR}/memory/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/memory/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/memory/serialize" THRIFT_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR})
+add_library(
+  cachelib_allocator
+
+  CCacheAllocator.h
+  CCacheManager.h
+  Cache.h
+  CacheAllocator-inl.h
+  CacheAllocator.h
+  CacheAllocatorConfig.h
+  CacheChainedItemIterator.h
+  CacheDetails.h
+  CacheItem-inl.h
+  CacheItem.h
+  CacheStats.h
+  CacheStatsInternal.h
+  CacheTraits.h
+  CacheVersion.h
+  ChainedAllocs.h
+  ChainedHashTable-inl.h
+  ChainedHashTable.h
+  EventInterface.h
+  FreeMemStrategy.h
+  Handle.h
+  HitsPerSlabStrategy.h
+  ICompactCache.h
+  KAllocation.h
+  LruTailAgeStrategy.h
+  MM2Q-inl.h
+  MM2Q.h
+  MMLru-inl.h
+  MMLru.h
+  MMTinyLFU-inl.h
+  MMTinyLFU.h
+  MarginalHitsOptimizeStrategy.h
+  MarginalHitsState-inl.h
+  MarginalHitsState.h
+  MarginalHitsStrategy.h
+  MemoryMappedFile.h
+  MemoryMonitor.h
+  NvmAdmissionPolicy.h
+  NvmCacheState.h
+  PoolOptimizeStrategy.h
+  PoolOptimizer.h
+  PoolRebalancer.h
+  PoolResizeStrategy.h
+  PoolResizer.h
+  RandomStrategy.h
+  ReadOnlySharedCacheView.h
+  Reaper-inl.h
+  Reaper.h
+  RebalanceInfo.h
+  RebalanceStrategy.h
+  Refcount-inl.h
+  Refcount.h
+  SlabReleaseStats.h
+  TempShmMapping.h
+  TlsActiveItemRing.h
+  TypedHandle.h
+  Util.h
+  CCacheAllocator.cpp
+  CCacheManager.cpp
+  Cache.cpp
+  CacheAllocator.cpp
+  CacheDetails.cpp
+  CacheStats.cpp
+  ContainerTypes.cpp
+  FreeMemStrategy.cpp
+  HitsPerSlabStrategy.cpp
+  LruTailAgeStrategy.cpp
+  MarginalHitsOptimizeStrategy.cpp
+  MarginalHitsStrategy.cpp
+  MemoryMonitor.cpp
+  NvmCacheState.cpp
+  PoolOptimizeStrategy.cpp
+  PoolOptimizer.cpp
+  PoolRebalancer.cpp
+  PoolResizer.cpp
+  RebalanceStrategy.cpp
+  SlabReleaseStats.cpp
+  TempShmMapping.cpp
+  $<TARGET_OBJECTS:allocator_objects-cpp2-obj> 
+
+  datastruct/DList-inl.h
+  datastruct/DList.h
+  datastruct/MultiDList-inl.h
+  datastruct/MultiDList.h
+  datastruct/SList-inl.h
+  datastruct/SList.h
+  $<TARGET_OBJECTS:datastruct_objects-cpp2-obj> 
+
+  memory/AllocationClass.h
+  memory/CompressedPtr.h
+  memory/MemoryAllocator.h
+  memory/MemoryAllocatorStats.h
+  memory/MemoryPool.h
+  memory/MemoryPoolManager.h
+  memory/Slab.h
+  memory/SlabAllocator.h
+  memory/AllocationClass.cpp
+  memory/MemoryAllocator.cpp
+  memory/MemoryPool.cpp
+  memory/MemoryPoolManager.cpp
+  memory/Slab.cpp
+  memory/SlabAllocator.cpp
+  $<TARGET_OBJECTS:memory_objects-cpp2-obj> 
+
+  memory/tests/TestBase.h
+  memory/tests/AllocationClassTest.cpp
+  memory/tests/MemoryAllocatorTest.cpp
+  memory/tests/MemoryPoolManagerTest.cpp
+  memory/tests/MemoryPoolTest.cpp
+  memory/tests/SlabAllocatorTest.cpp
+  memory/tests/TestBase.cpp
+
+  nvmcache/DipperItem.h
+  nvmcache/InFlightPuts.h
+  nvmcache/NavySetup.h
+  nvmcache/NvmCache-inl.h
+  nvmcache/NvmCache.h
+  nvmcache/ReqContexts.h
+  nvmcache/TombStones.h
+  nvmcache/WaitContext.h
+  nvmcache/DipperItem.cpp
+  nvmcache/NavySetup.cpp
+
+  tests/AccessTypeTest.h
+  tests/AllocatorHitStatsTest.h
+  tests/AllocatorResizeTest.h
+  tests/AllocatorTestUtils.h
+  tests/BaseAllocatorTest.h
+  tests/EventInterfaceTest.h
+  tests/MMTypeTest.h
+  tests/MultiAllocatorTest.h
+  tests/SimplePoolOptimizationTest.h
+  tests/SimpleRebalancingTest.h
+  tests/TestBase-inl.h
+  tests/TestBase.h
+  tests/AllocatorHitStatsTypeTest.cpp
+  tests/AllocatorResizeTypeTest.cpp
+  tests/AllocatorTypeTest.cpp
+  tests/CacheBaseTest.cpp
+  tests/ChainedHashTest.cpp
+  tests/EventInterfaceTest.cpp
+  tests/ItemHandleTest.cpp
+  tests/ItemTest.cpp
+  tests/MM2QTest.cpp
+  tests/MMLruTest.cpp
+  tests/MMTinyLFUTest.cpp
+  tests/MarginalHitsStateTest.cpp
+  tests/MultiAllocatorTest.cpp
+  tests/NvmCacheStateTest.cpp
+  tests/PoolOptimizeStrategyTest.cpp
+  tests/RebalanceStrategyTest.cpp
+  tests/RefCountTest.cpp
+  tests/SimplePoolOptimizationTest.cpp
+  tests/SimpleRebalancingTest.cpp
+  )
+
+target_link_libraries(
+  cachelib_allocator
+  PUBLIC
+    cachelib_shm
+    navy
+    ${FOLLY_LIBRARIES}
+    ${FBTHRIFT_LIBRARIES}
+)
+
+thrift_object("test_objects" "" "cpp2" "" "${CMAKE_CURRENT_SOURCE_DIR}/datastruct/tests" "${CMAKE_CURRENT_SOURCE_DIR}/datastruct/tests" "${CMAKE_CURRENT_SOURCE_DIR}/datastruct/tests" THRIFT_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR})
+
+
+add_executable(
+        allocator_test_bin
+
+        tests/AccessTypeTest.h
+        tests/AllocatorHitStatsTest.h
+        tests/AllocatorResizeTest.h
+        tests/AllocatorTestUtils.h
+        tests/BaseAllocatorTest.h
+        tests/EventInterfaceTest.h
+        tests/MMTypeTest.h
+        tests/MultiAllocatorTest.h
+        tests/SimplePoolOptimizationTest.h
+        tests/SimpleRebalancingTest.h
+        tests/TestBase-inl.h
+        tests/TestBase.h
+        tests/AllocatorHitStatsTypeTest.cpp
+        tests/AllocatorResizeTypeTest.cpp
+        tests/AllocatorTypeTest.cpp
+        tests/CacheBaseTest.cpp
+        tests/ChainedHashTest.cpp
+        tests/EventInterfaceTest.cpp
+        tests/ItemHandleTest.cpp
+        tests/ItemTest.cpp
+        tests/MM2QTest.cpp
+        tests/MMLruTest.cpp
+        tests/MMTinyLFUTest.cpp
+        tests/MarginalHitsStateTest.cpp
+        tests/MultiAllocatorTest.cpp
+        tests/NvmCacheStateTest.cpp
+        tests/PoolOptimizeStrategyTest.cpp
+        tests/RebalanceStrategyTest.cpp
+        tests/RefCountTest.cpp
+        tests/SimplePoolOptimizationTest.cpp
+        tests/SimpleRebalancingTest.cpp
+
+        memory/tests/TestBase.h
+        memory/tests/AllocationClassTest.cpp
+        memory/tests/MemoryAllocatorTest.cpp
+        memory/tests/MemoryPoolManagerTest.cpp
+        memory/tests/MemoryPoolTest.cpp
+        memory/tests/SlabAllocatorTest.cpp
+        memory/tests/TestBase.cpp
+
+        nvmcache/tests/NvmTestBase-inl.h
+        nvmcache/tests/NvmTestBase.h
+        nvmcache/tests/DipperItemTests.cpp
+        nvmcache/tests/InFlightPutsTest.cpp
+        nvmcache/tests/NvmCacheTests.cpp
+        nvmcache/tests/TombStoneTests.cpp
+
+        datastruct/tests/DListTest.cpp
+        datastruct/tests/MultiDListTest.cpp
+        datastruct/tests/SListTest.cpp
+        $<TARGET_OBJECTS:test_objects-cpp2-obj> 
+
+    )
+
+target_link_libraries(
+        allocator_test_bin
+
+        PUBLIC
+            navy
+            cachelib_allocator
+            GTest::GTest
+            GTest::Main
+            ${GMOCK_LIBRARIES}
+            ${FOLLY_LIBRARIES}
+)
+
+add_test(
+        NAME allocator_tests
+        COMMAND allocator_test_bin
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
diff --git a/cachelib/allocator/CacheAllocator.h b/cachelib/allocator/CacheAllocator.h
index 4a36616b5b..970463f566 100644
--- a/cachelib/allocator/CacheAllocator.h
+++ b/cachelib/allocator/CacheAllocator.h
@@ -46,9 +46,9 @@
 #include "cachelib/allocator/Util.h"
 #include "cachelib/allocator/memory/MemoryAllocator.h"
 #include "cachelib/allocator/memory/MemoryAllocatorStats.h"
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 #include "cachelib/allocator/nvmcache/NvmCache.h"
-#include "cachelib/allocator/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/serialize/gen-cpp2/allocator_objects_types.h"
 #include "cachelib/common/Exceptions.h"
 #include "cachelib/common/Hash.h"
 #include "cachelib/common/Mutex.h"
diff --git a/cachelib/allocator/ChainedHashTable.h b/cachelib/allocator/ChainedHashTable.h
index 7d518660ba..083c0e1c6c 100644
--- a/cachelib/allocator/ChainedHashTable.h
+++ b/cachelib/allocator/ChainedHashTable.h
@@ -7,7 +7,7 @@
 #include <folly/Optional.h>
 
 #include "cachelib/allocator/Cache.h"
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 #include "cachelib/common/CompilerUtils.h"
 #include "cachelib/common/Mutex.h"
 #include "cachelib/common/Throttler.h"
diff --git a/cachelib/allocator/MM2Q.h b/cachelib/allocator/MM2Q.h
index 154e433564..dbfc8e4357 100644
--- a/cachelib/allocator/MM2Q.h
+++ b/cachelib/allocator/MM2Q.h
@@ -9,7 +9,7 @@
 
 #include "cachelib/allocator/Util.h"
 #include "cachelib/allocator/datastruct/MultiDList.h"
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 #include "cachelib/common/CompilerUtils.h"
 #include "cachelib/common/Mutex.h"
 
diff --git a/cachelib/allocator/MMLru.h b/cachelib/allocator/MMLru.h
index 0c45e92bd8..1a975a5045 100644
--- a/cachelib/allocator/MMLru.h
+++ b/cachelib/allocator/MMLru.h
@@ -13,7 +13,7 @@
 
 #include "cachelib/allocator/Util.h"
 #include "cachelib/allocator/datastruct/DList.h"
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 #include "cachelib/common/CompilerUtils.h"
 #include "cachelib/common/Mutex.h"
 
diff --git a/cachelib/allocator/MMTinyLFU.h b/cachelib/allocator/MMTinyLFU.h
index 4f25f7f23c..dfa3f333dd 100644
--- a/cachelib/allocator/MMTinyLFU.h
+++ b/cachelib/allocator/MMTinyLFU.h
@@ -12,7 +12,7 @@
 #include "cachelib/allocator/CacheStats.h"
 #include "cachelib/allocator/Util.h"
 #include "cachelib/allocator/datastruct/MultiDList.h"
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 #include "cachelib/common/CompilerUtils.h"
 #include "cachelib/common/CountMinSketch.h"
 #include "cachelib/common/Mutex.h"
diff --git a/cachelib/allocator/NvmCacheState.cpp b/cachelib/allocator/NvmCacheState.cpp
index 438b8a8306..545faf88b6 100644
--- a/cachelib/allocator/NvmCacheState.cpp
+++ b/cachelib/allocator/NvmCacheState.cpp
@@ -9,7 +9,7 @@
 
 #include "cachelib/allocator/CacheVersion.h"
 #include "cachelib/allocator/NvmCacheState.h"
-#include "cachelib/allocator/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/serialize/gen-cpp2/allocator_objects_types.h"
 #include "cachelib/common/Serialization.h"
 #include "cachelib/common/Time.h"
 #include "cachelib/common/Utils.h"
diff --git a/cachelib/allocator/datastruct/CMakeLists.txt b/cachelib/allocator/datastruct/CMakeLists.txt
new file mode 100644
index 0000000000..7f2cdddd3b
--- /dev/null
+++ b/cachelib/allocator/datastruct/CMakeLists.txt
@@ -0,0 +1,24 @@
+#https://github.com/facebook/fbthrift/blob/master/ThriftLibrary.cmake lines 88 to 121
+thrift_object("datastruct_objects" "" "cpp2" "" "${CMAKE_CURRENT_SOURCE_DIR}/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/serialize" THRIFT_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR})
+
+add_library(
+  cachelib_allocator_datastruct
+
+  DList-inl.h
+  DList.h
+  MultiDList-inl.h
+  MultiDList.h
+  SList-inl.h
+  SList.h
+  $<TARGET_OBJECTS:allocator_objects-cpp2-obj> 
+  )
+
+target_link_libraries(
+  cachelib_allocator_datastruct
+  PUBLIC
+    ${FOLLY_LIBRARIES}
+    ${FBTHRIFT_LIBRARIES}
+    cachelib_allocator_memory
+)
+
+#add_subdirectory(tests)
diff --git a/cachelib/allocator/datastruct/DList.h b/cachelib/allocator/datastruct/DList.h
index 3a49f55b5f..d2ead3f837 100644
--- a/cachelib/allocator/datastruct/DList.h
+++ b/cachelib/allocator/datastruct/DList.h
@@ -4,7 +4,7 @@
 
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
-#include "cachelib/allocator/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/serialize/gen-cpp2/allocator_objects_types.h"
 #pragma GCC diagnostic pop
 
 #include "cachelib/common/CompilerUtils.h"
diff --git a/cachelib/allocator/datastruct/MultiDList.h b/cachelib/allocator/datastruct/MultiDList.h
index 75f6a1645e..bd462fe1f4 100644
--- a/cachelib/allocator/datastruct/MultiDList.h
+++ b/cachelib/allocator/datastruct/MultiDList.h
@@ -4,7 +4,7 @@
 
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
-#include "cachelib/allocator/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/serialize/gen-cpp2/allocator_objects_types.h"
 #pragma GCC diagnostic pop
 
 #include "cachelib/allocator/datastruct/DList.h"
diff --git a/cachelib/allocator/datastruct/SList.h b/cachelib/allocator/datastruct/SList.h
index 15a014bc1f..312da1381b 100644
--- a/cachelib/allocator/datastruct/SList.h
+++ b/cachelib/allocator/datastruct/SList.h
@@ -4,7 +4,7 @@
 
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
-#include "cachelib/allocator/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/serialize/gen-cpp2/allocator_objects_types.h"
 #pragma GCC diagnostic pop
 
 #include "cachelib/common/CompilerUtils.h"
diff --git a/cachelib/allocator/datastruct/serialize/objects.thrift b/cachelib/allocator/datastruct/serialize/datastruct_objects.thrift
similarity index 100%
rename from cachelib/allocator/datastruct/serialize/objects.thrift
rename to cachelib/allocator/datastruct/serialize/datastruct_objects.thrift
diff --git a/cachelib/allocator/memory/AllocationClass.cpp b/cachelib/allocator/memory/AllocationClass.cpp
index 1c995912b4..5bc93627ee 100644
--- a/cachelib/allocator/memory/AllocationClass.cpp
+++ b/cachelib/allocator/memory/AllocationClass.cpp
@@ -18,7 +18,7 @@
 #include <stdexcept>
 #include <thread>
 
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 
 using namespace facebook::cachelib;
 
diff --git a/cachelib/allocator/memory/AllocationClass.h b/cachelib/allocator/memory/AllocationClass.h
index 70a9dd5b25..8ed58ece4a 100644
--- a/cachelib/allocator/memory/AllocationClass.h
+++ b/cachelib/allocator/memory/AllocationClass.h
@@ -10,7 +10,7 @@
 #include "cachelib/allocator/memory/MemoryAllocatorStats.h"
 #include "cachelib/allocator/memory/Slab.h"
 #include "cachelib/allocator/memory/SlabAllocator.h"
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 
 #include <folly/lang/Aligned.h>
 #include <folly/synchronization/DistributedMutex.h>
diff --git a/cachelib/allocator/memory/CMakeLists.txt b/cachelib/allocator/memory/CMakeLists.txt
new file mode 100644
index 0000000000..04ec241e66
--- /dev/null
+++ b/cachelib/allocator/memory/CMakeLists.txt
@@ -0,0 +1,34 @@
+#https://github.com/facebook/fbthrift/blob/master/ThriftLibrary.cmake lines 88 to 121
+thrift_object("memory_objects" "" "cpp2" "" "${CMAKE_CURRENT_SOURCE_DIR}/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/serialize" "${CMAKE_CURRENT_SOURCE_DIR}/serialize" THRIFT_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR})
+add_library(
+  cachelib_allocator_memory
+
+  AllocationClass.h
+  CompressedPtr.h
+  MemoryAllocator.h
+  MemoryAllocatorStats.h
+  MemoryPool.h
+  MemoryPoolManager.h
+  Slab.h
+  SlabAllocator.h
+  AllocationClass.cpp
+  MemoryAllocator.cpp
+  MemoryPool.cpp
+  MemoryPoolManager.cpp
+  Slab.cpp
+  SlabAllocator.cpp
+  $<TARGET_OBJECTS:memory_objects-cpp2-obj> 
+  )
+
+target_link_libraries(
+  cachelib_allocator_memory
+
+  PUBLIC
+    ${FOLLY_LIBRARIES}
+    ${FBTHRIFT_LIBRARIES}
+    cachelib_allocator_datastruct
+)
+
+
+
+add_subdirectory(tests)
diff --git a/cachelib/allocator/memory/MemoryAllocator.h b/cachelib/allocator/memory/MemoryAllocator.h
index 87cc98bae4..bee6c1aeed 100644
--- a/cachelib/allocator/memory/MemoryAllocator.h
+++ b/cachelib/allocator/memory/MemoryAllocator.h
@@ -13,7 +13,7 @@
 #include <folly/Format.h>
 #pragma GCC diagnostic pop
 
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 
 namespace facebook {
 namespace cachelib {
diff --git a/cachelib/allocator/memory/MemoryPool.h b/cachelib/allocator/memory/MemoryPool.h
index e3e81a1c18..d492a03077 100644
--- a/cachelib/allocator/memory/MemoryPool.h
+++ b/cachelib/allocator/memory/MemoryPool.h
@@ -13,7 +13,7 @@
 
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 #pragma GCC diagnostic pop
 
 namespace facebook {
diff --git a/cachelib/allocator/memory/MemoryPoolManager.h b/cachelib/allocator/memory/MemoryPoolManager.h
index e84e48166f..d0a77a6d1e 100644
--- a/cachelib/allocator/memory/MemoryPoolManager.h
+++ b/cachelib/allocator/memory/MemoryPoolManager.h
@@ -14,7 +14,7 @@
 #include "cachelib/allocator/memory/MemoryPool.h"
 #include "cachelib/allocator/memory/Slab.h"
 #include "cachelib/allocator/memory/SlabAllocator.h"
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 
 namespace facebook {
 namespace cachelib {
diff --git a/cachelib/allocator/memory/SlabAllocator.h b/cachelib/allocator/memory/SlabAllocator.h
index ff308b8a68..bca8bb24a8 100644
--- a/cachelib/allocator/memory/SlabAllocator.h
+++ b/cachelib/allocator/memory/SlabAllocator.h
@@ -11,7 +11,7 @@
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
 #include <folly/Format.h>
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 #pragma GCC diagnostic pop
 #include <folly/logging/xlog.h>
 
diff --git a/cachelib/allocator/memory/serialize/objects.thrift b/cachelib/allocator/memory/serialize/memory_objects.thrift
similarity index 92%
rename from cachelib/allocator/memory/serialize/objects.thrift
rename to cachelib/allocator/memory/serialize/memory_objects.thrift
index 1c39bc08c0..c276986436 100644
--- a/cachelib/allocator/memory/serialize/objects.thrift
+++ b/cachelib/allocator/memory/serialize/memory_objects.thrift
@@ -1,6 +1,6 @@
 namespace cpp2 facebook.cachelib.serialization
 
-include "cachelib/allocator/datastruct/serialize/objects.thrift"
+include "cachelib/allocator/datastruct/serialize/datastruct_objects.thrift"
 
 // Adding a new "required" field will cause the cache to be dropped
 // in the next release for our users. If the field needs to be required,
@@ -22,7 +22,7 @@ struct AllocationClassObject {
   2: required i64 allocationSize, // to accommodate uint32_t allocationSize
   4: required i64 currOffset,
   8: required bool canAllocate,
-  9: SListObject freedAllocationsObject,
+  9: datastruct_objects.SListObject freedAllocationsObject,
   10: required i32 currSlabIdx,
   11: required list<i32> allocatedSlabIdxs,
   12: required list<i32> freeSlabIdxs,
diff --git a/cachelib/allocator/nvmcache/NavySetup.cpp b/cachelib/allocator/nvmcache/NavySetup.cpp
new file mode 100644
index 0000000000..1a0f693a55
--- /dev/null
+++ b/cachelib/allocator/nvmcache/NavySetup.cpp
@@ -0,0 +1,379 @@
+#include "cachelib/allocator/nvmcache/NavySetup.h"
+#include "cachelib/navy/Factory.h"
+#include "cachelib/navy/block_cache/HitsReinsertionPolicy.h"
+#include "cachelib/navy/scheduler/ThreadPoolJobScheduler.h"
+
+#include <folly/File.h>
+#include <folly/logging/xlog.h>
+
+namespace facebook {
+namespace cachelib {
+
+namespace {
+
+// Default value for (almost) 1TB flash device = 5GB reserved for metadata
+constexpr double kDefaultMetadataPercent = 0.5;
+
+constexpr folly::StringPiece kAdmissionPolicy{"dipper_navy_adm_policy"};
+constexpr folly::StringPiece kAdmissionProb{"dipper_navy_adm_probability"};
+constexpr folly::StringPiece kAdmissionWriteRate{"dipper_navy_adm_write_rate"};
+constexpr folly::StringPiece kAdmissionSuffixLen{
+    "dipper_navy_adm_suffix_length"};
+constexpr folly::StringPiece kBlockSize{"dipper_navy_block_size"};
+constexpr folly::StringPiece kDirectIO{"dipper_navy_direct_io"};
+constexpr folly::StringPiece kFileName{"dipper_navy_file_name"};
+constexpr folly::StringPiece kRAIDPaths{"dipper_navy_raid_paths"};
+constexpr folly::StringPiece kDeviceMetadataSize{"dipper_navy_metadata_size"};
+constexpr folly::StringPiece kFileSize{"dipper_navy_file_size"};
+constexpr folly::StringPiece kFsSize{"dipper_fs_size"};
+constexpr folly::StringPiece kTruncateFile{"dipper_navy_truncate_file"};
+constexpr folly::StringPiece kLru{"dipper_navy_lru"};
+constexpr folly::StringPiece kRegionSize{"dipper_navy_region_size"};
+constexpr folly::StringPiece kReadBuffer{"dipper_navy_read_buffer"};
+constexpr folly::StringPiece kSizeClasses{"dipper_navy_size_classes"};
+constexpr folly::StringPiece kBigHashSizePct{"dipper_navy_bighash_size_pct"};
+constexpr folly::StringPiece kBigHashBucketSize{
+    "dipper_navy_bighash_bucket_size"};
+constexpr folly::StringPiece kBigHashBucketBFSize{
+    "dipper_navy_bighash_bucket_bf_size"};
+constexpr folly::StringPiece kSmallItemMaxSize{
+    "dipper_navy_small_item_max_size"};
+constexpr folly::StringPiece kMaxConcurrentInserts{
+    "dipper_navy_max_concurrent_inserts"};
+constexpr folly::StringPiece kMaxParcelMemoryMB{
+    "dipper_navy_max_parcel_memory_mb"};
+constexpr folly::StringPiece kCleanRegions{"dipper_navy_clean_regions"};
+constexpr folly::StringPiece kReaderThreads{"dipper_navy_reader_threads"};
+constexpr folly::StringPiece kWriterThreads{"dipper_navy_writer_threads"};
+constexpr folly::StringPiece kReinsertionHitsThreshold{
+    "dipper_navy_reinsertion_hits_threshold"};
+constexpr folly::StringPiece kReinsertionProbabilityThreshold{
+    "dipper_navy_reinsertion_probability_threshold"};
+constexpr folly::StringPiece kNavyRequestOrderingShards{
+    "dipper_navy_req_order_shards_power"};
+
+uint64_t megabytesToBytes(uint64_t mb) { return mb << 20; }
+
+// Return a number that's equal or smaller than @num and aligned on @alignment
+uint64_t alignDown(uint64_t num, uint64_t alignment) {
+  return num - num % alignment;
+}
+
+// Return a number that's equal or bigger than @num and aligned on @alignment
+uint64_t alignUp(uint64_t num, uint64_t alignment) {
+  return alignDown(num + alignment - 1, alignment);
+}
+
+bool usesSimpleFile(const folly::dynamic& options) {
+  auto fileName = options.get_ptr(kFileName);
+  return fileName && !fileName->getString().empty();
+}
+
+bool usesRaidFiles(const folly::dynamic& options) {
+  auto raidPaths = options.get_ptr(kRAIDPaths);
+  return raidPaths && (raidPaths->size() > 0);
+}
+
+// Open cache file @fileName and set it size to @size.
+// Throws std::system_error if failed.
+folly::File openCacheFile(const std::string& fileName,
+                          uint64_t size,
+                          bool directIO,
+                          bool truncate) {
+  XLOG(ERR) << "Cache file: " << fileName << " size: " << size
+            << " direct IO: " << directIO << " truncate: " << truncate;
+  if (fileName.empty()) {
+    throw std::invalid_argument("File name is empty");
+  }
+  int flags{O_RDWR | O_CREAT};
+  if (directIO) {
+    flags |= O_DIRECT;
+  }
+  auto f = folly::File(fileName.c_str(), flags);
+  // TODO detect if file exists and is of expected size. If not,
+  // automatically fallocate the file or ftruncate the file.
+  if (truncate && ::fallocate(f.fd(), 0, 0, size) < 0) {
+    throw std::system_error(
+        errno,
+        std::system_category(),
+        folly::sformat("failed fallocate with size {}", size));
+  }
+
+  if (directIO && ::posix_fadvise(f.fd(), 0, size, POSIX_FADV_DONTNEED) < 0) {
+    throw std::system_error(
+        errno, std::system_category(), "Error fadvising cache file");
+  }
+
+  return f;
+}
+
+std::unique_ptr<cachelib::navy::Device> createDevice(
+    const folly::dynamic& options) {
+  const auto size = getNvmCacheSize(options);
+  if (usesRaidFiles(options) && usesSimpleFile(options)) {
+    throw std::invalid_argument("Can't use raid and simple file together");
+  }
+
+  if (usesRaidFiles(options) && options.get_ptr(kRAIDPaths)->size() <= 1) {
+    throw std::invalid_argument("Raid needs more than one path");
+  }
+  if (usesRaidFiles(options)) {
+    auto raidPaths = options.get_ptr(kRAIDPaths);
+
+    // File paths are opened in the increasing order of the
+    // path string. This ensures that RAID0 stripes aren't
+    // out of order even if the caller changes the order of
+    // the file paths. We can recover the cache as long as all
+    // the paths are specified, regardless of the order.
+    std::vector<std::string> paths;
+    for (const auto& file : *raidPaths) {
+      paths.push_back(file.getString());
+    }
+    std::sort(paths.begin(), paths.end());
+
+    std::vector<int> fdvec;
+    for (const auto& path : paths) {
+      folly::File f;
+      try {
+        f = openCacheFile(path,
+                          options[kFileSize].getInt(),
+                          options[kDirectIO].getBool(),
+                          options[kTruncateFile].getBool());
+      } catch (const std::exception& e) {
+        XLOG(ERR) << "Exception in openCacheFile: " << path << e.what();
+        throw;
+      }
+      fdvec.push_back(f.release());
+    } // for
+    return cachelib::navy::createDirectIoRAID0Device(
+        fdvec, options[kBlockSize].getInt(), options[kRegionSize].getInt());
+  }
+
+  if (!usesSimpleFile(options)) {
+    return cachelib::navy::createMemoryDevice(size);
+  }
+
+  // Create a simple file device
+  auto fileName = options.get_ptr(kFileName);
+  folly::File f;
+  try {
+    f = openCacheFile(fileName->getString(),
+                      size,
+                      options[kDirectIO].getBool(),
+                      options[kTruncateFile].getBool());
+  } catch (const std::exception& e) {
+    XLOG(ERR) << "Exception in openCacheFile: " << e.what();
+    throw;
+  }
+  return cachelib::navy::createDirectIoFileDevice(f.release(),
+                                                  options[kBlockSize].getInt());
+}
+
+void setupCacheProtos(const folly::dynamic& options,
+                      cachelib::navy::CacheProto& proto) {
+  const uint64_t totalCacheSize = getNvmCacheSize(options);
+
+  auto blockSize = options[kBlockSize].getInt();
+  auto getDefaultMetadataSize = [](size_t size, size_t alignment) {
+    XDCHECK(folly::isPowTwo(alignment));
+    auto mask = ~(alignment - 1);
+    return (static_cast<size_t>(kDefaultMetadataPercent * size / 100) & mask);
+  };
+  uint64_t metadataSize =
+      options
+          .getDefault(kDeviceMetadataSize,
+                      getDefaultMetadataSize(totalCacheSize, blockSize))
+          .getInt();
+  metadataSize = alignUp(metadataSize, blockSize);
+
+  if (metadataSize >= totalCacheSize) {
+    throw std::invalid_argument{
+        folly::sformat("Invalid metadata size: {}. Cache size: {}",
+                       metadataSize,
+                       totalCacheSize)};
+  }
+  proto.setMetadataSize(metadataSize);
+  uint64_t blockCacheSize = 0;
+  const auto bigHashPctSize = options.get_ptr(kBigHashSizePct);
+  if (bigHashPctSize && bigHashPctSize->getInt() > 0) {
+    const auto bucketSize = options[kBigHashBucketSize].getInt();
+
+    // If enabled, BigHash's storage starts after BlockCache's.
+    const auto sizeReservedForBigHash =
+        totalCacheSize * bigHashPctSize->getInt() / 100ul;
+
+    const uint64_t bigHashCacheOffset =
+        alignUp(totalCacheSize - sizeReservedForBigHash, bucketSize);
+    const uint64_t bigHashCacheSize =
+        alignDown(totalCacheSize - bigHashCacheOffset, bucketSize);
+
+    auto bigHash = cachelib::navy::createBigHashProto();
+    bigHash->setLayout(bigHashCacheOffset, bigHashCacheSize, bucketSize);
+
+    // Bucket Bloom filter size, bytes
+    const auto bfSize = options.getDefault(kBigHashBucketBFSize, 8).getInt();
+    if (bfSize > 0) {
+      // We set 4 hash function unconditionally. This seems to be the best
+      // for our use case. If BF size to bucket size ratio gets lower, try
+      // to reduce number of hashes.
+      constexpr uint32_t kNumHashes = 4;
+      const uint32_t bitsPerHash = bfSize * 8 / kNumHashes;
+      bigHash->setBloomFilter(kNumHashes, bitsPerHash);
+    }
+
+    proto.setBigHash(std::move(bigHash), options[kSmallItemMaxSize].getInt());
+
+    if (bigHashCacheOffset <= metadataSize) {
+      throw std::invalid_argument("NVM cache size is not big enough!");
+    }
+    blockCacheSize = bigHashCacheOffset - metadataSize;
+    XLOG(ERR) << "metadataSize: " << metadataSize
+              << " blockCacheSize: " << blockCacheSize
+              << " bigHashCacheOffset: " << bigHashCacheOffset
+              << " bigHashCacheSize: " << bigHashCacheSize;
+  } else {
+    blockCacheSize = totalCacheSize - metadataSize;
+    XLOG(ERR) << "metadataSize: " << metadataSize
+              << " blockCacheSize: " << blockCacheSize << ". No Bighash";
+  }
+
+  if (blockCacheSize > 0) {
+    auto blockCache = cachelib::navy::createBlockCacheProto();
+    blockCache->setBlockSize(options[kBlockSize].getInt());
+    blockCache->setLayout(
+        metadataSize, blockCacheSize, options[kRegionSize].getInt());
+    blockCache->setChecksum(true);
+    if (options[kLru].getBool()) {
+      blockCache->setLruEvictionPolicy();
+    } else {
+      blockCache->setFifoEvictionPolicy();
+    }
+    if (options.get_ptr(kSizeClasses) && !options[kSizeClasses].empty()) {
+      std::vector<uint32_t> sizeClasses;
+      for (const auto& sc : options[kSizeClasses]) {
+        sizeClasses.push_back(sc.getInt());
+      }
+      DCHECK(!sizeClasses.empty());
+      blockCache->setSizeClasses(std::move(sizeClasses));
+    } else {
+      blockCache->setReadBufferSize(options[kReadBuffer].getInt());
+    }
+    blockCache->setCleanRegionsPool(
+        options.getDefault(kCleanRegions, 1).getInt());
+
+    const uint8_t reinsertionHitsThreshold =
+        options.getDefault(kReinsertionHitsThreshold, 0).getInt();
+    if (reinsertionHitsThreshold > 0) {
+      blockCache->setHitsReinsertionPolicy(reinsertionHitsThreshold);
+    }
+
+    const uint32_t reinsertionProbabilityThreshold =
+        options.getDefault(kReinsertionProbabilityThreshold, 0).getInt();
+    if (reinsertionProbabilityThreshold > 0) {
+      blockCache->setProbabilisticReinsertionPolicy(
+          reinsertionProbabilityThreshold);
+    }
+
+    proto.setBlockCache(std::move(blockCache));
+  }
+}
+
+void setAdmissionPolicy(const folly::dynamic& options,
+                        cachelib::navy::CacheProto& proto) {
+  auto policyName = options.get_ptr(kAdmissionPolicy);
+  if (!policyName) {
+    return;
+  }
+  const std::string& name = policyName->getString();
+  if (name == "random") {
+    proto.setRejectRandomAdmissionPolicy(options[kAdmissionProb].getDouble());
+  } else if (name == "dynamic_random") {
+    size_t admissionSuffixLen = options.get_ptr(kAdmissionSuffixLen)
+                                    ? options[kAdmissionSuffixLen].getInt()
+                                    : 0;
+    proto.setDynamicRandomAdmissionPolicy(options[kAdmissionWriteRate].getInt(),
+                                          admissionSuffixLen);
+  } else {
+    throw std::invalid_argument{folly::sformat("invalid policy name {}", name)};
+  }
+}
+
+std::unique_ptr<cachelib::navy::JobScheduler> createJobScheduler(
+    const folly::dynamic& options) {
+  auto legacyDipperAsyncThreads =
+      options.getDefault("dipper_async_threads", 32).getInt();
+  auto readerThreads =
+      options.getDefault(kReaderThreads, legacyDipperAsyncThreads).getInt();
+  auto writerThreads =
+      options.getDefault(kWriterThreads, legacyDipperAsyncThreads).getInt();
+  if (options.get_ptr(kNavyRequestOrderingShards)) {
+    auto reqOrderShardsPower = options[kNavyRequestOrderingShards].getInt();
+    return std::make_unique<cachelib::navy::OrderedThreadPoolJobScheduler>(
+        readerThreads, writerThreads, reqOrderShardsPower);
+  } else {
+    return std::make_unique<cachelib::navy::ThreadPoolJobScheduler>(
+        readerThreads, writerThreads);
+  }
+}
+
+} // namespace
+
+uint64_t getNvmCacheSize(const folly::dynamic& options) {
+  auto fsSize = options.get_ptr(kFsSize);
+  if (fsSize && fsSize->getInt() > 0) {
+    return uint64_t(fsSize->getInt()) * 1024ul * 1024ul;
+  }
+  if (usesRaidFiles(options)) {
+    auto raidPaths = options.get_ptr(kRAIDPaths);
+    return raidPaths->size() * options[kFileSize].getInt();
+  }
+  // Simple file or memory
+  return options[kFileSize].getInt();
+}
+
+std::unique_ptr<navy::AbstractCache> createNavyCache(
+    const folly::dynamic& options, navy::DestructorCallback cb, bool truncate) {
+  auto proto = cachelib::navy::createCacheProto();
+  proto->setDevice(createDevice(options));
+  proto->setJobScheduler(createJobScheduler(options));
+  proto->setMaxConcurrentInserts(
+      options.getDefault(kMaxConcurrentInserts, 1'000'000).getInt());
+  proto->setMaxParcelMemory(
+      megabytesToBytes(options.getDefault(kMaxParcelMemoryMB, 256).getInt()));
+  setAdmissionPolicy(options, *proto);
+  proto->setDestructorCallback(cb);
+
+  setupCacheProtos(options, *proto);
+
+  auto cache = createCache(std::move(proto));
+  XDCHECK(cache != nullptr);
+
+  if (truncate) {
+    cache->reset();
+    return cache;
+  }
+
+  if (!cache->recover()) {
+    XLOG(ERR) << "No recovery data found. Continuing with clean cache.";
+  }
+  return cache;
+}
+
+void populateDefaultNavyOptions(folly::dynamic& options) {
+  // default values for some of the options if they are not already present.
+  folly::dynamic defs = folly::dynamic::object;
+  defs[kDirectIO] = true;
+  defs[kBlockSize] = 4096;
+  defs[kRegionSize] = 16 * 1024 * 1024;
+  defs[kLru] = true;
+  defs[kReadBuffer] = 4096;
+  defs[kTruncateFile] = false;
+  defs[kBigHashBucketBFSize] = 8;
+  defs[kRAIDPaths] = folly::dynamic::array;
+
+  options.update_missing(defs);
+  return;
+}
+
+} // namespace cachelib
+} // namespace facebook
diff --git a/cachelib/allocator/nvmcache/NavySetup.h b/cachelib/allocator/nvmcache/NavySetup.h
new file mode 100644
index 0000000000..9574f424f6
--- /dev/null
+++ b/cachelib/allocator/nvmcache/NavySetup.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "cachelib/navy/AbstractCache.h"
+
+#include <folly/dynamic.h>
+namespace facebook {
+namespace cachelib {
+
+std::unique_ptr<facebook::cachelib::navy::AbstractCache> createNavyCache(
+    const folly::dynamic& options,
+    facebook::cachelib::navy::DestructorCallback cb,
+    bool truncate);
+
+void populateDefaultNavyOptions(folly::dynamic& options);
+
+uint64_t getNvmCacheSize(const folly::dynamic& options);
+
+} // namespace cachelib
+} // namespace facebook
diff --git a/cachelib/allocator/nvmcache/NvmCache-inl.h b/cachelib/allocator/nvmcache/NvmCache-inl.h
index 920f88f950..e47e76ce2d 100644
--- a/cachelib/allocator/nvmcache/NvmCache-inl.h
+++ b/cachelib/allocator/nvmcache/NvmCache-inl.h
@@ -82,18 +82,19 @@ typename NvmCache<C>::ItemHandle NvmCache<C>::find(folly::StringPiece key) {
   XDCHECK(ctx);
   auto guard = folly::makeGuard([ctx, this]() { removeFromFillMap(*ctx); });
 
-  auto rv = store_->dipperGetAsync(
-      dipper::DipperGetParams{ctx->getKey()},
-      [this, ctx](int err, folly::ByteRange k, folly::ByteRange v) {
-        this->onGetComplete(*ctx, err, k, v);
+  auto status = navyCache_->lookupAsync(
+      makeBufferView(ctx->getKey()),
+      [this, ctx](navy::Status s, navy::BufferView k, navy::Buffer v) {
+        this->onGetComplete(*ctx, s, k, v.view());
       });
-  if (rv != dipper::DipperStatus::OK()) {
-    // instead of disabling dipper, we enqueue a delete and return a miss.
+  if (status != navy::Status::Ok) {
+    // instead of disabling navy, we enqueue a delete and return a miss.
     remove(key);
     stats().numNvmGetMiss.inc();
   } else {
     guard.dismiss();
   }
+
   return hdl;
 }
 
@@ -103,22 +104,22 @@ typename NvmCache<C>::ItemHandle NvmCache<C>::peek(folly::StringPiece key) {
     return nullptr;
   }
 
-  dipper::DipperGetParams params{key};
   folly::Baton b;
   ItemHandle hdl{};
   hdl.markWentToNvm();
-  auto cb = [&, this](int err, folly::ByteRange, folly::ByteRange v) {
-    if (err != ENOENT) {
-      auto dItem = reinterpret_cast<const DipperItem*>(v.data());
-      hdl = createItem(key, *dItem);
-    }
-    b.post();
-  };
 
   // no need for fill lock or inspecting the state of other concurrent
   // operations since we only want to check the state for debugging purposes.
-  const auto rv = store_->dipperGetAsync(params, std::move(cb));
-  if (rv != dipper::DipperStatus::OK()) {
+  auto status = navyCache_->lookupAsync(
+      makeBufferView(key),
+      [&, this](navy::Status st, navy::BufferView, navy::Buffer v) {
+        if (st != navy::Status::NotFound) {
+          auto dItem = reinterpret_cast<const DipperItem*>(v.data());
+          hdl = createItem(key, *dItem);
+        }
+        b.post();
+      });
+  if (status != navy::Status::Ok) {
     return hdl;
   }
   b.wait();
@@ -126,7 +127,13 @@ typename NvmCache<C>::ItemHandle NvmCache<C>::peek(folly::StringPiece key) {
 }
 
 template <typename C>
-void NvmCache<C>::evictCB(folly::StringPiece key, folly::StringPiece value) {
+void NvmCache<C>::evictCB(navy::BufferView key,
+                          navy::BufferView value,
+                          navy::DestructorEvent event) {
+  if (event != cachelib::navy::DestructorEvent::Recycled) {
+    return;
+  }
+
   stats().numNvmEvictions.inc();
 
   const auto& dItem = *reinterpret_cast<const DipperItem*>(value.data());
@@ -146,7 +153,8 @@ void NvmCache<C>::evictCB(folly::StringPiece key, folly::StringPiece value) {
       ? cache_.nvmLargeEvictionAgeSecs_.trackValue(lifetime)
       : cache_.nvmSmallEvictionAgeSecs_.trackValue(lifetime);
 
-  auto hdl = cache_.peek(key);
+  auto hdl = cache_.peek(folly::StringPiece{
+      reinterpret_cast<const char*>(key.data()), key.size()});
   if (!hdl) {
     return;
   }
@@ -168,26 +176,14 @@ void NvmCache<C>::evictCB(folly::StringPiece key, folly::StringPiece value) {
 }
 
 template <typename C>
-NvmCache<C>::NvmCache(C& c, const Config& config, bool truncate)
+NvmCache<C>::NvmCache(C& c, Config config, bool truncate)
     : config_(config.validate()), cache_(c) {
-  constexpr folly::StringPiece kNavyReqOrderStr =
-      "dipper_navy_req_order_shards_power";
-  constexpr folly::StringPiece kTruncateFlashCache =
-      "dipper_truncate_flash_cache";
-
-  auto dipperOptions = config_.dipperOptions;
-  bool usingNavyReqOrdering = dipperOptions.get_ptr(kNavyReqOrderStr);
-  dipperOptions[kTruncateFlashCache] = truncate;
-
-  store_ = dipper::dipperOpen(
-      dipperOptions,
-      [this](folly::StringPiece key, folly::StringPiece val) {
-        this->evictCB(key, val);
-      },
-      [this](folly::StringPiece key, folly::StringPiece val) {
-        return this->compactionFilterCb(key, val);
+  navyCache_ = createNavyCache(
+      config_.dipperOptions,
+      [this](navy::BufferView k, navy::BufferView v, navy::DestructorEvent e) {
+        this->evictCB(k, v, e);
       },
-      !usingNavyReqOrdering);
+      truncate);
 }
 
 template <typename C>
@@ -300,6 +296,7 @@ void NvmCache<C>::put(const ItemHandle& hdl, PutToken token) {
   } else {
     iobuf = toIOBuf(std::move(dItem));
   }
+
   const auto valSize = iobuf.length();
   auto val = folly::ByteRange{iobuf.data(), iobuf.length()};
 
@@ -310,10 +307,10 @@ void NvmCache<C>::put(const ItemHandle& hdl, PutToken token) {
   auto putCleanup = [&putContexts, &ctx]() { putContexts.destroyContext(ctx); };
   auto guard = folly::makeGuard([putCleanup]() { putCleanup(); });
 
-  dipper::DipperPutFlags flags = item.isUnevictable()
-                                     ? dipper::DIPPER_PUT_NEVER_EVICT
-                                     : dipper::DIPPER_PUT_NONE;
-  dipper::DipperPutParams params(ctx.key(), val, flags);
+  navy::InsertOptions opts;
+  if (item.isUnevictable()) {
+    opts.setPermanent();
+  }
 
   if (item.isNvmClean() && item.isNvmEvicted()) {
     stats().numNvmPutFromClean.inc();
@@ -321,20 +318,22 @@ void NvmCache<C>::put(const ItemHandle& hdl, PutToken token) {
 
   // On a concurrent get, we remove the key from inflight evictions and hence
   // key not being present means a concurrent get happened with an inflight
-  // eviction, and we should abandon this write to dipper since we already
+  // eviction, and we should abandon this write to navy since we already
   // reported the key doesn't exist in the cache.
   const bool executed = token.executeIfValid([&]() {
-    auto rv =
-        store_->dipperPutAsync(params, [this, putCleanup, valSize](int err) {
+    auto status = navyCache_->insertAsync(
+        makeBufferView(ctx.key()), makeBufferView(val), opts,
+        [this, putCleanup, valSize](navy::Status st, navy::BufferView) {
           putCleanup();
-          if (err == 0) {
+          if (st == navy::Status::Ok) {
             cache_.nvmPutSize_.trackValue(valSize);
           }
         });
-    if (rv != dipper::DipperStatus::OK()) {
-      stats().numNvmPutErrs.inc();
-    } else {
+
+    if (status == navy::Status::Ok) {
       guard.dismiss();
+    } else {
+      stats().numNvmPutErrs.inc();
     }
   });
 
@@ -380,22 +379,22 @@ bool NvmCache<C>::mightHaveConcurrentFill(size_t shard,
 
 template <typename C>
 void NvmCache<C>::onGetComplete(GetCtx& ctx,
-                                int error,
-                                folly::ByteRange k,
-                                folly::ByteRange val) {
+                                navy::Status status,
+                                navy::BufferView k,
+                                navy::BufferView val) {
   auto key =
       folly::StringPiece{reinterpret_cast<const char*>(k.data()), k.size()};
   auto guard = folly::makeGuard([&ctx]() { ctx.cache.removeFromFillMap(ctx); });
-  // dipper got disabled while we were fetching. If so, safely return a miss.
-  // If dipper gets disabled beyond this point, it is okay since we fetched it
+  // navy got disabled while we were fetching. If so, safely return a miss.
+  // If navy gets disabled beyond this point, it is okay since we fetched it
   // before we got disabled.
   if (!isEnabled()) {
     return;
   }
 
-  if (error != 0) {
-    if (error != ENOENT) {
-      // instead of disabling dipper, we enqueue a delete and return a miss.
+  if (status != navy::Status::Ok) {
+    // instead of disabling navy, we enqueue a delete and return a miss.
+    if (status != navy::Status::NotFound) {
       remove(key);
     }
     stats().numNvmGetMiss.inc();
@@ -410,11 +409,11 @@ void NvmCache<C>::onGetComplete(GetCtx& ctx,
   const DipperItem* dItem = nullptr;
   folly::IOBuf decryptedIoBuf;
   if (config_.decryptCb) {
-    auto decryptedIoBufRet = config_.decryptCb(val);
+    auto decryptedIoBufRet = config_.decryptCb({val.data(), val.size()});
     if (!decryptedIoBufRet) {
       stats().numNvmDecryptionErrors.inc();
       stats().numNvmGetMiss.inc();
-      // instead of disabling dipper, we enqueue a delete and return a miss.
+      // instead of disabling navy, we enqueue a delete and return a miss.
       remove(key);
       return;
     }
@@ -451,7 +450,7 @@ void NvmCache<C>::onGetComplete(GetCtx& ctx,
     return;
   }
 
-  // by the time we filled from dipper, another thread inserted in RAM. We
+  // by the time we filled from navy, another thread inserted in RAM. We
   // disregard.
   if (cache_.insertImpl(it, AllocatorApiEvent::INSERT_FROM_NVM)) {
     if (config_.memoryInsertCb) {
@@ -460,7 +459,7 @@ void NvmCache<C>::onGetComplete(GetCtx& ctx,
     it.markWentToNvm();
     ctx.setItemHandle(std::move(it));
   }
-}
+} // namespace cachelib
 
 template <typename C>
 typename NvmCache<C>::ItemHandle NvmCache<C>::createItem(
@@ -517,10 +516,10 @@ typename NvmCache<C>::ItemHandle NvmCache<C>::createItem(
 }
 
 template <typename C>
-void NvmCache<C>::disableDipper(const std::string& msg) {
+void NvmCache<C>::disableNavy(const std::string& msg) {
   if (isEnabled()) {
-    dipperEnabled_ = false;
-    XLOGF(CRITICAL, "Disabling dipper. {}", msg);
+    navyEnabled_ = false;
+    XLOGF(CRITICAL, "Disabling navy. {}", msg);
   }
 }
 
@@ -545,12 +544,15 @@ void NvmCache<C>::remove(folly::StringPiece key) {
   auto& ctx = delContexts.createContext(key, std::move(tracker));
 
   // capture array reference for delContext. it is stable
-  auto delCleanup = [&delContexts, &ctx, this](int err) {
+  auto delCleanup = [&delContexts, &ctx, this](navy::Status status,
+                                               navy::BufferView) {
     delContexts.destroyContext(ctx);
-    if (err != 0 && err != ENOENT) {
-      // we set disable dipper since we failed to delete something
-      disableDipper(folly::sformat("Delete Failure. errno = {}", err));
+    if (status == navy::Status::Ok || status == navy::Status::NotFound) {
+      return;
     }
+    // we set disable navy since we failed to delete something
+    disableNavy(folly::sformat("Delete Failure. status = {}",
+                               static_cast<int>(status)));
   };
 
   stats().numNvmDeletes.inc();
@@ -559,38 +561,43 @@ void NvmCache<C>::remove(folly::StringPiece key) {
   // deletion.
   inflightPuts_[shard].invalidateToken(key);
 
-  dipper::DipperDeleteParams params(ctx.key());
-
   auto lock = getFillLockForShard(shard);
   cancelFillLocked(key, shard);
 
-  auto rv = store_->dipperDeleteAsync(params, delCleanup);
-  if (rv != dipper::DipperStatus::OK()) {
-    delCleanup(rv.getErrno());
+  auto status = navyCache_->removeAsync(makeBufferView(ctx.key()), delCleanup);
+  if (status != navy::Status::Ok) {
+    delCleanup(status, {});
   }
 }
 
 template <typename C>
 bool NvmCache<C>::shutDown() {
-  const auto rv = store_->shutDown();
-  dipperEnabled_ = false;
-  return rv;
+  navyEnabled_ = false;
+  try {
+    this->flushPendingOps();
+    navyCache_->persist();
+  } catch (const std::exception& e) {
+    XLOG(ERR) << "Got error persisting cache: " << e.what();
+    return false;
+  }
+  XLOG(INFO) << "Cache recovery saved to the Flash Device";
+  return true;
 }
 
 template <typename C>
 void NvmCache<C>::flushPendingOps() {
-  store_->dipperFlush();
+  navyCache_->flush();
 }
 
 template <typename C>
-bool NvmCache<C>::compactionFilterCb(folly::StringPiece /* unused */,
-                                     folly::StringPiece val) {
-  const auto* dItem = reinterpret_cast<const DipperItem*>(val.data());
-  bool expired = dItem->isExpired();
-  if (expired) {
-    stats().numNvmCompactionFiltered.inc();
-  }
-  return expired;
+std::unordered_map<std::string, double> NvmCache<C>::getStatsMap() const {
+  std::unordered_map<std::string, double> statsMap;
+  navyCache_->getCounters([&statsMap](folly::StringPiece key, double value) {
+    auto keyStr = key.str();
+    DCHECK_EQ(0, statsMap.count(keyStr));
+    statsMap.insert({std::move(keyStr), value});
+  });
+  return statsMap;
 }
 
 } // namespace cachelib
diff --git a/cachelib/allocator/nvmcache/NvmCache.h b/cachelib/allocator/nvmcache/NvmCache.h
index 60e5c8d25a..4352a8219f 100644
--- a/cachelib/allocator/nvmcache/NvmCache.h
+++ b/cachelib/allocator/nvmcache/NvmCache.h
@@ -11,18 +11,13 @@
 
 #include "cachelib/allocator/nvmcache/DipperItem.h"
 #include "cachelib/allocator/nvmcache/InFlightPuts.h"
+#include "cachelib/allocator/nvmcache/NavySetup.h"
 #include "cachelib/allocator/nvmcache/ReqContexts.h"
 #include "cachelib/allocator/nvmcache/TombStones.h"
 #include "cachelib/allocator/nvmcache/WaitContext.h"
 #include "cachelib/common/Exceptions.h"
 #include "cachelib/common/Utils.h"
 
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wconversion"
-#include "dipper/dipper.h"
-#include "dipper/dipper_types.h"
-#pragma GCC diagnostic pop
-
 namespace facebook {
 namespace cachelib {
 
@@ -111,7 +106,7 @@ class NvmCache {
       return configMap;
     }
 
-    Config validate() const {
+    Config validate() {
       const bool hasEncodeCb = !!encodeCb;
       const bool hasDecodeCb = !!decodeCb;
       if (hasEncodeCb != hasDecodeCb) {
@@ -126,14 +121,15 @@ class NvmCache {
             "Encrypt and Decrypt CBs must be both specified or both empty.");
       }
 
+      populateDefaultNavyOptions(dipperOptions);
       return *this;
     }
   };
 
   // @param c         the cache instance using nvmcache
-  // @param config    the config for dipper store
+  // @param config    the config for nvmcache
   // @param truncate  if we should truncate the nvmcache store
-  NvmCache(C& c, const Config& config, bool truncate);
+  NvmCache(C& c, Config config, bool truncate);
 
   // Look up item by key
   // @param key         key to lookup
@@ -151,9 +147,8 @@ class NvmCache {
   //                    false otherwise
   PutToken createPutToken(folly::StringPiece key);
 
-  // store the given item in dipper
-  // @param hdl         handle to cache item to store in dipper. will be not
-  //                    null
+  // store the given item in navy
+  // @param hdl         handle to cache item. should not be null
   // @param token       the put token for the item. this must have been
   //                    obtained before enqueueing the put to maintain
   //                    consistency
@@ -161,7 +156,7 @@ class NvmCache {
 
   // returns the current state of whether nvmcache is enabled or not. nvmcache
   // can be disabled if the backend implementation ends up in a corrupt state
-  bool isEnabled() const noexcept { return dipperEnabled_; }
+  bool isEnabled() const noexcept { return navyEnabled_; }
 
   // creates a delete tombstone for the key. This will ensure that all
   // concurrent gets and puts to nvmcache can synchronize with an upcoming
@@ -192,11 +187,11 @@ class NvmCache {
   void flushPendingOps();
 
   // Obtain stats in a <string -> double> representation.
-  std::unordered_map<std::string, double> getStatsMap() const {
-    return store_->getStatsMap();
-  }
+  std::unordered_map<std::string, double> getStatsMap() const;
 
-  size_t getSize() const noexcept { return store_->dipperGetSize(); }
+  size_t getSize() const noexcept {
+    return getNvmCacheSize(config_.dipperOptions);
+  }
 
  private:
   detail::Stats& stats() { return cache_.stats_; }
@@ -214,11 +209,11 @@ class NvmCache {
 
   std::unique_ptr<DipperItem> makeDipperItem(const ItemHandle& handle);
 
-  // wrap an item into a blob for writing into dipper.
+  // wrap an item into a blob for writing into navy.
   Blob makeBlob(const Item& it);
   uint32_t getStorageSizeInNvm(const Item& it);
 
-  // Holds all the necessary data to do an async dipper get
+  // Holds all the necessary data to do an async navy get
   // All of the supported operations aren't thread safe. The caller
   // needs to ensure thread safety
   struct GetCtx {
@@ -299,8 +294,8 @@ class NvmCache {
     getFillMap(key).erase(key);
   }
 
-  // Logs and disables dipper usage
-  void disableDipper(const std::string& msg);
+  // Logs and disables navy usage
+  void disableNavy(const std::string& msg);
 
   // returns true if there is a concurrent get request in flight fetching from
   // nvm.
@@ -333,24 +328,21 @@ class NvmCache {
   }
 
   void onGetComplete(GetCtx& ctx,
-                     int error,
-                     folly::ByteRange key,
-                     folly::ByteRange value);
+                     navy::Status s,
+                     navy::BufferView key,
+                     navy::BufferView value);
 
-  void evictCB(folly::StringPiece key, folly::StringPiece val);
+  void evictCB(navy::BufferView key,
+               navy::BufferView val,
+               navy::DestructorEvent e);
 
-  // callback passed down to engine that needs to filter items out when doing
-  // level compaction
-  //
-  // @param key      key for item
-  // @param val      value for item
-  //
-  // @return  True if the item should be thrown away
-  bool compactionFilterCb(folly::StringPiece key, folly::StringPiece value);
+  static navy::BufferView makeBufferView(folly::ByteRange b) {
+    return navy::BufferView{b.size(), b.data()};
+  }
 
   const Config config_;
-  C& cache_;                              //< cache allocator
-  std::atomic<bool> dipperEnabled_{true}; //< switch to turn off/on dipper
+  C& cache_;                            //< cache allocator
+  std::atomic<bool> navyEnabled_{true}; //< switch to turn off/on navy
 
   static constexpr size_t kShards = 8192;
 
@@ -370,11 +362,11 @@ class NvmCache {
   std::array<PutContexts, kShards> putContexts_;
   std::array<DelContexts, kShards> delContexts_;
   // co-ordination between in-flight evictions from cache that are not queued
-  // to dipper and in-flight gets into nvmcache that are not yet queued.
+  // to navy and in-flight gets into nvmcache that are not yet queued.
   std::array<InFlightPuts, kShards> inflightPuts_;
   std::array<TombStones, kShards> tombstones_;
 
-  std::unique_ptr<dipper::DipperStore> store_; //< dipper store
+  std::unique_ptr<cachelib::navy::AbstractCache> navyCache_;
 };
 
 } // namespace cachelib
diff --git a/cachelib/allocator/nvmcache/tests/NvmCacheTests.cpp b/cachelib/allocator/nvmcache/tests/NvmCacheTests.cpp
index 1ab06c94ad..aef676cc1d 100644
--- a/cachelib/allocator/nvmcache/tests/NvmCacheTests.cpp
+++ b/cachelib/allocator/nvmcache/tests/NvmCacheTests.cpp
@@ -58,7 +58,7 @@ TYPED_TEST(NvmCacheTest, EvictToDipperGet) {
   const auto nEvictions = this->evictionCount() - evictBefore;
   ASSERT_LT(0, nEvictions);
 
-  // read from reverse to no cause evictions to dipper
+  // read from reverse to no cause evictions to navy
   for (unsigned int i = nKeys + 100; i-- > 0;) {
     unsigned int index = i - 1;
     auto key = folly::sformat("key{}", index);
@@ -102,7 +102,7 @@ TYPED_TEST(NvmCacheTest, EvictToDipperGetCheckCtime) {
 
   /* sleep override */ std::this_thread::sleep_for(std::chrono::seconds(5));
 
-  // read from reverse to no cause evictions to dipper
+  // read from reverse to no cause evictions to navy
   for (unsigned int i = nKeys - 1; i > 0; i--) {
     auto key = std::string("blah") + folly::to<std::string>(i - 1);
     auto hdl = this->fetch(key, false /* ramOnly */);
@@ -430,7 +430,7 @@ TYPED_TEST(NvmCacheTest, NvmClean) {
 
   ASSERT_LT(0, numClean);
 
-  // we must have done evictions from ram to dipper
+  // we must have done evictions from ram to navy
   nEvictions = this->evictionCount() - evictBefore;
   nPuts = this->getStats().numNvmPuts - putsBefore;
   ASSERT_LT(0, nEvictions);
@@ -449,7 +449,7 @@ TYPED_TEST(NvmCacheTest, NvmClean) {
   }
   ASSERT_EQ(0, this->getStats().numNvmEvictions);
 
-  // we must have done evictions from ram to dipper
+  // we must have done evictions from ram to navy
   nEvictions = this->evictionCount() - evictBefore;
   nPuts = this->getStats().numNvmPuts - putsBefore;
   ASSERT_LT(0, nEvictions);
@@ -524,7 +524,7 @@ TYPED_TEST(NvmCacheTest, NvmEvicted) {
     ASSERT_TRUE(hdl->isNvmClean());
   }
 
-  // we must have done evictions from ram to dipper
+  // we must have done evictions from ram to navy
   ASSERT_EQ(this->evictionCount() - evictBefore, 2 * nKeys);
   ASSERT_EQ(nKeys, this->getStats().numNvmPuts - putsBefore);
   ASSERT_EQ(nKeys, this->getStats().numNvmPutFromClean);
diff --git a/cachelib/allocator/nvmcache/tests/NvmTestBase-inl.h b/cachelib/allocator/nvmcache/tests/NvmTestBase-inl.h
index 3fd7facf02..dca6189de9 100644
--- a/cachelib/allocator/nvmcache/tests/NvmTestBase-inl.h
+++ b/cachelib/allocator/nvmcache/tests/NvmTestBase-inl.h
@@ -3,7 +3,6 @@
 
 #include "cachelib/allocator/NvmCacheState.h"
 #include "cachelib/allocator/nvmcache/tests/NvmTestBase.h"
-#include "dipper/dipper_registry.h"
 
 namespace facebook {
 namespace cachelib {
@@ -34,7 +33,6 @@ NavyDipper::NavyDipper() {
 
 template <typename B>
 NvmCacheTest<B>::NvmCacheTest() {
-  facebook::dipper::registerBackend<facebook::dipper::NavyDipperFactory>();
   cacheDir_ = folly::sformat("/tmp/nvmcache-cachedir/{}", ::getpid());
   {
     allocConfig_.enableCachePersistence(cacheDir_);
@@ -115,7 +113,7 @@ void NvmCacheTest<B>::coldRoll() {
 
 template <typename B>
 void NvmCacheTest<B>::iceRoll() {
-  // shutdown with warm roll and indicatae that we want to drop dipper
+  // shutdown with warm roll and indicatae that we want to drop navy
   if (cache_->shutDown() != LruAllocator::ShutDownStatus::kSuccess) {
     throw std::runtime_error("Failed to ice roll");
   }
diff --git a/cachelib/allocator/nvmcache/tests/NvmTestBase.h b/cachelib/allocator/nvmcache/tests/NvmTestBase.h
index d796aafdae..a9738a63af 100644
--- a/cachelib/allocator/nvmcache/tests/NvmTestBase.h
+++ b/cachelib/allocator/nvmcache/tests/NvmTestBase.h
@@ -4,7 +4,6 @@
 
 #include "cachelib/allocator/CacheAllocator.h"
 #include "cachelib/common/Utils.h"
-#include "dipper/navy_dipper/navyif.h"
 
 namespace facebook {
 namespace cachelib {
diff --git a/cachelib/allocator/serialize/objects.thrift b/cachelib/allocator/serialize/allocator_objects.thrift
similarity index 92%
rename from cachelib/allocator/serialize/objects.thrift
rename to cachelib/allocator/serialize/allocator_objects.thrift
index b11e907314..800c61cba6 100644
--- a/cachelib/allocator/serialize/objects.thrift
+++ b/cachelib/allocator/serialize/allocator_objects.thrift
@@ -1,6 +1,6 @@
 namespace cpp2 facebook.cachelib.serialization
 
-include "cachelib/allocator/datastruct/serialize/objects.thrift"
+include "cachelib/allocator/datastruct/serialize/datastruct_objects.thrift"
 
 // Adding a new "required" field will cause the cache to be dropped
 // in the next release for our users. If the field needs to be required,
@@ -59,7 +59,7 @@ struct MMLruObject {
 
   6: required i64 insertionPoint,
   7: required i64 tailSize,
-  8: required DListObject lru,
+  8: required datastruct_objects.DListObject lru,
   9: required i64 compressedInsertionPoint,
 }
 
@@ -86,7 +86,7 @@ struct MM2QObject {
   11: i64 evictions = 0,
 
   // Warm, hot and cold lrus
-  12: required MultiDListObject lrus,
+  12: required datastruct_objects.MultiDListObject lrus,
 }
 
 struct MM2QCollection {
@@ -110,7 +110,7 @@ struct MMTinyLFUObject {
   2: i64 evictions = 0,
 
   // Warm, hot and cold lrus
-  3: required MultiDListObject lrus,
+  3: required datastruct_objects.MultiDListObject lrus,
 }
 
 struct MMTinyLFUCollection {
@@ -131,7 +131,7 @@ struct ChainedHashTableObject {
 struct MMTTLBucketObject {
   4: i64 expirationTime,
   5: i64 creationTime,
-  6: required DListObject dList,
+  6: required datastruct_objects.DListObject dList,
 }
 
 struct TTLBucketCollection {
diff --git a/cachelib/allocator/tests/NvmCacheStateTest.cpp b/cachelib/allocator/tests/NvmCacheStateTest.cpp
index 7dd757e673..52492e4e15 100644
--- a/cachelib/allocator/tests/NvmCacheStateTest.cpp
+++ b/cachelib/allocator/tests/NvmCacheStateTest.cpp
@@ -6,7 +6,7 @@
 #include <folly/io/RecordIO.h>
 #include "cachelib/allocator/CacheVersion.h"
 #include "cachelib/allocator/NvmCacheState.h"
-#include "cachelib/allocator/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/serialize/gen-cpp2/allocator_objects_types.h"
 #include "cachelib/common/Serialization.h"
 #include "cachelib/common/Utils.h"
 
diff --git a/cachelib/benchmarks/CMakeLists.txt b/cachelib/benchmarks/CMakeLists.txt
new file mode 100644
index 0000000000..40f2e5f9f2
--- /dev/null
+++ b/cachelib/benchmarks/CMakeLists.txt
@@ -0,0 +1,204 @@
+thrift_object("DataTypeBench" "" "cpp2" "frozen" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" THRIFT_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR})
+
+
+add_executable(
+  benchmarks_mmtype
+
+  MMTypeBench.h
+  MMTypeBench.cpp
+)
+target_link_libraries(
+  benchmarks_mmtype
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_binary_vs_hash_table   
+
+  BinarySearchVsHashTableBench.cpp
+    )
+target_link_libraries(
+    benchmarks_binary_vs_hash_table   
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_bucket_mutex
+
+  BucketMutexBench.cpp
+  
+  )
+target_link_libraries(
+    benchmarks_bucket_mutex
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_bytes_equal
+
+  BytesEqualBenchmark.cpp
+  )
+target_link_libraries(
+    benchmarks_bytes_equal
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_map_operation
+
+  CachelibMapOperationBench.cpp 
+  )
+target_link_libraries(
+    benchmarks_map_operation
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_map_workload
+
+  CachelibMapWorkloadBench.cpp
+  $<TARGET_OBJECTS:DataTypeBench-cpp2-obj>
+  )
+target_link_libraries(
+    benchmarks_map_workload
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_range_map_workload
+
+  CachelibRangeMapWorkloadBench.cpp
+  $<TARGET_OBJECTS:DataTypeBench-cpp2-obj>
+  )
+target_link_libraries(
+    benchmarks_range_map_workload
+
+  PUBLIC
+  cachelib_allocator
+  cachelib_common
+  cachelib_datatype
+  ${FOLLY_LIBRARIES}
+  ${FBTHRIFT_LIBRARIES}
+)
+
+add_executable(
+    benchmarks_compact_cache
+
+  CompactCacheBench.cpp
+  )
+target_link_libraries(
+    benchmarks_compact_cache
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_items_reaper
+
+  ItemsReaperBench.cpp
+  )
+target_link_libraries(
+    benchmarks_items_reaper
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_mmtype_access
+
+  MMTypeAccessBench.cpp
+  )
+target_link_libraries(
+    benchmarks_mmtype_access
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_mutex
+
+  MutexBench.cpp
+  )
+target_link_libraries(
+    benchmarks_mutex
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_ptr_compression
+
+  PtrCompressionBench.cpp
+  )
+ target_link_libraries(
+    benchmarks_ptr_compression
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+) 
+
+  add_executable(
+    benchmarks_slist
+
+  SListBench.cpp
+  )
+target_link_libraries(
+    benchmarks_slist
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+    benchmarks_thread_local
+
+  ThreadLocalBench.cpp
+  )
+target_link_libraries(
+    benchmarks_thread_local
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+  add_executable(
+    benchmarks_tl-bench
+
+  tl-bench/main.cpp
+)
+target_link_libraries(
+    benchmarks_tl-bench
+
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
diff --git a/cachelib/cachebench/CMakeLists.txt b/cachelib/cachebench/CMakeLists.txt
new file mode 100644
index 0000000000..7392e635cf
--- /dev/null
+++ b/cachelib/cachebench/CMakeLists.txt
@@ -0,0 +1,133 @@
+add_library(
+  cachelib_cachebench
+
+  cache/Cache-inl.h
+  cache/Cache.h
+  cache/CacheStats.h
+
+  consistency/LogEventStream.h
+  consistency/RingBuffer.h
+  consistency/ShortThreadId.h
+  consistency/ValueHistory.h
+  consistency/ValueTracker.h
+  consistency/LogEventStream.cpp
+  consistency/ShortThreadId.cpp
+  consistency/ValueHistory.cpp
+  consistency/ValueTracker.cpp
+
+  runner/CacheStressor.h
+  runner/FastShutdown.h
+  runner/IntegrationStressor.h
+  runner/OutputStressor.h
+  runner/ProgressTracker.h
+  runner/Runner.h
+  runner/Stressor.h
+  runner/TestStopper.h
+  runner/FastShutdown.cpp
+  runner/IntegrationStressor.cpp
+  runner/ProgressTracker.cpp
+  runner/Runner.cpp
+  runner/Stressor.cpp
+  runner/TestStopper.cpp
+
+  util/CacheConfig.h
+  util/Config.h
+  util/Exceptions.h
+  util/JSONConfig.h
+  util/NandWrites.h
+  util/Parallel.h
+  util/Parallel.cpp
+  util/Request.h
+  util/CacheConfig.cpp
+  util/Config.cpp
+  util/NandWrites.cpp
+
+
+  workload/GeneratorBase.h
+  workload/OnlineGenerator-inl.h
+  workload/OnlineGenerator.h
+  workload/PieceWiseReplayGenerator-inl.h
+  workload/PieceWiseReplayGenerator.h
+  workload/ReplayGenerator.h
+  workload/ReplayGeneratorBase.h
+  workload/WorkloadGenerator-inl.h
+  workload/WorkloadGenerator.h
+
+  workload/distributions/DiscreteDistribution.h
+  workload/distributions/FastDiscrete.h
+  workload/distributions/NormalDistribution.h
+  workload/distributions/RangeDistribution.h
+
+  )
+
+add_executable(
+nand_writes_util
+      
+  util/main.cpp
+  util/NandWrites.cpp
+  util/NandWrites.h
+
+)
+
+target_link_libraries(
+  cachelib_cachebench
+  PUBLIC
+    cachelib_shm
+    cachelib_common
+    navy
+    ${FOLLY_LIBRARIES}
+    ${FBTHRIFT_LIBRARIES}
+    memcached
+)
+target_link_libraries(
+  nand_writes_util
+  PUBLIC
+    ${FOLLY_LIBRARIES}
+)
+
+add_executable(
+        cachebench_test_bin
+
+        consistency/tests/RingBufferTest.cpp
+        consistency/tests/ShortThreadIdTest.cpp
+        consistency/tests/ValueHistoryTest.cpp
+        consistency/tests/ValueTrackerTest.cpp
+
+        workload/tests/WorkloadGeneratorTest.cpp
+    )
+
+target_link_libraries(
+        cachebench_test_bin
+
+        PUBLIC
+            cachelib_cachebench
+            cachelib_datatype
+            navy
+            cachelib_allocator
+            cachelib_common
+            GTest::GTest
+            GTest::Main
+            ${GMOCK_LIBRARIES}
+            ${FOLLY_LIBRARIES}
+)
+
+add_test(
+        NAME cachebench_tests
+        COMMAND cachebench_test_bin
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
+
+add_executable(
+    cachebench_bin
+
+    main.cpp
+    )
+
+target_link_libraries(
+    cachebench_bin
+    
+    cachelib_cachebench
+    cachelib_common
+    cachelib_datatype
+    ${FOLLY_LIBRARIES}
+    )
diff --git a/cachelib/cachebench/cache/Cache-inl.h b/cachelib/cachebench/cache/Cache-inl.h
index c691c3b36c..35a80ea855 100644
--- a/cachelib/cachebench/cache/Cache-inl.h
+++ b/cachelib/cachebench/cache/Cache-inl.h
@@ -9,8 +9,6 @@
 
 #include "cachelib/allocator/Util.h"
 #include "cachelib/cachebench/util/NandWrites.h"
-#include "dipper/dipper_registry.h"
-#include "dipper/navy_dipper/navyif.h"
 
 namespace facebook {
 namespace cachelib {
@@ -88,13 +86,10 @@ Cache<Allocator>::Cache(CacheConfig config,
     nvmConfig.dipperOptions = folly::dynamic::object;
     nvmConfig.dipperOptions["dipper_backend"] = config_.dipperBackend;
     nvmConfig.dipperOptions["dipper_force_reinit"] = true;
-    nvmConfig.dipperOptions["dipper_compression"] =
-        static_cast<int>(dipper::DipperCompressionMethod::DCM_NONE);
 
     nvmConfig.dipperOptions["dipper_async_threads"] =
         config_.dipperAsyncThreads;
 
-    facebook::dipper::registerBackend<facebook::dipper::NavyDipperFactory>();
     if (config_.dipperNavyUseMemoryDevice) {
       // nothing to do here.
     } else if (!config_.dipperDevicePath.empty()) {
diff --git a/cachelib/cachebench/cache/Cache.h b/cachelib/cachebench/cache/Cache.h
index 930179ce53..e3a3613aef 100644
--- a/cachelib/cachebench/cache/Cache.h
+++ b/cachelib/cachebench/cache/Cache.h
@@ -4,7 +4,7 @@
 #include <folly/hash/Hash.h>
 
 // Comment out the define for FB_ENV when we build for external environments
-#define CACHEBENCH_FB_ENV
+//#define CACHEBENCH_FB_ENV
 #ifdef CACHEBENCH_FB_ENV
 #include "cachelib/facebook/admin/CacheAdmin.h"
 #endif
diff --git a/cachelib/cachebench/main.cpp b/cachelib/cachebench/main.cpp
index e7b564249c..f3c55d9729 100644
--- a/cachelib/cachebench/main.cpp
+++ b/cachelib/cachebench/main.cpp
@@ -4,7 +4,7 @@
 #include <gflags/gflags.h>
 
 // Comment out the define for FB_ENV when we build for external environments
-#define CACHEBENCH_FB_ENV
+//#define CACHEBENCH_FB_ENV
 #ifdef CACHEBENCH_FB_ENV
 #include "cachelib/cachebench/fb303/FB303ThriftServer.h"
 #include "common/init/Init.h"
@@ -54,6 +54,7 @@ int main(int argc, char** argv) {
     return 1;
   }
 
+  gflags::SetUsageMessage("Welcome to the CacheBench command line utility!");
 #ifdef CACHEBENCH_FB_ENV
   facebook::initFacebook(&argc, &argv);
   std::unique_ptr<FB303ThriftService> fb303_;
diff --git a/cachelib/cachebench/runner/CacheStressor.h b/cachelib/cachebench/runner/CacheStressor.h
index 26fd4f733c..2eecb76d18 100644
--- a/cachelib/cachebench/runner/CacheStressor.h
+++ b/cachelib/cachebench/runner/CacheStressor.h
@@ -222,6 +222,7 @@ class CacheStressor : public Stressor {
     std::atomic<uint64_t> totalCount = 0;
 
     auto prePopulateFn = [&]() {
+      wg_->registerThread();
       std::mt19937 gen(folly::Random::rand32());
       std::discrete_distribution<> keyPoolDist(
           config_.keyPoolDistribution.begin(),
diff --git a/cachelib/cachebench/runner/IntegrationStressor.cpp b/cachelib/cachebench/runner/IntegrationStressor.cpp
index 1035e49a37..5f5c999a4e 100644
--- a/cachelib/cachebench/runner/IntegrationStressor.cpp
+++ b/cachelib/cachebench/runner/IntegrationStressor.cpp
@@ -345,8 +345,9 @@ void CachelibRangeMapStressor::readEntries(TestMap& map) {
   for (auto& kv : range) {
     auto res = map.lookup(kv.key);
     if (res == map.end()) {
+        uint32_t k = kv.key;
       throw std::runtime_error(folly::sformat(
-          "Bug: Key '{}' in a range disappeared from map", kv.key));
+          "Bug: Key '{}' in a range disappeared from map", k));
     }
   }
 }
diff --git a/cachelib/cachebench/runner/MicroStressor.h b/cachelib/cachebench/runner/MicroStressor.h
new file mode 100644
index 0000000000..bbfc03f92b
--- /dev/null
+++ b/cachelib/cachebench/runner/MicroStressor.h
@@ -0,0 +1,431 @@
+#pragma once
+
+#include <atomic>
+#include <iostream>
+#include <unordered_set>
+
+#include <event.h>
+#include <time.h>
+#include <folly/Random.h>
+
+#include "cachelib/cachebench/cache/Cache.h"
+#include "cachelib/cachebench/runner/Stressor.h"
+#include "cachelib/cachebench/runner/TestStopper.h"
+#include "cachelib/cachebench/util/Config.h"
+#include "cachelib/cachebench/util/Exceptions.h"
+#include "cachelib/cachebench/util/Parallel.h"
+#include "cachelib/cachebench/util/Request.h"
+#include "cachelib/cachebench/workload/GeneratorBase.h"
+#include "cachelib/external/memcached/protocol_binary.h"
+
+extern "C" int init_memcached(int argc, char ** argv, size_t size_in_mb, uint32_t nthreads);
+extern "C" int stop_memcached();
+extern "C" struct conn;
+extern "C" struct item;
+extern "C" item *item_get(const char *key, const size_t nkey, conn *c, const bool do_update);
+extern "C" conn *conn_new(const int sfd, int init_state, const int event_flags, const int read_buffer_size, int transport, struct event_base *base, void *ssl);
+extern "C" item *item_alloc(char *key, size_t nkey, int flags, unsigned int exptime, int nbytes);
+extern "C" item * process_cachebench_set(char * key, int nkey, char * val, int vlen, conn *c, int cmd);
+extern "C" int process_cachebench_get_or_touch(char * key, int nkey, conn *c, int cmd);
+extern "C" int store_item(item *item, int comm, conn* c);
+extern "C" struct LIBEVENT_THREAD;
+extern "C" struct LIBEVENT_THREAD * threads;
+extern "C" void bind_thread_helper(conn * c, uint32_t wid);
+extern "C" struct event_base * main_base;
+extern "C" int memcached_event_loop(bool * stop_main_loop);
+extern "C" char *stats_prefix_dump(int *length);
+extern "C" conn * connect_server(const char *hostname, in_port_t port,bool nonblock, const bool ssl);
+extern "C" void send_ascii_command(const char *buf);
+extern "C" void read_ascii_response(char *buffer, size_t size);
+extern "C" int init_tester(int argc, char **argv);
+extern "C" struct conn * con;
+extern "C" item * conn_fetch_item(conn * c);
+extern "C" item * conn_fetch_resp_item(conn * c);
+extern "C" void process_command(conn *c, char * command);
+
+
+namespace facebook {
+namespace cachelib {
+namespace cachebench {
+template <typename Allocator>
+class MicroStressor : public Stressor {
+ public:
+  using CacheT = Cache<Allocator>;
+  using Key = typename CacheT::Key;
+  using ItemHandle = typename CacheT::ItemHandle;
+
+  enum class Mode { Exclusive, Shared };
+
+  // @param wrapper   wrapper that encapsulate the CacheAllocator
+  // @param config    stress test config
+  MicroStressor(CacheConfig cacheConfig,
+                StressorConfig config,
+                std::unique_ptr<GeneratorBase>&& generator)
+      : config_(std::move(config)),
+        throughputStats_(config_.numThreads),
+        wg_(std::move(generator)),
+        hardcodedString_(genHardcodedString()) {
+    // threads and main_base are UNSAFE until this is called!
+    if(init_memcached(0, NULL, cacheConfig.cacheSizeMB, config_.numThreads) == EXIT_SUCCESS &&
+            init_tester(0, NULL) == EXIT_SUCCESS){
+        memcached_running_ = true;
+    }
+
+    // Fill up the cache with specified key/value distribution
+    memcached_loop_ = std::thread([loop=&stop_memcached_loop_]() {
+                memcached_event_loop(loop);
+    });
+    if (config_.prepopulateCache) {
+      try {
+        auto ret = prepopulateCache();
+        std::cout << folly::sformat("Inserted {:,} keys in {:.2f} mins",
+                                    ret.first,
+                                    ret.second.count() / 60.)
+                  << std::endl;
+        // wait for all the cache operations for prepopulation to complete.
+
+        //std::cout << "== Cache Stats ==" << std::endl;
+        //cache_->getStats().render(std::cout);
+        std::cout << std::endl;
+        int l;
+      } catch (const cachebench::EndOfTrace& ex) {
+        std::cout << "End of trace encountered during prepopulaiton, "
+                     "attempting to continue..."
+                  << std::endl;
+      }
+    }
+  }
+
+  ~MicroStressor() override { finish(); }
+
+  // Start the stress test
+  // We first launch a stats worker that will wake up periodically
+  // Then we start another worker thread that will be responsible for
+  // spawning all the stress test threads
+  void start() override {
+    startTime_ = std::chrono::system_clock::now();
+
+    std::cout << folly::sformat("Total {:.2f}M ops to be run",
+                                config_.numThreads * config_.numOps / 1e6)
+              << std::endl;
+    stressWorker_ = std::thread([this] {
+      std::vector<std::thread> workers;
+      for (uint64_t i = 0; i < config_.numThreads; ++i) {
+        conn * c = conn_new(i,0,0,1024,0,main_base,NULL);
+        bind_thread_helper(c, i);
+        workers.push_back(
+            std::thread([this, throughputStats = &throughputStats_.at(i), c=c]() {
+              stressByDiscreteDistribution(*throughputStats, c);
+            }));
+      }
+
+      for (auto& worker : workers) {
+        worker.join();
+      }
+
+      testDurationNs_ =
+          std::chrono::nanoseconds{std::chrono::system_clock::now() -
+                                   startTime_}
+              .count();
+      stop_memcached_loop_ = true;
+      std::cout << "waiting for mc" << std::endl;
+      memcached_loop_.join();
+    });
+  }
+
+  // Block until all stress workers are finished.
+  void finish() override {
+    if (stressWorker_.joinable()) {
+      stressWorker_.join();
+    }
+    if (memcached_running_){//  && stop_memcached() == EXIT_SUCCESS){
+        memcached_running_ = false;
+    }
+  }
+
+  std::chrono::time_point<std::chrono::system_clock> startTime()
+      const override {
+    return startTime_;
+  }
+
+  Stats getCacheStats() const override { Stats tmp; return tmp;}
+
+  ThroughputStats aggregateThroughputStats() const override {
+    if (throughputStats_.empty()) {
+      return {config_.name};
+    }
+
+    ThroughputStats res{config_.name};
+    for (const auto& stats : throughputStats_) {
+      res += stats;
+    }
+
+    return res;
+  }
+
+  void renderWorkloadGeneratorStats(uint64_t elapsedTimeNs,
+                                    std::ostream& out) const override {
+    wg_->renderStats(elapsedTimeNs, out);
+  }
+
+  uint64_t getTestDurationNs() const override { return testDurationNs_; }
+
+ private:
+  static std::string genHardcodedString() {
+    const std::string s = "The quick brown fox jumps over the lazy dog. ";
+    std::string val;
+    for (int i = 0; i < 4 * 1024 * 1024; i += s.size()) {
+      val += s;
+    }
+    return val;
+  }
+
+  folly::SharedMutex& getLock(Key key) {
+    auto bucket = MurmurHash2{}(key.data(), key.size()) % locks_.size();
+    return locks_[bucket];
+  }
+
+  // TODO maintain state on whether key has chained allocs and use it to only
+  // lock for keys with chained items.
+  void chainedItemLock(Mode mode, Key key) {
+    if (lockEnabled_) {
+      auto& lock = getLock(key);
+      mode == Mode::Shared ? lock.lock_shared() : lock.lock();
+    }
+  }
+  void chainedItemUnlock(Mode mode, Key key) {
+    if (lockEnabled_) {
+      auto& lock = getLock(key);
+      mode == Mode::Shared ? lock.unlock_shared() : lock.unlock();
+    }
+  }
+
+  // Fill the cache and make sure all pools are full.
+  // It's the generator's resposibility of providing keys that obey to the
+  // distribution
+  std::pair<size_t, std::chrono::seconds> prepopulateCache() {
+    std::atomic<uint64_t> totalCount = 0;
+
+    auto prePopulateFn = [&](uint32_t wid) {
+      conn * c = conn_new(wid,0,0,1024,0,main_base,NULL);
+      bind_thread_helper(c, wid);
+
+      std::mt19937 gen(folly::Random::rand32());
+      std::discrete_distribution<> keyPoolDist(
+          config_.keyPoolDistribution.begin(),
+          config_.keyPoolDistribution.end());
+      size_t count = 0;
+      std::unordered_set<PoolId> fullPools;
+      std::optional<uint64_t> lastRequestId = std::nullopt;
+
+      // in some cases eviction will never happen. To avoid infinite loop in
+      // this case, we use continuousCacheHits as the signal of cache full
+      size_t continuousCacheHits;
+      while (true) {
+        // get a pool according to keyPoolDistribution
+        const Request& req = getReq(wid%config_.opPoolDistribution.size(), gen, lastRequestId);
+        int found = process_cachebench_get_or_touch(&req.key.at(0), req.key.size(), c, PROTOCOL_BINARY_CMD_GET);
+        if (found == EXIT_SUCCESS) {
+          // Treat 1000 continuous cache hits as cache full
+          if (++continuousCacheHits > 1000) {
+            break;
+          }
+
+          if (req.requestId) {
+            wg_->notifyResult(*req.requestId, OpResultType::kGetHit);
+          }
+          continue;
+        }
+
+        // cache miss. Allocate and write to cache
+        continuousCacheHits = 0;
+        // req contains a new key, set it to cache
+        item * memcachedItem = process_cachebench_set(&req.key.at(0), req.key.size(), &hardcodedString_.at(0), *(req.sizeBegin) + 2, c, PROTOCOL_BINARY_CMD_SET);
+        if (! memcachedItem){
+            std::cout << "mc OOM" << std::endl;
+            break;
+        }
+        count++;
+        if(count > .1*config_.numKeys){
+            //TODO fix prepop to be more clever
+            break;
+        }
+        if (req.requestId) {
+          wg_->notifyResult(*req.requestId, OpResultType::kSetSuccess);
+        }
+        }
+      totalCount.fetch_add(count);
+    };
+
+    auto numThreads = config_.prepopulateThreads ? config_.prepopulateThreads
+                                                 : config_.numThreads;
+    auto duration = detail::executeParallelWid(prePopulateFn, numThreads);
+    return std::make_pair(totalCount.load(), duration);
+  }
+
+  // Runs a number of operations on the cache allocator. The actual
+  // operations and key/value used are determined by a set of random
+  // number generators
+  //
+  // Throughput and Hit/Miss rates are tracked here as well
+  //
+  // @param genOp       randomly generate operations
+  // @param genOpPool   randomly choose a pool to run this op in
+  //                    this pretty much only works for allocate
+  // @param genValSize  randomly chooses a size
+  // @param genChainedItemLen   randomly chooses length for the chain to append
+  // @param genChainedItemValSize   randomly choose a size for chained item
+  // @param stats       Throughput stats
+  void stressByDiscreteDistribution(ThroughputStats& stats, conn * c) {
+    wg_->registerThread();
+
+    std::mt19937 gen(folly::Random::rand32());
+    std::discrete_distribution<> opPoolDist(config_.opPoolDistribution.begin(),
+                                            config_.opPoolDistribution.end());
+    const uint64_t opDelayBatch = config_.opDelayBatch;
+    const uint64_t opDelayNs = config_.opDelayNs;
+    const std::chrono::nanoseconds opDelay(opDelayNs);
+
+    const bool needDelay = opDelayBatch != 0 && opDelayNs != 0;
+    uint64_t opCounter = 0;
+        auto throttleFn = [&] {
+      if (needDelay && ++opCounter == opDelayBatch) {
+        opCounter = 0;
+        std::this_thread::sleep_for(opDelay);
+      }
+    };
+
+    std::optional<uint64_t> lastRequestId = std::nullopt;
+    for (uint64_t i = 0;
+         i < config_.numOps && !shouldTestStop();
+         ++i) {
+      try {
+        ++stats.ops;
+
+        const auto pid = opPoolDist(gen);
+        const Request& req(getReq(pid, gen, lastRequestId));
+        std::string* key = &(req.key);
+        const OpType op = wg_->getOp(pid, gen, req.requestId);
+        std::string oneHitKey;
+        if (op == OpType::kLoneGet || op == OpType::kLoneSet) {
+          oneHitKey = Request::getUniqueKey();
+          key = &oneHitKey;
+        }
+
+        OpResultType result(OpResultType::kNop);
+        switch (op) {
+        case OpType::kLoneSet:
+        case OpType::kSet: {
+          result = setKey(pid, stats, key, *(req.sizeBegin), c);
+          throttleFn();
+          break;
+        }
+        case OpType::kAddChained:
+        case OpType::kLoneGet:
+        case OpType::kGet: {
+          ++stats.get;
+
+          Mode lockMode = Mode::Shared;
+
+          // TODO currently pure lookaside, we should
+          // add a distribution over sequences of requests/access patterns
+          // e.g. get-no-set and set-no-get
+          int found = process_cachebench_get_or_touch(&req.key.at(0), req.key.size(), c, PROTOCOL_BINARY_CMD_GET);
+          if (found == EXIT_FAILURE) {
+            ++stats.getMiss;
+            result = OpResultType::kGetMiss;
+
+            if (config_.enableLookaside) {
+              // allocate and insert on miss
+              // upgrade access privledges, (lock_upgrade is not
+              // appropriate here)
+              setKey(pid, stats, key, *(req.sizeBegin), c);
+            }
+          } else {
+            result = OpResultType::kGetHit;
+          }
+
+          throttleFn();
+          break;
+        }
+        case OpType::kDel: {
+          //TODO implement deletes, use the model below
+          ++stats.del;
+          //auto res = cache_->remove(*key);
+          //if (res == CacheT::RemoveRes::kNotFoundInRam) {
+          //  ++stats.delNotFound;
+          //}
+          throttleFn();
+          break;
+        }
+        default:
+          throw std::runtime_error(
+              folly::sformat("invalid operation generated: {}", (int)op));
+          break;
+        }
+
+        lastRequestId = req.requestId;
+        if (req.requestId) {
+          // req might be deleted after calling notifyResult()
+          wg_->notifyResult(*req.requestId, result);
+        }
+      } catch (const cachebench::EndOfTrace& ex) {
+        break;
+      }
+    }
+  }
+
+  OpResultType setKey(PoolId pid,
+                      ThroughputStats& stats,
+                      std::string* key,
+                      size_t size,
+                      conn * c
+                      ) {
+    ++stats.set;
+    
+    item * memcachedItem = process_cachebench_set(&key->at(0), key->size(), &hardcodedString_.at(0), size + 2, c, PROTOCOL_BINARY_CMD_SET);
+
+    if ( memcachedItem == NULL ) {
+      ++stats.setFailure;
+      return OpResultType::kSetFailure;
+    } else {
+      return OpResultType::kSetSuccess;
+    }
+  }
+
+  const Request& getReq(const PoolId& pid,
+                        std::mt19937& gen,
+                        std::optional<uint64_t>& lastRequestId) {
+      const Request& req(wg_->getReq(pid, gen, lastRequestId));
+      return req;
+  }
+
+  const StressorConfig config_;
+
+  std::vector<ThroughputStats> throughputStats_;
+
+  std::unique_ptr<GeneratorBase> wg_;
+
+  // locks when using chained item and moving.
+  std::array<folly::SharedMutex, 1024> locks_;
+
+  // if locking is enabled.
+  std::atomic<bool> lockEnabled_{false};
+
+  // memorize rng to improve random performance
+  folly::ThreadLocalPRNG rng;
+
+  std::string hardcodedString_;
+
+
+  std::thread stressWorker_;
+
+  std::chrono::time_point<std::chrono::system_clock> startTime_;
+  uint64_t testDurationNs_{0};
+  bool memcached_running_{false};
+  bool stop_memcached_loop_{false};
+  std::thread memcached_loop_;
+};
+} // namespace cachebench
+} // namespace cachelib
+} // namespace facebook
diff --git a/cachelib/cachebench/runner/Stressor.cpp b/cachelib/cachebench/runner/Stressor.cpp
index 5faaed40e8..bcfa8a0d04 100644
--- a/cachelib/cachebench/runner/Stressor.cpp
+++ b/cachelib/cachebench/runner/Stressor.cpp
@@ -2,6 +2,7 @@
 
 #include "cachelib/allocator/CacheAllocator.h"
 #include "cachelib/cachebench/runner/CacheStressor.h"
+#include "cachelib/cachebench/runner/MicroStressor.h"
 #include "cachelib/cachebench/runner/FastShutdown.h"
 #include "cachelib/cachebench/runner/IntegrationStressor.h"
 #include "cachelib/cachebench/runner/OutputStressor.h"
@@ -112,6 +113,11 @@ std::unique_ptr<Stressor> Stressor::makeStressor(
           cacheConfig, stressorConfig, std::move(generator));
     }
   }
+  if (stressorConfig.mode == "micro") {
+    auto generator = makeGenerator(stressorConfig);
+    return std::make_unique<MicroStressor<Lru2QAllocator>>(
+          cacheConfig, stressorConfig, std::move(generator));
+  }
   if (stressorConfig.mode == "stdout") {
     return std::make_unique<OutputStressor<LruAllocator>>(cacheConfig,
                                                           stressorConfig);
diff --git a/cachelib/cachebench/test_configs/comparisontest/assoc_altoona1_follower_pop.json b/cachelib/cachebench/test_configs/comparisontest/assoc_altoona1_follower_pop.json
new file mode 100644
index 0000000000..88182b18e0
--- /dev/null
+++ b/cachelib/cachebench/test_configs/comparisontest/assoc_altoona1_follower_pop.json
@@ -0,0 +1,1527 @@
+{
+  "oneHits": 0.025391725016963105,
+  "popularityWeights": [
+    15762992,
+    3040157,
+    1586370,
+    1136949,
+    1115646,
+    1070971,
+    1033447,
+    960640,
+    910114,
+    752020,
+    660558,
+    1292454,
+    569688,
+    519525,
+    496147,
+    463090,
+    447015,
+    440496,
+    402858,
+    397748,
+    382594,
+    664500,
+    324635,
+    307555,
+    275691,
+    268987,
+    259564,
+    501174,
+    231991,
+    227180,
+    223217,
+    218789,
+    424196,
+    408912,
+    401620,
+    375688,
+    366612,
+    717688,
+    532794,
+    524214,
+    343314,
+    502212,
+    1310456,
+    475932,
+    155727,
+    305742,
+    150089,
+    144821,
+    281414,
+    137692,
+    136043,
+    265020,
+    130835,
+    126708,
+    728874,
+    1305843,
+    117364,
+    113123,
+    326775,
+    322902,
+    425728,
+    416744,
+    102546,
+    602640,
+    97909,
+    289893,
+    190042,
+    92925,
+    275673,
+    268824,
+    265404,
+    87514,
+    83535,
+    165030,
+    163118,
+    80611,
+    157234,
+    232401,
+    151466,
+    149380,
+    221769,
+    72380,
+    142512,
+    140128,
+    758087,
+    272804,
+    134250,
+    196968,
+    711667,
+    316765,
+    250088,
+    183195,
+    302070,
+    178140,
+    175962,
+    57824,
+    169746,
+    112016,
+    275785,
+    162255,
+    211692,
+    260900,
+    154926,
+    459180,
+    201820,
+    199012,
+    393864,
+    97190,
+    288042,
+    237090,
+    93386,
+    457610,
+    317030,
+    223640,
+    176728,
+    174812,
+    215980,
+    982514,
+    84556,
+    125370,
+    246906,
+    244002,
+    80318,
+    198690,
+    117873,
+    77490,
+    191785,
+    112179,
+    258755,
+    292592,
+    397540,
+    214614,
+    389334,
+    69996,
+    103794,
+    272976,
+    270056,
+    267240,
+    494790,
+    456946,
+    580464,
+    254968,
+    378228,
+    187164,
+    493408,
+    396500,
+    362328,
+    328669,
+    177480,
+    146395,
+    230216,
+    199227,
+    225264,
+    139215,
+    247914,
+    163548,
+    161874,
+    133175,
+    210856,
+    208728,
+    129005,
+    355726,
+    276430,
+    223668,
+    49200,
+    267564,
+    168301,
+    70866,
+    140202,
+    138774,
+    205416,
+    135540,
+    156359,
+    221040,
+    152558,
+    129066,
+    191340,
+    231484,
+    333088,
+    288330,
+    285348,
+    383135,
+    299355,
+    295875,
+    156064,
+    192320,
+    247364,
+    244413,
+    223344,
+    183930,
+    163602,
+    286928,
+    319194,
+    157986,
+    330049,
+    292094,
+    493232,
+    319903,
+    300006,
+    346248,
+    456260,
+    257984,
+    175428,
+    331485,
+    281286,
+    231915,
+    198783,
+    227055,
+    344540,
+    296580,
+    293460,
+    623844,
+    186706,
+    156255,
+    210840,
+    305800,
+    357760,
+    231523,
+    215632,
+    453220,
+    250686,
+    221901,
+    374477,
+    268443,
+    265587,
+    162760,
+    210664,
+    441216,
+    254709,
+    192112,
+    201841,
+    258566,
+    278544,
+    287075,
+    386070,
+    157360,
+    233646,
+    176112,
+    315897,
+    345024,
+    256152,
+    253488,
+    386872,
+    248424,
+    348398,
+    324608,
+    391599,
+    357516,
+    275268,
+    398971,
+    327454,
+    314556,
+    216959,
+    196077,
+    295712,
+    228700,
+    208265,
+    286720,
+    372498,
+    386188,
+    399556,
+    361074,
+    382950,
+    404112,
+    366696,
+    420699,
+    424476,
+    307040,
+    359865,
+    324597,
+    305643,
+    473116,
+    368544,
+    311559,
+    481472,
+    387140,
+    427460,
+    335616,
+    389988,
+    343056,
+    424500,
+    378162,
+    360256,
+    452562,
+    285096,
+    389702,
+    372456,
+    539888,
+    404116,
+    296700,
+    357560,
+    404544,
+    356649,
+    383904,
+    471933,
+    442891,
+    420420,
+    356520,
+    411670,
+    326032,
+    449514,
+    427875,
+    389643,
+    413660,
+    525730,
+    514932,
+    488070,
+    418704,
+    472857,
+    447015,
+    416480,
+    412240,
+    545700,
+    434128,
+    414668,
+    375896,
+    420970,
+    600904,
+    546858,
+    474700,
+    380619,
+    441940,
+    437475,
+    496713,
+    487188,
+    442035,
+    437580,
+    441875,
+    498065,
+    407265,
+    560208,
+    445306,
+    361746,
+    460992,
+    382956,
+    483960,
+    443112,
+    383344,
+    489000,
+    565312,
+    486791,
+    394576,
+    522084,
+    468468,
+    419520,
+    458892,
+    483070,
+    449568,
+    490948,
+    506920,
+    491462,
+    537882,
+    440830,
+    503550,
+    571556,
+    509795,
+    465608,
+    483450,
+    488070,
+    457910,
+    497034,
+    417690,
+    465576,
+    500280,
+    528176,
+    555390,
+    467460,
+    541260,
+    480960,
+    444756,
+    539002,
+    527877,
+    511525,
+    495397,
+    436149,
+    495985,
+    546724,
+    493876,
+    460200,
+    591790,
+    504306,
+    499158,
+    476545,
+    449540,
+    518340,
+    571120,
+    445470,
+    457603,
+    563280,
+    494799,
+    491986,
+    491616,
+    506925,
+    577570,
+    498782,
+    487032,
+    488612,
+    543560,
+    609984,
+    465312,
+    545725,
+    515554,
+    453359,
+    507024,
+    483813,
+    496692,
+    536525,
+    542611,
+    542724,
+    550281,
+    477360,
+    483633,
+    568540,
+    504570,
+    486716,
+    490728,
+    520960,
+    524342,
+    525820,
+    574922,
+    609368,
+    454512,
+    580554,
+    543484,
+    578340,
+    498533,
+    537654,
+    469430,
+    540792,
+    530610,
+    560096,
+    561738,
+    525970,
+    504450,
+    483260,
+    521645,
+    563420,
+    568830,
+    639800,
+    560925,
+    508641,
+    576725,
+    537200,
+    546219,
+    526000,
+    615373,
+    490347,
+    536354,
+    552318,
+    484224,
+    585390,
+    576784,
+    585156,
+    572884,
+    475968,
+    537948,
+    548680,
+    596960,
+    595264,
+    557504,
+    561560,
+    591800,
+    532032,
+    580503,
+    555386,
+    509565,
+    563760,
+    522698,
+    568232,
+    518643,
+    595000,
+    542961,
+    548240,
+    558144,
+    584031,
+    473551,
+    581241,
+    562045,
+    586322,
+    555399,
+    529511,
+    593630,
+    502170,
+    535408,
+    522978,
+    551684,
+    550095,
+    508288,
+    597921,
+    530064,
+    556592,
+    541600,
+    575057,
+    490941,
+    537075,
+    559143,
+    573045,
+    571511,
+    593657,
+    546840,
+    594686,
+    555787,
+    590841,
+    568727,
+    499705,
+    554528,
+    524370,
+    549984,
+    527597,
+    566280,
+    551785,
+    565250,
+    557208,
+    575752,
+    566875,
+    577212,
+    594503,
+    613664,
+    513420,
+    509442,
+    491985,
+    544839,
+    535182,
+    543186,
+    559317,
+    589965,
+    531432,
+    532140,
+    578349,
+    561267,
+    583800,
+    592698,
+    567891,
+    583557,
+    611721,
+    528165,
+    489020,
+    535440,
+    532800,
+    530575,
+    501960,
+    498945,
+    524400,
+    556465,
+    555750,
+    555805,
+    551760,
+    552885,
+    591680,
+    561850,
+    590100,
+    587640,
+    607620,
+    598185,
+    614400,
+    498095,
+    453560,
+    496908,
+    540030,
+    523778,
+    527250,
+    493801,
+    510130,
+    543411,
+    569374,
+    543150,
+    526149,
+    549648,
+    554974,
+    545112,
+    605680,
+    584877,
+    578136,
+    621775,
+    642466,
+    700245,
+    661808,
+    627815,
+    624483,
+    466141,
+    487808,
+    496635,
+    469220,
+    514591,
+    521992,
+    500180,
+    487056,
+    511500,
+    519792,
+    500609,
+    517104,
+    509694,
+    541060,
+    535074,
+    557530,
+    547682,
+    552048,
+    582855,
+    615164,
+    596066,
+    594012,
+    600907,
+    597080,
+    607452,
+    652288,
+    632944,
+    651820,
+    627585,
+    657624,
+    663157,
+    648282,
+    682486,
+    687200,
+    454282,
+    473655,
+    475552,
+    478837,
+    459648,
+    473297,
+    489140,
+    492636,
+    484537,
+    507286,
+    506574,
+    530250,
+    531802,
+    520353,
+    542321,
+    591347,
+    557370,
+    590549,
+    567847,
+    595296,
+    598170,
+    589620,
+    572526,
+    598866,
+    603301,
+    611961,
+    612190,
+    615472,
+    613491,
+    605345,
+    614308,
+    626670,
+    648907,
+    632075,
+    646161,
+    681609,
+    666000,
+    660018,
+    690668,
+    686273,
+    700128,
+    718520,
+    729302,
+    730824,
+    734333,
+    750605,
+    758100,
+    773839,
+    784467,
+    380556,
+    392980,
+    393820,
+    418560,
+    400045,
+    419052,
+    408549,
+    419244,
+    405860,
+    421110,
+    416965,
+    432872,
+    428736,
+    443932,
+    431715,
+    464100,
+    435501,
+    450754,
+    444609,
+    464880,
+    472894,
+    465192,
+    480249,
+    487388,
+    473925,
+    477744,
+    493772,
+    504432,
+    495012,
+    515060,
+    504735,
+    521968,
+    501897,
+    544962,
+    509340,
+    553024,
+    550494,
+    542314,
+    537044,
+    575280,
+    555072,
+    592354,
+    564699,
+    606200,
+    573815,
+    608040,
+    601974,
+    618488,
+    615111,
+    636700,
+    632835,
+    682560,
+    641832,
+    674682,
+    663840,
+    694936,
+    666328,
+    731640,
+    698681,
+    742560,
+    719511,
+    763268,
+    746068,
+    793080,
+    783860,
+    833306,
+    809886,
+    864576,
+    830180,
+    890340,
+    875916,
+    941248,
+    920187,
+    979472,
+    948325,
+    1054296,
+    1022994,
+    1074062,
+    1109199,
+    1165940,
+    1130557,
+    1267650,
+    1215211,
+    1386416,
+    1338210,
+    1510390,
+    1451320,
+    1666488,
+    1611676,
+    1868540,
+    1905219,
+    2243584,
+    2254413,
+    2777778,
+    2781350,
+    3715564,
+    4049589,
+    6656638
+  ],
+  "popularityBuckets": [
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    2,
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    1,
+    2,
+    1,
+    1,
+    1,
+    1,
+    1,
+    2,
+    1,
+    1,
+    1,
+    1,
+    2,
+    2,
+    2,
+    2,
+    2,
+    4,
+    3,
+    3,
+    2,
+    3,
+    8,
+    3,
+    1,
+    2,
+    1,
+    1,
+    2,
+    1,
+    1,
+    2,
+    1,
+    1,
+    6,
+    11,
+    1,
+    1,
+    3,
+    3,
+    4,
+    4,
+    1,
+    6,
+    1,
+    3,
+    2,
+    1,
+    3,
+    3,
+    3,
+    1,
+    1,
+    2,
+    2,
+    1,
+    2,
+    3,
+    2,
+    2,
+    3,
+    1,
+    2,
+    2,
+    11,
+    4,
+    2,
+    3,
+    11,
+    5,
+    4,
+    3,
+    5,
+    3,
+    3,
+    1,
+    3,
+    2,
+    5,
+    3,
+    4,
+    5,
+    3,
+    9,
+    4,
+    4,
+    8,
+    2,
+    6,
+    5,
+    2,
+    10,
+    7,
+    5,
+    4,
+    4,
+    5,
+    23,
+    2,
+    3,
+    6,
+    6,
+    2,
+    5,
+    3,
+    2,
+    5,
+    3,
+    7,
+    8,
+    11,
+    6,
+    11,
+    2,
+    3,
+    8,
+    8,
+    8,
+    15,
+    14,
+    18,
+    8,
+    12,
+    6,
+    16,
+    13,
+    12,
+    11,
+    6,
+    5,
+    8,
+    7,
+    8,
+    5,
+    9,
+    6,
+    6,
+    5,
+    8,
+    8,
+    5,
+    14,
+    11,
+    9,
+    2,
+    11,
+    7,
+    3,
+    6,
+    6,
+    9,
+    6,
+    7,
+    10,
+    7,
+    6,
+    9,
+    11,
+    16,
+    14,
+    14,
+    19,
+    15,
+    15,
+    8,
+    10,
+    13,
+    13,
+    12,
+    10,
+    9,
+    16,
+    18,
+    9,
+    19,
+    17,
+    29,
+    19,
+    18,
+    21,
+    28,
+    16,
+    11,
+    21,
+    18,
+    15,
+    13,
+    15,
+    23,
+    20,
+    20,
+    43,
+    13,
+    11,
+    15,
+    22,
+    26,
+    17,
+    16,
+    34,
+    19,
+    17,
+    29,
+    21,
+    21,
+    13,
+    17,
+    36,
+    21,
+    16,
+    17,
+    22,
+    24,
+    25,
+    34,
+    14,
+    21,
+    16,
+    29,
+    32,
+    24,
+    24,
+    37,
+    24,
+    34,
+    32,
+    39,
+    36,
+    28,
+    41,
+    34,
+    33,
+    23,
+    21,
+    32,
+    25,
+    23,
+    32,
+    42,
+    44,
+    46,
+    42,
+    45,
+    48,
+    44,
+    51,
+    52,
+    38,
+    45,
+    41,
+    39,
+    61,
+    48,
+    41,
+    64,
+    52,
+    58,
+    46,
+    54,
+    48,
+    60,
+    54,
+    52,
+    66,
+    42,
+    58,
+    56,
+    82,
+    62,
+    46,
+    56,
+    64,
+    57,
+    62,
+    77,
+    73,
+    70,
+    60,
+    70,
+    56,
+    78,
+    75,
+    69,
+    74,
+    95,
+    94,
+    90,
+    78,
+    89,
+    85,
+    80,
+    80,
+    107,
+    86,
+    83,
+    76,
+    86,
+    124,
+    114,
+    100,
+    81,
+    95,
+    95,
+    109,
+    108,
+    99,
+    99,
+    101,
+    115,
+    95,
+    132,
+    106,
+    87,
+    112,
+    94,
+    120,
+    111,
+    97,
+    125,
+    146,
+    127,
+    104,
+    139,
+    126,
+    114,
+    126,
+    134,
+    126,
+    139,
+    145,
+    142,
+    157,
+    130,
+    150,
+    172,
+    155,
+    143,
+    150,
+    153,
+    145,
+    159,
+    135,
+    152,
+    165,
+    176,
+    187,
+    159,
+    186,
+    167,
+    156,
+    191,
+    189,
+    185,
+    181,
+    161,
+    185,
+    206,
+    188,
+    177,
+    230,
+    198,
+    198,
+    191,
+    182,
+    212,
+    236,
+    186,
+    193,
+    240,
+    213,
+    214,
+    216,
+    225,
+    259,
+    226,
+    223,
+    226,
+    254,
+    288,
+    222,
+    263,
+    251,
+    223,
+    252,
+    243,
+    252,
+    275,
+    281,
+    284,
+    291,
+    255,
+    261,
+    310,
+    278,
+    271,
+    276,
+    296,
+    301,
+    305,
+    337,
+    361,
+    272,
+    351,
+    332,
+    357,
+    311,
+    339,
+    299,
+    348,
+    345,
+    368,
+    373,
+    353,
+    342,
+    331,
+    361,
+    394,
+    402,
+    457,
+    405,
+    371,
+    425,
+    400,
+    411,
+    400,
+    473,
+    381,
+    421,
+    438,
+    388,
+    474,
+    472,
+    484,
+    479,
+    402,
+    459,
+    473,
+    520,
+    524,
+    496,
+    505,
+    538,
+    489,
+    539,
+    521,
+    483,
+    540,
+    506,
+    556,
+    513,
+    595,
+    549,
+    560,
+    576,
+    609,
+    499,
+    619,
+    605,
+    638,
+    611,
+    589,
+    667,
+    570,
+    614,
+    606,
+    646,
+    651,
+    608,
+    723,
+    648,
+    688,
+    677,
+    727,
+    627,
+    693,
+    729,
+    755,
+    761,
+    799,
+    744,
+    818,
+    773,
+    831,
+    809,
+    719,
+    806,
+    770,
+    816,
+    791,
+    858,
+    845,
+    875,
+    872,
+    911,
+    907,
+    934,
+    973,
+    1016,
+    860,
+    862,
+    841,
+    941,
+    934,
+    958,
+    997,
+    1063,
+    968,
+    980,
+    1077,
+    1057,
+    1112,
+    1142,
+    1107,
+    1151,
+    1221,
+    1067,
+    998,
+    1104,
+    1110,
+    1117,
+    1068,
+    1073,
+    1140,
+    1223,
+    1235,
+    1249,
+    1254,
+    1271,
+    1376,
+    1322,
+    1405,
+    1416,
+    1482,
+    1477,
+    1536,
+    1261,
+    1160,
+    1284,
+    1410,
+    1382,
+    1406,
+    1331,
+    1390,
+    1497,
+    1586,
+    1530,
+    1499,
+    1584,
+    1618,
+    1608,
+    1808,
+    1767,
+    1768,
+    1925,
+    2014,
+    2223,
+    2128,
+    2045,
+    2061,
+    1559,
+    1648,
+    1695,
+    1618,
+    1793,
+    1838,
+    1780,
+    1752,
+    1860,
+    1911,
+    1861,
+    1944,
+    1938,
+    2081,
+    2082,
+    2195,
+    2182,
+    2226,
+    2379,
+    2542,
+    2494,
+    2517,
+    2579,
+    2596,
+    2676,
+    2912,
+    2864,
+    2990,
+    2919,
+    3102,
+    3173,
+    3147,
+    3362,
+    3436,
+    2306,
+    2429,
+    2464,
+    2507,
+    2432,
+    2531,
+    2644,
+    2692,
+    2677,
+    2834,
+    2862,
+    3030,
+    3074,
+    3043,
+    3209,
+    3541,
+    3378,
+    3623,
+    3527,
+    3744,
+    3810,
+    3804,
+    3742,
+    3966,
+    4049,
+    4163,
+    4222,
+    4304,
+    4351,
+    4355,
+    4484,
+    4642,
+    4879,
+    4825,
+    5009,
+    5367,
+    5328,
+    5366,
+    5708,
+    5767,
+    5984,
+    6248,
+    6454,
+    6584,
+    6737,
+    7015,
+    7220,
+    7513,
+    7767,
+    3844,
+    4010,
+    4060,
+    4360,
+    4211,
+    4458,
+    4393,
+    4557,
+    4460,
+    4679,
+    4685,
+    4919,
+    4928,
+    5162,
+    5079,
+    5525,
+    5247,
+    5497,
+    5489,
+    5811,
+    5986,
+    5964,
+    6237,
+    6413,
+    6319,
+    6456,
+    6764,
+    7006,
+    6972,
+    7358,
+    7315,
+    7676,
+    7491,
+    8257,
+    7836,
+    8641,
+    8738,
+    8747,
+    8804,
+    9588,
+    9408,
+    10213,
+    9907,
+    10825,
+    10433,
+    11260,
+    11358,
+    11894,
+    12061,
+    12734,
+    12915,
+    14220,
+    13656,
+    14667,
+    14752,
+    15794,
+    15496,
+    17420,
+    17041,
+    18564,
+    18449,
+    20086,
+    20164,
+    22030,
+    22396,
+    24509,
+    24542,
+    27018,
+    26780,
+    29678,
+    30204,
+    33616,
+    34081,
+    37672,
+    37933,
+    43929,
+    44478,
+    48821,
+    52819,
+    58297,
+    59503,
+    70425,
+    71483,
+    86651,
+    89214,
+    107885,
+    111640,
+    138874,
+    146516,
+    186854,
+    211691,
+    280448,
+    322059,
+    462963,
+    556270,
+    928891,
+    1349863,
+    3328319
+  ]
+}
diff --git a/cachelib/cachebench/test_configs/comparisontest/assoc_altoona1_follower_sizes.json b/cachelib/cachebench/test_configs/comparisontest/assoc_altoona1_follower_sizes.json
new file mode 100644
index 0000000000..efd6ba5e71
--- /dev/null
+++ b/cachelib/cachebench/test_configs/comparisontest/assoc_altoona1_follower_sizes.json
@@ -0,0 +1,4560 @@
+{
+  "valSizeRangeProbability": [
+    699850,
+    556850,
+    456850,
+    281954,
+    447390,
+    305899,
+    211895,
+    91114,
+    62505,
+    87219,
+    82433,
+    32352,
+    17676,
+    128493,
+    8234,
+    55653,
+    14913,
+    58719,
+    7786,
+    18191,
+    6881,
+    59801,
+    8193,
+    3382,
+    36986,
+    5231,
+    3879,
+    15751,
+    4642,
+    39627,
+    3189,
+    11088,
+    3011,
+    1869,
+    23126,
+    3498,
+    4747,
+    18554,
+    6555,
+    1784,
+    29360,
+    5093,
+    803,
+    5823,
+    18716,
+    1156,
+    3941,
+    3500,
+    14370,
+    1594,
+    2301,
+    2555,
+    18683,
+    4561,
+    763,
+    1325,
+    15076,
+    1281,
+    5640,
+    2095,
+    1520,
+    12856,
+    1322,
+    1982,
+    4155,
+    424,
+    13863,
+    5015,
+    2229,
+    1788,
+    456,
+    17915,
+    3573,
+    1689,
+    2662,
+    1674,
+    346,
+    9910,
+    3266,
+    169,
+    1434,
+    2763,
+    2045,
+    12026,
+    1104,
+    3286,
+    200,
+    1602,
+    1827,
+    2280,
+    12045,
+    35,
+    1564,
+    1751,
+    148,
+    506,
+    2235,
+    533,
+    10978,
+    132,
+    1865,
+    115,
+    552,
+    1752,
+    1626,
+    766,
+    8958,
+    136,
+    2276,
+    252,
+    3094,
+    75,
+    2547,
+    414,
+    8501,
+    1821,
+    74,
+    3161,
+    178,
+    630,
+    50,
+    1635,
+    883,
+    394,
+    9526,
+    513,
+    499,
+    2463,
+    43,
+    63,
+    48,
+    960,
+    3155,
+    685,
+    7742,
+    1048,
+    40,
+    743,
+    671,
+    1089,
+    66,
+    4116,
+    1439,
+    651,
+    335,
+    6062,
+    1438,
+    31,
+    356,
+    1074,
+    2441,
+    477,
+    503,
+    155,
+    1010,
+    664,
+    8541,
+    365,
+    883,
+    64,
+    6248,
+    1623,
+    842,
+    947,
+    126,
+    50,
+    545,
+    593,
+    2394,
+    6492,
+    598,
+    4100,
+    64,
+    50,
+    537,
+    1398,
+    1585,
+    698,
+    760,
+    46,
+    1575,
+    578,
+    1364,
+    9075,
+    102,
+    272,
+    889,
+    91,
+    763,
+    1432,
+    199,
+    66,
+    398,
+    10,
+    1461,
+    980,
+    1530,
+    32,
+    6137,
+    692,
+    468,
+    581,
+    391,
+    421,
+    72,
+    1165,
+    997,
+    625,
+    60,
+    7,
+    1199,
+    457,
+    676,
+    826,
+    6206,
+    234,
+    371,
+    1286,
+    200,
+    63,
+    1260,
+    2511,
+    566,
+    33,
+    1757,
+    161,
+    15,
+    905,
+    1062,
+    1537,
+    36,
+    6458,
+    23,
+    368,
+    406,
+    3694,
+    107,
+    9,
+    374,
+    523,
+    1837,
+    7,
+    938,
+    34,
+    48,
+    25,
+    70,
+    916,
+    423,
+    167,
+    5305,
+    1640,
+    33,
+    673,
+    1893,
+    126,
+    14,
+    269,
+    471,
+    864,
+    94,
+    29,
+    700,
+    190,
+    6,
+    35,
+    410,
+    640,
+    920,
+    51,
+    6807,
+    731,
+    467,
+    676,
+    747,
+    726,
+    2145,
+    14,
+    250,
+    230,
+    578,
+    799,
+    14,
+    31,
+    679,
+    96,
+    16,
+    2374,
+    342,
+    694,
+    574,
+    59,
+    3492,
+    752,
+    47,
+    655,
+    962,
+    1921,
+    2,
+    17,
+    1,
+    384,
+    88,
+    707,
+    235,
+    3,
+    219,
+    846,
+    44,
+    5,
+    37,
+    325,
+    420,
+    186,
+    464,
+    6521,
+    4,
+    983,
+    758,
+    1511,
+    423,
+    573,
+    22,
+    16,
+    25,
+    5,
+    338,
+    54,
+    1116,
+    307,
+    13,
+    308,
+    8,
+    101,
+    6,
+    1443,
+    765,
+    91,
+    609,
+    90,
+    4271,
+    615,
+    17,
+    414,
+    1477,
+    2507,
+    654,
+    18,
+    24,
+    6,
+    4,
+    460,
+    76,
+    382,
+    795,
+    10,
+    1431,
+    453,
+    24,
+    95,
+    4,
+    647,
+    258,
+    392,
+    168,
+    337,
+    3996,
+    7,
+    702,
+    274,
+    1005,
+    743,
+    15,
+    93,
+    580,
+    11,
+    189,
+    7,
+    11,
+    1220,
+    41,
+    1020,
+    315,
+    11,
+    503,
+    529,
+    10,
+    53,
+    9,
+    31,
+    5,
+    640,
+    79,
+    616,
+    3692,
+    454,
+    507,
+    126,
+    561,
+    358,
+    220,
+    232,
+    774,
+    7,
+    9,
+    16,
+    12,
+    224,
+    169,
+    82,
+    530,
+    644,
+    10,
+    10,
+    11,
+    367,
+    16,
+    69,
+    15,
+    570,
+    166,
+    601,
+    121,
+    486,
+    3786,
+    208,
+    1512,
+    471,
+    251,
+    345,
+    128,
+    12,
+    34,
+    573,
+    7,
+    10,
+    53,
+    10,
+    275,
+    57,
+    422,
+    391,
+    225,
+    5,
+    5,
+    292,
+    520,
+    17,
+    63,
+    5,
+    441,
+    25,
+    232,
+    229,
+    101,
+    502,
+    3095,
+    142,
+    41,
+    532,
+    87,
+    12,
+    17,
+    330,
+    9,
+    264,
+    333,
+    9,
+    7,
+    9,
+    11,
+    6,
+    308,
+    10,
+    102,
+    1040,
+    177,
+    13,
+    8,
+    347,
+    197,
+    17,
+    45,
+    3,
+    375,
+    240,
+    588,
+    109,
+    697,
+    9699,
+    29,
+    91,
+    235,
+    32,
+    36,
+    178,
+    15,
+    124,
+    6,
+    93,
+    466,
+    11,
+    3,
+    8,
+    7,
+    3,
+    519,
+    3,
+    54,
+    268,
+    414,
+    241,
+    5,
+    319,
+    530,
+    3,
+    3,
+    57,
+    4,
+    250,
+    169,
+    227,
+    7,
+    140,
+    1474,
+    2993,
+    76,
+    337,
+    17,
+    714,
+    14,
+    24,
+    2,
+    10,
+    219,
+    4,
+    12,
+    428,
+    37,
+    5,
+    106,
+    3,
+    3,
+    303,
+    3,
+    28,
+    687,
+    376,
+    170,
+    3,
+    307,
+    6,
+    332,
+    13,
+    47,
+    4,
+    2808,
+    196,
+    437,
+    46,
+    3488,
+    66,
+    29,
+    52,
+    22,
+    276,
+    6,
+    27,
+    11,
+    15,
+    4,
+    123,
+    2,
+    318,
+    537,
+    6,
+    8,
+    7,
+    6,
+    8,
+    451,
+    11,
+    34,
+    9,
+    383,
+    355,
+    198,
+    11,
+    2201,
+    7,
+    416,
+    2,
+    8,
+    52,
+    5,
+    26,
+    3,
+    123,
+    387,
+    129,
+    2720,
+    7,
+    57,
+    33,
+    17,
+    22,
+    470,
+    3,
+    19,
+    487,
+    3,
+    1,
+    190,
+    6,
+    26,
+    76,
+    372,
+    5,
+    246,
+    3,
+    3,
+    2303,
+    12,
+    41,
+    4,
+    491,
+    298,
+    103,
+    8,
+    3,
+    8,
+    7,
+    352,
+    8,
+    3,
+    20,
+    3,
+    22,
+    817,
+    174,
+    331,
+    60,
+    3092,
+    7,
+    75,
+    12,
+    37,
+    34,
+    9,
+    311,
+    24,
+    14,
+    9,
+    5,
+    155,
+    7,
+    1271,
+    50,
+    818,
+    6,
+    6,
+    8,
+    6,
+    9,
+    281,
+    11,
+    34,
+    7,
+    473,
+    259,
+    160,
+    3,
+    5,
+    21,
+    1,
+    226,
+    5,
+    3,
+    31,
+    8,
+    34,
+    4,
+    148,
+    363,
+    4,
+    137,
+    2210,
+    598,
+    48,
+    11,
+    43,
+    13,
+    12,
+    385,
+    11,
+    823,
+    2,
+    32,
+    4,
+    116,
+    8,
+    9,
+    33,
+    233,
+    6,
+    4,
+    1,
+    1,
+    4,
+    314,
+    13,
+    38,
+    4,
+    370,
+    304,
+    141,
+    4,
+    10,
+    5,
+    299,
+    4,
+    2,
+    23,
+    4,
+    18,
+    2,
+    138,
+    199,
+    6,
+    110,
+    2192,
+    2969,
+    86,
+    11,
+    13,
+    15,
+    16,
+    4,
+    279,
+    13,
+    14,
+    7,
+    10,
+    4,
+    156,
+    7,
+    16,
+    63,
+    310,
+    8,
+    2,
+    4,
+    4,
+    4,
+    4,
+    323,
+    4,
+    14,
+    5,
+    335,
+    139,
+    117,
+    3,
+    6,
+    17,
+    4,
+    213,
+    3,
+    4,
+    503,
+    3,
+    17,
+    4,
+    168,
+    313,
+    4,
+    104,
+    2102,
+    488,
+    52,
+    12,
+    13,
+    23,
+    14,
+    8,
+    12,
+    324,
+    3,
+    15,
+    3,
+    3,
+    7,
+    5,
+    88,
+    6,
+    4,
+    16,
+    44,
+    194,
+    4,
+    3,
+    5,
+    3,
+    4,
+    3,
+    265,
+    6,
+    24,
+    3,
+    337,
+    300,
+    145,
+    3,
+    7,
+    6,
+    3,
+    255,
+    5,
+    3,
+    20,
+    4,
+    17,
+    4,
+    61,
+    229,
+    2,
+    102,
+    1889,
+    3,
+    448,
+    37,
+    12,
+    19,
+    21,
+    5,
+    10,
+    7,
+    253,
+    2,
+    14,
+    2,
+    8,
+    2,
+    144,
+    3,
+    5,
+    16,
+    61,
+    262,
+    2,
+    5,
+    5,
+    4,
+    4,
+    241,
+    1,
+    38,
+    2,
+    267,
+    168,
+    4,
+    67,
+    3,
+    2,
+    9,
+    4,
+    248,
+    7,
+    3,
+    25,
+    2,
+    15,
+    3,
+    135,
+    222,
+    87,
+    1907,
+    395,
+    8,
+    32,
+    16,
+    10,
+    14,
+    9,
+    4,
+    9,
+    290,
+    6,
+    4,
+    9,
+    4,
+    4,
+    2,
+    1,
+    101,
+    1,
+    2,
+    8,
+    7,
+    217,
+    1,
+    4,
+    3,
+    4,
+    3,
+    172,
+    5,
+    27,
+    4,
+    316,
+    246,
+    6,
+    124,
+    3,
+    3,
+    313,
+    11,
+    1,
+    184,
+    2,
+    2,
+    20,
+    9,
+    3,
+    90,
+    185,
+    1,
+    69,
+    1800,
+    514,
+    14,
+    39,
+    16,
+    3,
+    13,
+    9,
+    4,
+    5,
+    4,
+    215,
+    6,
+    4,
+    1,
+    3,
+    3,
+    6,
+    113,
+    1,
+    2,
+    11,
+    50,
+    214,
+    2,
+    3,
+    5,
+    1,
+    215,
+    4,
+    1,
+    19,
+    3,
+    220,
+    206,
+    2,
+    92,
+    3,
+    7,
+    4,
+    195,
+    1,
+    1,
+    19,
+    5,
+    7,
+    3,
+    126,
+    167,
+    1,
+    95,
+    1862,
+    303,
+    4,
+    39,
+    4,
+    8,
+    2,
+    8,
+    2,
+    5,
+    3,
+    224,
+    1,
+    8,
+    6,
+    2,
+    1,
+    3,
+    97,
+    3,
+    2,
+    24,
+    2,
+    194,
+    2,
+    1,
+    1,
+    2,
+    160,
+    1,
+    4,
+    16,
+    3,
+    292,
+    234,
+    2,
+    109,
+    2,
+    3,
+    10,
+    1,
+    167,
+    3,
+    27,
+    1,
+    9,
+    1,
+    122,
+    175,
+    64,
+    1621,
+    8,
+    365,
+    9,
+    21,
+    4,
+    2,
+    2,
+    13,
+    4,
+    194,
+    3,
+    1,
+    1,
+    2,
+    1,
+    113,
+    3,
+    5,
+    42,
+    140,
+    2,
+    1,
+    3,
+    1,
+    4,
+    161,
+    4,
+    2,
+    20,
+    1,
+    442,
+    164,
+    1,
+    75,
+    1,
+    7,
+    1,
+    182,
+    1,
+    1,
+    9,
+    1,
+    8,
+    1,
+    60,
+    139,
+    70,
+    1413,
+    5,
+    292,
+    3,
+    6,
+    22,
+    1,
+    3,
+    1,
+    5,
+    2,
+    2,
+    1,
+    2,
+    221,
+    3,
+    1,
+    2,
+    2,
+    4,
+    62,
+    15,
+    181,
+    1,
+    1,
+    1,
+    3,
+    135,
+    8,
+    2,
+    4,
+    8,
+    4,
+    272,
+    118,
+    2,
+    88,
+    2,
+    164,
+    1,
+    15,
+    6,
+    1,
+    75,
+    112,
+    3,
+    54,
+    1338,
+    5,
+    345,
+    3,
+    4,
+    18,
+    2,
+    2,
+    3,
+    8,
+    1,
+    1,
+    4,
+    3,
+    3,
+    184,
+    2,
+    1,
+    2,
+    1,
+    1,
+    2,
+    3,
+    60,
+    2,
+    5,
+    30,
+    141,
+    2,
+    1,
+    1,
+    2,
+    2,
+    1,
+    161,
+    4,
+    1,
+    10,
+    2,
+    210,
+    102,
+    3,
+    107,
+    3,
+    1,
+    3,
+    1,
+    107,
+    1,
+    1,
+    12,
+    1,
+    1,
+    62,
+    133,
+    1,
+    67,
+    1614,
+    4,
+    3,
+    277,
+    2,
+    4,
+    19,
+    3,
+    2,
+    4,
+    1,
+    2,
+    1,
+    1,
+    160,
+    1,
+    5,
+    1,
+    1,
+    60,
+    1,
+    1,
+    5,
+    1,
+    123,
+    5,
+    2,
+    1,
+    1,
+    1,
+    2,
+    137,
+    160,
+    1,
+    1,
+    10,
+    157,
+    102,
+    1,
+    64,
+    1,
+    2,
+    5,
+    1,
+    105,
+    1,
+    1,
+    10,
+    3,
+    60,
+    72,
+    67,
+    1176,
+    7,
+    4,
+    264,
+    1,
+    3,
+    11,
+    5,
+    1,
+    1,
+    4,
+    3,
+    3,
+    1,
+    1,
+    1,
+    2,
+    157,
+    1,
+    4,
+    2,
+    1,
+    7,
+    1,
+    3,
+    72,
+    1,
+    11,
+    1,
+    113,
+    3,
+    2,
+    1,
+    156,
+    7,
+    4,
+    13,
+    1,
+    165,
+    70,
+    2,
+    58,
+    2,
+    1,
+    1,
+    118,
+    1,
+    9,
+    4,
+    76,
+    83,
+    56,
+    1134,
+    3,
+    2,
+    9,
+    223,
+    3,
+    1,
+    20,
+    2,
+    1,
+    1,
+    1,
+    4,
+    3,
+    2,
+    1,
+    1,
+    145,
+    1,
+    1,
+    1,
+    1,
+    1,
+    2,
+    1,
+    1,
+    33,
+    1,
+    1,
+    6,
+    109,
+    4,
+    3,
+    126,
+    2,
+    2,
+    10,
+    161,
+    65,
+    1,
+    85,
+    3,
+    4,
+    90,
+    1,
+    3,
+    2,
+    2,
+    44,
+    67,
+    1,
+    49,
+    961,
+    2,
+    3,
+    5,
+    186,
+    4,
+    2,
+    3,
+    7,
+    1,
+    1,
+    1,
+    5,
+    2,
+    113,
+    2,
+    1,
+    1,
+    1,
+    2,
+    53,
+    1,
+    1,
+    8,
+    104,
+    7,
+    1,
+    87,
+    1,
+    1,
+    1,
+    2,
+    138,
+    3,
+    1,
+    3,
+    1,
+    143,
+    69,
+    1,
+    37,
+    2,
+    1,
+    1,
+    89,
+    5,
+    5,
+    51,
+    85,
+    63,
+    1028,
+    6,
+    3,
+    1,
+    5,
+    180,
+    1,
+    3,
+    5,
+    2,
+    3,
+    1,
+    2,
+    1,
+    7,
+    1,
+    1,
+    1,
+    88,
+    1,
+    1,
+    3,
+    1,
+    2,
+    32,
+    1,
+    1,
+    1,
+    8,
+    86,
+    1,
+    1,
+    1,
+    113,
+    1,
+    1,
+    8,
+    5,
+    112,
+    68,
+    43,
+    1,
+    2,
+    105,
+    9,
+    2,
+    41,
+    67,
+    49,
+    955,
+    4,
+    1,
+    5,
+    149,
+    1,
+    2,
+    10,
+    4,
+    2,
+    1,
+    6,
+    2,
+    108,
+    2,
+    2,
+    1,
+    1,
+    28,
+    2,
+    8,
+    80,
+    3,
+    1,
+    1,
+    1,
+    77,
+    2,
+    3,
+    11,
+    2,
+    183,
+    51,
+    52,
+    1,
+    9,
+    82,
+    1,
+    3,
+    5,
+    1,
+    49,
+    61,
+    34,
+    859,
+    1,
+    2,
+    2,
+    4,
+    2,
+    152,
+    1,
+    1,
+    12,
+    1,
+    1,
+    4,
+    1,
+    103,
+    4,
+    32,
+    147,
+    1,
+    1,
+    9,
+    48,
+    2,
+    1,
+    1,
+    1,
+    81,
+    4,
+    1,
+    6,
+    1,
+    130,
+    33,
+    1,
+    51,
+    2,
+    63,
+    3,
+    1,
+    50,
+    43,
+    1,
+    50,
+    811,
+    1,
+    2,
+    2,
+    1,
+    1,
+    1,
+    124,
+    2,
+    11,
+    1,
+    1,
+    1,
+    1,
+    70,
+    1,
+    3,
+    25,
+    2,
+    11,
+    75,
+    4,
+    1,
+    2,
+    1,
+    99,
+    1,
+    7,
+    78,
+    35,
+    64,
+    2,
+    2,
+    58,
+    6,
+    1,
+    1,
+    40,
+    82,
+    1,
+    27,
+    785,
+    1,
+    1,
+    1,
+    3,
+    5,
+    1,
+    1,
+    123,
+    2,
+    9,
+    1,
+    4,
+    1,
+    1,
+    58,
+    1,
+    5,
+    1,
+    1,
+    31,
+    6,
+    16,
+    40,
+    3,
+    1,
+    81,
+    3,
+    5,
+    1,
+    85,
+    60,
+    42,
+    1,
+    2,
+    3,
+    28,
+    1,
+    3,
+    1,
+    33,
+    29,
+    1,
+    757,
+    1,
+    3,
+    1,
+    1,
+    2,
+    120,
+    1,
+    1,
+    8,
+    3,
+    87,
+    5,
+    31,
+    1,
+    1,
+    8,
+    23,
+    5,
+    70,
+    2,
+    4,
+    1,
+    93,
+    31,
+    1,
+    37,
+    1,
+    1,
+    113,
+    6,
+    3,
+    1,
+    26,
+    22,
+    29,
+    783,
+    1,
+    2,
+    3,
+    1,
+    3,
+    1,
+    3,
+    92,
+    1,
+    3,
+    7,
+    2,
+    1,
+    55,
+    3,
+    1,
+    23,
+    1,
+    10,
+    64,
+    12,
+    1,
+    63,
+    3,
+    54,
+    23,
+    52,
+    1,
+    49,
+    1,
+    2,
+    2,
+    19,
+    23,
+    28,
+    652,
+    2,
+    1,
+    1,
+    77,
+    4,
+    1,
+    1,
+    1,
+    11,
+    1,
+    1,
+    4,
+    47,
+    1,
+    23,
+    14,
+    2,
+    2,
+    11,
+    31,
+    6,
+    1,
+    1,
+    50,
+    4,
+    3,
+    65,
+    21,
+    32,
+    1,
+    1,
+    21,
+    1,
+    3,
+    1,
+    29,
+    34,
+    1,
+    13,
+    641,
+    2,
+    3,
+    1,
+    1,
+    1,
+    1,
+    1,
+    97,
+    2,
+    1,
+    1,
+    2,
+    4,
+    2,
+    1,
+    41,
+    1,
+    4,
+    1,
+    21,
+    1,
+    1,
+    12,
+    20,
+    13,
+    1,
+    1,
+    43,
+    1,
+    8,
+    1,
+    71,
+    21,
+    28,
+    19,
+    1,
+    1,
+    32,
+    18,
+    1,
+    601,
+    1,
+    1,
+    2,
+    82,
+    1,
+    1,
+    3,
+    1,
+    1,
+    42,
+    1,
+    1,
+    6,
+    19,
+    1,
+    2,
+    3,
+    17,
+    12,
+    1,
+    53,
+    2,
+    4,
+    55,
+    13,
+    1,
+    30,
+    1,
+    22,
+    3,
+    1,
+    30,
+    15,
+    554,
+    1,
+    3,
+    1,
+    53,
+    1,
+    1,
+    1,
+    32,
+    13,
+    13,
+    8,
+    23,
+    18,
+    1,
+    36,
+    1,
+    1,
+    2,
+    44,
+    13,
+    27,
+    17,
+    1,
+    2,
+    2,
+    2,
+    18,
+    20,
+    1178,
+    14,
+    1,
+    2,
+    61,
+    1,
+    3,
+    1,
+    2,
+    1,
+    1,
+    25,
+    1,
+    15,
+    13,
+    1,
+    1,
+    1,
+    9,
+    10,
+    11,
+    1,
+    2,
+    21,
+    2,
+    68,
+    14,
+    32,
+    18,
+    2,
+    25,
+    15,
+    391,
+    1,
+    1,
+    1,
+    2,
+    1,
+    1,
+    1,
+    7,
+    32,
+    5,
+    1,
+    1,
+    1,
+    18,
+    31,
+    1,
+    18,
+    1,
+    1,
+    13,
+    17,
+    32,
+    1,
+    2,
+    25,
+    14,
+    33,
+    19,
+    1,
+    18,
+    12,
+    1,
+    311,
+    2,
+    3,
+    1,
+    1,
+    1,
+    1,
+    44,
+    1,
+    8,
+    1,
+    15,
+    22,
+    1,
+    13,
+    1,
+    2,
+    5,
+    13,
+    15,
+    33,
+    1,
+    1,
+    1,
+    33,
+    5,
+    1,
+    22,
+    9,
+    1,
+    4,
+    14,
+    11,
+    249,
+    1,
+    1,
+    46,
+    1,
+    6,
+    1,
+    1,
+    21,
+    32,
+    1,
+    9,
+    1,
+    5,
+    7,
+    28,
+    1,
+    23,
+    1,
+    4,
+    34,
+    8,
+    19,
+    2,
+    8,
+    1,
+    3,
+    4,
+    20,
+    9,
+    210,
+    9,
+    1,
+    5,
+    1,
+    1,
+    8,
+    32,
+    2,
+    3,
+    2,
+    1,
+    15,
+    306,
+    13,
+    3,
+    7,
+    42,
+    15,
+    32,
+    7,
+    22,
+    2,
+    11,
+    7,
+    1,
+    16,
+    8,
+    179,
+    10,
+    1,
+    1,
+    1,
+    483,
+    1,
+    1,
+    1,
+    1,
+    11,
+    185,
+    7,
+    1,
+    2,
+    11,
+    2,
+    15,
+    1,
+    24,
+    9,
+    20,
+    5,
+    2,
+    1,
+    1,
+    236,
+    23,
+    5,
+    939,
+    7,
+    2,
+    1,
+    1,
+    184,
+    3,
+    14,
+    1,
+    7,
+    1,
+    3,
+    6,
+    1,
+    16,
+    1,
+    1,
+    30,
+    9,
+    1,
+    25,
+    9,
+    35,
+    3,
+    41,
+    20,
+    6,
+    111,
+    1,
+    8,
+    1,
+    1,
+    2,
+    1,
+    1,
+    1,
+    25,
+    3,
+    1,
+    15,
+    7,
+    2,
+    4,
+    6,
+    16,
+    2,
+    23,
+    7,
+    25,
+    2,
+    1,
+    13,
+    1,
+    2,
+    2,
+    13,
+    6,
+    57,
+    1,
+    9,
+    1,
+    1,
+    13,
+    5,
+    2,
+    2,
+    4,
+    12,
+    10,
+    3,
+    21,
+    12,
+    1,
+    1,
+    9,
+    6,
+    32,
+    1,
+    2,
+    1,
+    1,
+    1,
+    18,
+    1,
+    2,
+    9,
+    5,
+    4,
+    6,
+    9,
+    1,
+    1,
+    17,
+    1,
+    16,
+    2,
+    13,
+    3,
+    158,
+    1,
+    2,
+    18,
+    2,
+    2,
+    1,
+    11,
+    1,
+    7,
+    4,
+    6,
+    9,
+    1,
+    6,
+    12,
+    2,
+    17,
+    1,
+    1,
+    5,
+    2,
+    12,
+    3,
+    6,
+    50,
+    1,
+    7,
+    4,
+    1,
+    7,
+    1,
+    6,
+    1,
+    230,
+    3,
+    12,
+    1,
+    1,
+    5,
+    1,
+    9,
+    7,
+    2,
+    6,
+    2,
+    29,
+    3,
+    3,
+    4,
+    2,
+    5,
+    6,
+    3,
+    12,
+    1,
+    16,
+    10,
+    1,
+    6,
+    5,
+    3,
+    5,
+    6,
+    2,
+    4,
+    5,
+    5,
+    1,
+    2,
+    1,
+    18,
+    4,
+    3,
+    14,
+    2,
+    4,
+    1,
+    1,
+    3,
+    4,
+    6,
+    3,
+    1,
+    2,
+    4,
+    30,
+    2,
+    9,
+    2,
+    2,
+    6,
+    12,
+    4,
+    2,
+    2,
+    4,
+    3,
+    2,
+    10,
+    12,
+    3,
+    1,
+    1,
+    4,
+    2,
+    1,
+    5,
+    3,
+    12,
+    7,
+    8,
+    3,
+    1,
+    5,
+    1,
+    2,
+    1,
+    1,
+    6,
+    1,
+    10,
+    2,
+    2,
+    6,
+    2,
+    2,
+    3,
+    10,
+    9,
+    5,
+    1,
+    6,
+    1,
+    1,
+    1,
+    4,
+    2,
+    2,
+    9,
+    2,
+    2,
+    8,
+    1,
+    1,
+    10,
+    17,
+    5,
+    1,
+    2,
+    1,
+    2,
+    14,
+    4,
+    3,
+    1,
+    3,
+    1,
+    13,
+    5,
+    4,
+    1,
+    12,
+    1,
+    1,
+    8,
+    7,
+    10,
+    101,
+    2,
+    1
+  ],
+  "valSizeRange": [
+    31,
+    63,
+    95,
+    127,
+    159,
+    191,
+    223,
+    255,
+    287,
+    319,
+    351,
+    383,
+    415,
+    447,
+    479,
+    511,
+    543,
+    575,
+    607,
+    639,
+    671,
+    703,
+    735,
+    767,
+    799,
+    831,
+    863,
+    895,
+    927,
+    959,
+    991,
+    1023,
+    1055,
+    1087,
+    1119,
+    1151,
+    1183,
+    1215,
+    1247,
+    1279,
+    1311,
+    1343,
+    1375,
+    1407,
+    1439,
+    1471,
+    1503,
+    1535,
+    1567,
+    1599,
+    1631,
+    1663,
+    1695,
+    1727,
+    1759,
+    1791,
+    1823,
+    1855,
+    1887,
+    1919,
+    1951,
+    1983,
+    2015,
+    2047,
+    2079,
+    2111,
+    2143,
+    2175,
+    2207,
+    2239,
+    2271,
+    2303,
+    2335,
+    2367,
+    2399,
+    2431,
+    2463,
+    2495,
+    2527,
+    2559,
+    2591,
+    2623,
+    2655,
+    2687,
+    2719,
+    2751,
+    2783,
+    2815,
+    2847,
+    2879,
+    2911,
+    2943,
+    2975,
+    3007,
+    3039,
+    3071,
+    3103,
+    3135,
+    3167,
+    3199,
+    3231,
+    3263,
+    3295,
+    3327,
+    3359,
+    3391,
+    3423,
+    3455,
+    3487,
+    3519,
+    3551,
+    3583,
+    3615,
+    3647,
+    3679,
+    3711,
+    3743,
+    3775,
+    3807,
+    3839,
+    3871,
+    3903,
+    3935,
+    3967,
+    3999,
+    4031,
+    4063,
+    4095,
+    4127,
+    4159,
+    4191,
+    4223,
+    4255,
+    4287,
+    4319,
+    4351,
+    4383,
+    4415,
+    4447,
+    4479,
+    4511,
+    4543,
+    4575,
+    4607,
+    4639,
+    4671,
+    4703,
+    4735,
+    4767,
+    4799,
+    4831,
+    4863,
+    4895,
+    4927,
+    4959,
+    4991,
+    5023,
+    5055,
+    5087,
+    5119,
+    5151,
+    5183,
+    5215,
+    5247,
+    5279,
+    5311,
+    5343,
+    5375,
+    5407,
+    5439,
+    5471,
+    5503,
+    5535,
+    5567,
+    5599,
+    5631,
+    5663,
+    5695,
+    5727,
+    5759,
+    5791,
+    5823,
+    5855,
+    5887,
+    5919,
+    5951,
+    5983,
+    6015,
+    6047,
+    6079,
+    6111,
+    6143,
+    6175,
+    6207,
+    6239,
+    6271,
+    6303,
+    6335,
+    6367,
+    6399,
+    6431,
+    6463,
+    6495,
+    6527,
+    6559,
+    6591,
+    6623,
+    6655,
+    6687,
+    6719,
+    6751,
+    6783,
+    6815,
+    6847,
+    6879,
+    6911,
+    6943,
+    6975,
+    7007,
+    7039,
+    7071,
+    7103,
+    7135,
+    7167,
+    7199,
+    7231,
+    7263,
+    7295,
+    7327,
+    7359,
+    7391,
+    7423,
+    7455,
+    7487,
+    7519,
+    7551,
+    7583,
+    7615,
+    7647,
+    7679,
+    7711,
+    7743,
+    7775,
+    7807,
+    7839,
+    7871,
+    7903,
+    7935,
+    7967,
+    7999,
+    8031,
+    8063,
+    8095,
+    8127,
+    8159,
+    8191,
+    8223,
+    8255,
+    8287,
+    8319,
+    8351,
+    8383,
+    8415,
+    8447,
+    8479,
+    8511,
+    8543,
+    8575,
+    8607,
+    8639,
+    8671,
+    8703,
+    8735,
+    8767,
+    8799,
+    8831,
+    8863,
+    8895,
+    8927,
+    8959,
+    8991,
+    9023,
+    9055,
+    9087,
+    9119,
+    9151,
+    9183,
+    9215,
+    9247,
+    9279,
+    9311,
+    9343,
+    9375,
+    9407,
+    9439,
+    9471,
+    9503,
+    9535,
+    9567,
+    9599,
+    9631,
+    9663,
+    9695,
+    9727,
+    9759,
+    9791,
+    9823,
+    9855,
+    9887,
+    9919,
+    9951,
+    9983,
+    10015,
+    10047,
+    10079,
+    10111,
+    10143,
+    10175,
+    10207,
+    10239,
+    10271,
+    10303,
+    10335,
+    10367,
+    10399,
+    10431,
+    10463,
+    10495,
+    10527,
+    10559,
+    10591,
+    10623,
+    10655,
+    10687,
+    10719,
+    10783,
+    10815,
+    10847,
+    10879,
+    10911,
+    10943,
+    10975,
+    11007,
+    11039,
+    11071,
+    11103,
+    11135,
+    11167,
+    11199,
+    11231,
+    11263,
+    11295,
+    11327,
+    11359,
+    11391,
+    11423,
+    11455,
+    11487,
+    11519,
+    11551,
+    11583,
+    11647,
+    11679,
+    11711,
+    11743,
+    11775,
+    11807,
+    11839,
+    11871,
+    11903,
+    11935,
+    11967,
+    11999,
+    12031,
+    12063,
+    12095,
+    12127,
+    12159,
+    12191,
+    12223,
+    12255,
+    12287,
+    12319,
+    12351,
+    12383,
+    12415,
+    12447,
+    12479,
+    12543,
+    12575,
+    12607,
+    12639,
+    12671,
+    12735,
+    12767,
+    12799,
+    12831,
+    12863,
+    12895,
+    12927,
+    12959,
+    12991,
+    13023,
+    13055,
+    13087,
+    13119,
+    13151,
+    13183,
+    13215,
+    13247,
+    13279,
+    13311,
+    13343,
+    13375,
+    13407,
+    13439,
+    13471,
+    13503,
+    13535,
+    13599,
+    13631,
+    13663,
+    13695,
+    13759,
+    13791,
+    13823,
+    13855,
+    13887,
+    13919,
+    13951,
+    13983,
+    14015,
+    14047,
+    14079,
+    14111,
+    14143,
+    14175,
+    14207,
+    14271,
+    14303,
+    14335,
+    14367,
+    14399,
+    14431,
+    14463,
+    14495,
+    14527,
+    14559,
+    14591,
+    14655,
+    14687,
+    14719,
+    14751,
+    14783,
+    14815,
+    14847,
+    14879,
+    14943,
+    14975,
+    15007,
+    15039,
+    15071,
+    15103,
+    15135,
+    15167,
+    15199,
+    15231,
+    15263,
+    15295,
+    15327,
+    15359,
+    15391,
+    15423,
+    15455,
+    15487,
+    15519,
+    15551,
+    15583,
+    15615,
+    15647,
+    15679,
+    15743,
+    15775,
+    15807,
+    15903,
+    15935,
+    15967,
+    15999,
+    16063,
+    16095,
+    16127,
+    16159,
+    16191,
+    16223,
+    16255,
+    16287,
+    16319,
+    16351,
+    16383,
+    16415,
+    16447,
+    16479,
+    16511,
+    16543,
+    16607,
+    16639,
+    16671,
+    16703,
+    16735,
+    16767,
+    16799,
+    16831,
+    16863,
+    16927,
+    16959,
+    16991,
+    17023,
+    17055,
+    17087,
+    17183,
+    17215,
+    17247,
+    17279,
+    17311,
+    17343,
+    17375,
+    17407,
+    17439,
+    17471,
+    17503,
+    17535,
+    17567,
+    17599,
+    17631,
+    17663,
+    17695,
+    17727,
+    17759,
+    17791,
+    17855,
+    17887,
+    17919,
+    17983,
+    18015,
+    18047,
+    18079,
+    18111,
+    18143,
+    18175,
+    18239,
+    18271,
+    18303,
+    18335,
+    18367,
+    18431,
+    18463,
+    18559,
+    18623,
+    18655,
+    18687,
+    18751,
+    18783,
+    18815,
+    18847,
+    18879,
+    18911,
+    18943,
+    18975,
+    19007,
+    19039,
+    19071,
+    19103,
+    19135,
+    19167,
+    19199,
+    19231,
+    19263,
+    19327,
+    19359,
+    19423,
+    19455,
+    19487,
+    19519,
+    19551,
+    19583,
+    19615,
+    19647,
+    19679,
+    19743,
+    19775,
+    19807,
+    19839,
+    19871,
+    19903,
+    19967,
+    20031,
+    20063,
+    20127,
+    20159,
+    20191,
+    20255,
+    20287,
+    20319,
+    20351,
+    20383,
+    20415,
+    20447,
+    20479,
+    20511,
+    20543,
+    20575,
+    20607,
+    20639,
+    20671,
+    20703,
+    20735,
+    20767,
+    20831,
+    20863,
+    20927,
+    20991,
+    21023,
+    21055,
+    21087,
+    21119,
+    21151,
+    21183,
+    21215,
+    21247,
+    21279,
+    21343,
+    21375,
+    21407,
+    21439,
+    21471,
+    21503,
+    21567,
+    21663,
+    21695,
+    21727,
+    21791,
+    21823,
+    21887,
+    21919,
+    21951,
+    21983,
+    22015,
+    22047,
+    22079,
+    22111,
+    22143,
+    22175,
+    22207,
+    22239,
+    22271,
+    22335,
+    22367,
+    22399,
+    22431,
+    22463,
+    22495,
+    22559,
+    22623,
+    22687,
+    22719,
+    22751,
+    22783,
+    22815,
+    22847,
+    22879,
+    22911,
+    22943,
+    22975,
+    23039,
+    23071,
+    23135,
+    23167,
+    23199,
+    23231,
+    23295,
+    23391,
+    23423,
+    23487,
+    23519,
+    23551,
+    23583,
+    23647,
+    23679,
+    23711,
+    23743,
+    23775,
+    23807,
+    23839,
+    23871,
+    23903,
+    23935,
+    23967,
+    23999,
+    24031,
+    24063,
+    24127,
+    24159,
+    24191,
+    24223,
+    24255,
+    24319,
+    24351,
+    24415,
+    24511,
+    24543,
+    24575,
+    24607,
+    24639,
+    24703,
+    24735,
+    24767,
+    24831,
+    24895,
+    24927,
+    24991,
+    25023,
+    25055,
+    25087,
+    25183,
+    25279,
+    25311,
+    25375,
+    25407,
+    25439,
+    25503,
+    25535,
+    25567,
+    25599,
+    25631,
+    25663,
+    25695,
+    25727,
+    25759,
+    25791,
+    25823,
+    25855,
+    25887,
+    25919,
+    25983,
+    26015,
+    26047,
+    26111,
+    26143,
+    26175,
+    26207,
+    26271,
+    26303,
+    26335,
+    26399,
+    26463,
+    26527,
+    26559,
+    26591,
+    26623,
+    26687,
+    26719,
+    26751,
+    26783,
+    26847,
+    26911,
+    26943,
+    26975,
+    27039,
+    27071,
+    27103,
+    27199,
+    27295,
+    27327,
+    27391,
+    27455,
+    27487,
+    27551,
+    27583,
+    27615,
+    27647,
+    27679,
+    27711,
+    27743,
+    27775,
+    27807,
+    27839,
+    27871,
+    27903,
+    27935,
+    27967,
+    27999,
+    28031,
+    28063,
+    28095,
+    28159,
+    28191,
+    28223,
+    28255,
+    28287,
+    28319,
+    28383,
+    28415,
+    28447,
+    28511,
+    28607,
+    28639,
+    28671,
+    28735,
+    28767,
+    28831,
+    28863,
+    28895,
+    28927,
+    28991,
+    29055,
+    29119,
+    29151,
+    29215,
+    29247,
+    29279,
+    29375,
+    29503,
+    29535,
+    29599,
+    29663,
+    29695,
+    29759,
+    29791,
+    29823,
+    29855,
+    29887,
+    29919,
+    29951,
+    29983,
+    30015,
+    30047,
+    30079,
+    30111,
+    30143,
+    30175,
+    30239,
+    30271,
+    30303,
+    30367,
+    30399,
+    30463,
+    30495,
+    30527,
+    30559,
+    30591,
+    30655,
+    30719,
+    30815,
+    30879,
+    30943,
+    30975,
+    31039,
+    31103,
+    31135,
+    31167,
+    31199,
+    31231,
+    31263,
+    31327,
+    31391,
+    31455,
+    31487,
+    31551,
+    31583,
+    31647,
+    31743,
+    31871,
+    31903,
+    31967,
+    32063,
+    32127,
+    32191,
+    32223,
+    32255,
+    32287,
+    32319,
+    32351,
+    32383,
+    32415,
+    32447,
+    32479,
+    32511,
+    32543,
+    32575,
+    32607,
+    32639,
+    32671,
+    32703,
+    32735,
+    32799,
+    32831,
+    32895,
+    32927,
+    32959,
+    32991,
+    33023,
+    33119,
+    33183,
+    33279,
+    33375,
+    33439,
+    33471,
+    33535,
+    33599,
+    33631,
+    33663,
+    33695,
+    33727,
+    33759,
+    33823,
+    33887,
+    33919,
+    33983,
+    34015,
+    34079,
+    34111,
+    34175,
+    34431,
+    34463,
+    34559,
+    34623,
+    34655,
+    34719,
+    34751,
+    34815,
+    34847,
+    34879,
+    34911,
+    34943,
+    34975,
+    35007,
+    35039,
+    35071,
+    35103,
+    35135,
+    35199,
+    35231,
+    35263,
+    35295,
+    35327,
+    35359,
+    35423,
+    35487,
+    35551,
+    35583,
+    35615,
+    35647,
+    35679,
+    35775,
+    35839,
+    35935,
+    36095,
+    36127,
+    36159,
+    36223,
+    36287,
+    36319,
+    36383,
+    36415,
+    36447,
+    36543,
+    36639,
+    36703,
+    36767,
+    36799,
+    36863,
+    36927,
+    37023,
+    37183,
+    37215,
+    37311,
+    37407,
+    37439,
+    37503,
+    37535,
+    37599,
+    37631,
+    37663,
+    37695,
+    37727,
+    37759,
+    37791,
+    37823,
+    37855,
+    37919,
+    37951,
+    37983,
+    38015,
+    38047,
+    38111,
+    38143,
+    38207,
+    38271,
+    38335,
+    38399,
+    38431,
+    38463,
+    38495,
+    38559,
+    38623,
+    38719,
+    38943,
+    39007,
+    39039,
+    39071,
+    39135,
+    39199,
+    39231,
+    39295,
+    39327,
+    39359,
+    39391,
+    39487,
+    39583,
+    39647,
+    39711,
+    39775,
+    39871,
+    39999,
+    40159,
+    40223,
+    40319,
+    40415,
+    40511,
+    40543,
+    40607,
+    40639,
+    40671,
+    40703,
+    40735,
+    40767,
+    40799,
+    40831,
+    40927,
+    40991,
+    41023,
+    41087,
+    41119,
+    41215,
+    41279,
+    41343,
+    41407,
+    41503,
+    41567,
+    41599,
+    41631,
+    41823,
+    41855,
+    41951,
+    42079,
+    42143,
+    42175,
+    42207,
+    42271,
+    42335,
+    42367,
+    42463,
+    42495,
+    42527,
+    42559,
+    42751,
+    42815,
+    42879,
+    42943,
+    43007,
+    43071,
+    43231,
+    43391,
+    43423,
+    43551,
+    43647,
+    43743,
+    43807,
+    43839,
+    43871,
+    43903,
+    43935,
+    43967,
+    43999,
+    44031,
+    44063,
+    44095,
+    44127,
+    44191,
+    44223,
+    44255,
+    44287,
+    44319,
+    44383,
+    44415,
+    44447,
+    44575,
+    44671,
+    44831,
+    44927,
+    44991,
+    45215,
+    45311,
+    45439,
+    45503,
+    45535,
+    45567,
+    45599,
+    45663,
+    45727,
+    45791,
+    45855,
+    45887,
+    45951,
+    46175,
+    46335,
+    46463,
+    46527,
+    46879,
+    46911,
+    47039,
+    47135,
+    47167,
+    47263,
+    47295,
+    47359,
+    47391,
+    47423,
+    47455,
+    47487,
+    47519,
+    47551,
+    47583,
+    47615,
+    47647,
+    47679,
+    47743,
+    47775,
+    47807,
+    47839,
+    47871,
+    47903,
+    47935,
+    47967,
+    48031,
+    48063,
+    48159,
+    48223,
+    48383,
+    48447,
+    48511,
+    48543,
+    48607,
+    48703,
+    48799,
+    48831,
+    48959,
+    49087,
+    49151,
+    49183,
+    49247,
+    49311,
+    49407,
+    49439,
+    49535,
+    49567,
+    49631,
+    49663,
+    49759,
+    49887,
+    49983,
+    50047,
+    50111,
+    50175,
+    50271,
+    50623,
+    50687,
+    50815,
+    50911,
+    50975,
+    51039,
+    51071,
+    51135,
+    51167,
+    51199,
+    51231,
+    51263,
+    51295,
+    51327,
+    51423,
+    51455,
+    51487,
+    51519,
+    51551,
+    51583,
+    51647,
+    51743,
+    51775,
+    51871,
+    51935,
+    52095,
+    52191,
+    52287,
+    52319,
+    52383,
+    52415,
+    52447,
+    52479,
+    52607,
+    52703,
+    52735,
+    52863,
+    52959,
+    53087,
+    53119,
+    53183,
+    53279,
+    53407,
+    53503,
+    53567,
+    53599,
+    53631,
+    53759,
+    53887,
+    53983,
+    54047,
+    54143,
+    54207,
+    54303,
+    54687,
+    54879,
+    55007,
+    55135,
+    55167,
+    55231,
+    55263,
+    55295,
+    55327,
+    55359,
+    55423,
+    55455,
+    55519,
+    55551,
+    55583,
+    55615,
+    55647,
+    55679,
+    55711,
+    55743,
+    55775,
+    55807,
+    55903,
+    55935,
+    55967,
+    56031,
+    56095,
+    56191,
+    56255,
+    56287,
+    56511,
+    56543,
+    56575,
+    56607,
+    56639,
+    57119,
+    57279,
+    57343,
+    57375,
+    57439,
+    57567,
+    57631,
+    57695,
+    57791,
+    57855,
+    57887,
+    58047,
+    58207,
+    58303,
+    58367,
+    58463,
+    58655,
+    59071,
+    59295,
+    59391,
+    59551,
+    59583,
+    59647,
+    59679,
+    59711,
+    59743,
+    59775,
+    59807,
+    59839,
+    59871,
+    59903,
+    59935,
+    59967,
+    60031,
+    60063,
+    60095,
+    60159,
+    60191,
+    60255,
+    60319,
+    60383,
+    60415,
+    60447,
+    60511,
+    60575,
+    60671,
+    60767,
+    60799,
+    60895,
+    61023,
+    61055,
+    61151,
+    61183,
+    61855,
+    61951,
+    61983,
+    62015,
+    62175,
+    62303,
+    62399,
+    62463,
+    62527,
+    62719,
+    62879,
+    63039,
+    63263,
+    63359,
+    63807,
+    63871,
+    64031,
+    64159,
+    64223,
+    64319,
+    64351,
+    64415,
+    64447,
+    64479,
+    64511,
+    64543,
+    64575,
+    64607,
+    64639,
+    64671,
+    64735,
+    64799,
+    64831,
+    64927,
+    65087,
+    65247,
+    65279,
+    65375,
+    65439,
+    65535,
+    65663,
+    65887,
+    65919,
+    65951,
+    66047,
+    66079,
+    66143,
+    66207,
+    66431,
+    66463,
+    66623,
+    66815,
+    66911,
+    66943,
+    66975,
+    67135,
+    67231,
+    67295,
+    67423,
+    67487,
+    67551,
+    67743,
+    67903,
+    68031,
+    68095,
+    68415,
+    68927,
+    69151,
+    69311,
+    69471,
+    69503,
+    69567,
+    69599,
+    69631,
+    69663,
+    69695,
+    69727,
+    69791,
+    69823,
+    69855,
+    69887,
+    69951,
+    69983,
+    70015,
+    70047,
+    70079,
+    70111,
+    70207,
+    70303,
+    70367,
+    70431,
+    70495,
+    70591,
+    70687,
+    70911,
+    71167,
+    71199,
+    71231,
+    71327,
+    71359,
+    71615,
+    71743,
+    71775,
+    72255,
+    72319,
+    72383,
+    72511,
+    72639,
+    72703,
+    72799,
+    72959,
+    73183,
+    73343,
+    73567,
+    73919,
+    74463,
+    74687,
+    74847,
+    75039,
+    75071,
+    75135,
+    75167,
+    75199,
+    75263,
+    75295,
+    75359,
+    75423,
+    75455,
+    75551,
+    75583,
+    75647,
+    75679,
+    75935,
+    75999,
+    76127,
+    76159,
+    76351,
+    76607,
+    76927,
+    77023,
+    77055,
+    77087,
+    77343,
+    77503,
+    77759,
+    78047,
+    78111,
+    78143,
+    78335,
+    78463,
+    78527,
+    78655,
+    78783,
+    79039,
+    79231,
+    79455,
+    79711,
+    79839,
+    80415,
+    80479,
+    80671,
+    80863,
+    81055,
+    81087,
+    81119,
+    81151,
+    81183,
+    81247,
+    81279,
+    81311,
+    81343,
+    81407,
+    81439,
+    81471,
+    81567,
+    81695,
+    81759,
+    82015,
+    82239,
+    82719,
+    82783,
+    82879,
+    83039,
+    83199,
+    83231,
+    83263,
+    83359,
+    83711,
+    84223,
+    84319,
+    84383,
+    84479,
+    84607,
+    84735,
+    84799,
+    84927,
+    85055,
+    85087,
+    85151,
+    85823,
+    86207,
+    86847,
+    87135,
+    87327,
+    87423,
+    87551,
+    87583,
+    87615,
+    87647,
+    87679,
+    87711,
+    87743,
+    87775,
+    87807,
+    87935,
+    87967,
+    88095,
+    88223,
+    88255,
+    88511,
+    88607,
+    88671,
+    88831,
+    89343,
+    89759,
+    89855,
+    89887,
+    89951,
+    90047,
+    90431,
+    90943,
+    91071,
+    91135,
+    91359,
+    91615,
+    91743,
+    91903,
+    91999,
+    92575,
+    92671,
+    93119,
+    93439,
+    93823,
+    94111,
+    94303,
+    94399,
+    94559,
+    94591,
+    94623,
+    94655,
+    94687,
+    94719,
+    94751,
+    94783,
+    94815,
+    94847,
+    94975,
+    95007,
+    95135,
+    95199,
+    95295,
+    95359,
+    95679,
+    95775,
+    95935,
+    95999,
+    96095,
+    96511,
+    96927,
+    97055,
+    97087,
+    97151,
+    98239,
+    98367,
+    98431,
+    98687,
+    98847,
+    98943,
+    99071,
+    99263,
+    99359,
+    99583,
+    99807,
+    100095,
+    100447,
+    100575,
+    101311,
+    101663,
+    101855,
+    101983,
+    102143,
+    102207,
+    102271,
+    102303,
+    102335,
+    102367,
+    102431,
+    102495,
+    102527,
+    102623,
+    102943,
+    103359,
+    103615,
+    104223,
+    104671,
+    104703,
+    104831,
+    104863,
+    104927,
+    106239,
+    106303,
+    106591,
+    106783,
+    106847,
+    107007,
+    107167,
+    107231,
+    107551,
+    107807,
+    108127,
+    108639,
+    109439,
+    109535,
+    109791,
+    110015,
+    110303,
+    110335,
+    110367,
+    110399,
+    110431,
+    110495,
+    110527,
+    110591,
+    110623,
+    110655,
+    110687,
+    110719,
+    110847,
+    110943,
+    111519,
+    111615,
+    111935,
+    112255,
+    112575,
+    113087,
+    113247,
+    113279,
+    113311,
+    113695,
+    114719,
+    115135,
+    115423,
+    115583,
+    115807,
+    116447,
+    116767,
+    117151,
+    117343,
+    118207,
+    118591,
+    118847,
+    119135,
+    119167,
+    119199,
+    119231,
+    119455,
+    119487,
+    119551,
+    119583,
+    119615,
+    119679,
+    119711,
+    119871,
+    119999,
+    120095,
+    120575,
+    120671,
+    120895,
+    121599,
+    122047,
+    122143,
+    122303,
+    122335,
+    122399,
+    122527,
+    123775,
+    123903,
+    123999,
+    124351,
+    124639,
+    124863,
+    125087,
+    125471,
+    125983,
+    126143,
+    126367,
+    126719,
+    127647,
+    128095,
+    128351,
+    128479,
+    128671,
+    128703,
+    128735,
+    128767,
+    128831,
+    128863,
+    128895,
+    128927,
+    128991,
+    129055,
+    129087,
+    129119,
+    129151,
+    129247,
+    129311,
+    129695,
+    130047,
+    130207,
+    130495,
+    130559,
+    131167,
+    131327,
+    131807,
+    131903,
+    132095,
+    132127,
+    132191,
+    132639,
+    133695,
+    133823,
+    133951,
+    134303,
+    134559,
+    134623,
+    134847,
+    135103,
+    136223,
+    136863,
+    137887,
+    138335,
+    138623,
+    138783,
+    138975,
+    139039,
+    139103,
+    139167,
+    139391,
+    139423,
+    139583,
+    139647,
+    140191,
+    140479,
+    140639,
+    140767,
+    140927,
+    141023,
+    141855,
+    142111,
+    142463,
+    142655,
+    142719,
+    142783,
+    143551,
+    144543,
+    144671,
+    145055,
+    145407,
+    145631,
+    145823,
+    145919,
+    146719,
+    147135,
+    147839,
+    148927,
+    149407,
+    149727,
+    150111,
+    150175,
+    150207,
+    150463,
+    150559,
+    150719,
+    150847,
+    151295,
+    151903,
+    152319,
+    153215,
+    154079,
+    154143,
+    154207,
+    155039,
+    156127,
+    156255,
+    156447,
+    156671,
+    157055,
+    157279,
+    157599,
+    158911,
+    159455,
+    159679,
+    160831,
+    160863,
+    161375,
+    161695,
+    162111,
+    162143,
+    162239,
+    162591,
+    162623,
+    162687,
+    162911,
+    163231,
+    163423,
+    163647,
+    163903,
+    164063,
+    164223,
+    164511,
+    165471,
+    166079,
+    166175,
+    166207,
+    166431,
+    166463,
+    166559,
+    167551,
+    167967,
+    168639,
+    169215,
+    169631,
+    169887,
+    170207,
+    171615,
+    172447,
+    174303,
+    174655,
+    175103,
+    175135,
+    175167,
+    175199,
+    175263,
+    175423,
+    175487,
+    175583,
+    175615,
+    175647,
+    175967,
+    176031,
+    176511,
+    176767,
+    177183,
+    177663,
+    177983,
+    178719,
+    179519,
+    179743,
+    179807,
+    179871,
+    182111,
+    182271,
+    182751,
+    183199,
+    183487,
+    183839,
+    185375,
+    187615,
+    188255,
+    188639,
+    188863,
+    189119,
+    189183,
+    189215,
+    189311,
+    189471,
+    189535,
+    189631,
+    189695,
+    189759,
+    190047,
+    190111,
+    191391,
+    191903,
+    192767,
+    193023,
+    193375,
+    193887,
+    194143,
+    194207,
+    194271,
+    196703,
+    196863,
+    197375,
+    197759,
+    197855,
+    198175,
+    198431,
+    198559,
+    200223,
+    201151,
+    202655,
+    203327,
+    203743,
+    204255,
+    204383,
+    204415,
+    204863,
+    205023,
+    205247,
+    205439,
+    206047,
+    206687,
+    207263,
+    207615,
+    208479,
+    209343,
+    209663,
+    209759,
+    209823,
+    210975,
+    212447,
+    212607,
+    213183,
+    213695,
+    214047,
+    214431,
+    214623,
+    216223,
+    216991,
+    217279,
+    218879,
+    219583,
+    220031,
+    220575,
+    220607,
+    220639,
+    220703,
+    220799,
+    221151,
+    221247,
+    221279,
+    221599,
+    221695,
+    222367,
+    222687,
+    223231,
+    223839,
+    225151,
+    226463,
+    226527,
+    226623,
+    229439,
+    230815,
+    231167,
+    231615,
+    232863,
+    233535,
+    236383,
+    236639,
+    237183,
+    237663,
+    238239,
+    238271,
+    238303,
+    238335,
+    238559,
+    238975,
+    239327,
+    239423,
+    239903,
+    240703,
+    241119,
+    241759,
+    243167,
+    244255,
+    244575,
+    244671,
+    244767,
+    247807,
+    248159,
+    249279,
+    249663,
+    250143,
+    252223,
+    252735,
+    253439,
+    255295,
+    255327,
+    256159,
+    256671,
+    257311,
+    257343,
+    257407,
+    257471,
+    257663,
+    258111,
+    258591,
+    260415,
+    261119,
+    262655,
+    263807,
+    264159,
+    264255,
+    264351,
+    267647,
+    267871,
+    268575,
+    269247,
+    269663,
+    270047,
+    270175,
+    272415,
+    272959,
+    273727,
+    275743,
+    276639,
+    277215,
+    277887,
+    277919,
+    277951,
+    277983,
+    278015,
+    278047,
+    278111,
+    278271,
+    278751,
+    278783,
+    279295,
+    280159,
+    281247,
+    283647,
+    284927,
+    285311,
+    285407,
+    289055,
+    289279,
+    290783,
+    291231,
+    291775,
+    292031,
+    293375,
+    294239,
+    294815,
+    295647,
+    297791,
+    298783,
+    299391,
+    300127,
+    300159,
+    300191,
+    300223,
+    300575,
+    301087,
+    303775,
+    306367,
+    308127,
+    308223,
+    312191,
+    314047,
+    314527,
+    315135,
+    317759,
+    319295,
+    321631,
+    322719,
+    323359,
+    324127,
+    324223,
+    324319,
+    324511,
+    324927,
+    325151,
+    325183,
+    325311,
+    325791,
+    328063,
+    330879,
+    332799,
+    332895,
+    337183,
+    337439,
+    338367,
+    339199,
+    339711,
+    340351,
+    343199,
+    348543,
+    349247,
+    350079,
+    350111,
+    350143,
+    350175,
+    350207,
+    350239,
+    350527,
+    351199,
+    351839,
+    354335,
+    357375,
+    359423,
+    359551,
+    361855,
+    364191,
+    366335,
+    366911,
+    367583,
+    368799,
+    369599,
+    370655,
+    372447,
+    376415,
+    377183,
+    378175,
+    379327,
+    379839,
+    382687,
+    385951,
+    387647,
+    388319,
+    390591,
+    393311,
+    394655,
+    395647,
+    396255,
+    397023,
+    397343,
+    399167,
+    400319,
+    402239,
+    406559,
+    407391,
+    408447,
+    409663,
+    410399,
+    413311,
+    416831,
+    419231,
+    419391,
+    419551,
+    424799,
+    427327,
+    427967,
+    428767,
+    431135,
+    432383,
+    439071,
+    439967,
+    441151,
+    442463,
+    443263,
+    446367,
+    450207,
+    452191,
+    452799,
+    452959,
+    458783,
+    460351,
+    461503,
+    462207,
+    463103,
+    466975,
+    469183,
+    474207,
+    475199,
+    476447,
+    477855,
+    478719,
+    482111,
+    486239,
+    489023,
+    495519,
+    497215,
+    498431,
+    499199,
+    500159,
+    504319,
+    512159,
+    513215,
+    514559,
+    516095,
+    525151,
+    527455,
+    528159,
+    528351,
+    535167,
+    538335,
+    539135,
+    540159,
+    553151,
+    554271,
+    555743,
+    557375,
+    562335,
+    567167,
+    570623,
+    577983,
+    582303,
+    583391,
+    588287,
+    597407,
+    598623,
+    600191,
+    601983,
+    607327,
+    612543,
+    616287,
+    624223,
+    627935,
+    628895,
+    630079,
+    645215,
+    646527,
+    650143,
+    655935,
+    661567,
+    665599,
+    674175,
+    680479,
+    696831,
+    698271,
+    700095,
+    702175,
+    714495,
+    718591,
+    728127,
+    732415,
+    733535,
+    734943,
+    741087,
+    752575,
+    758367,
+    765087,
+    771647,
+    791039,
+    793727,
+    812799,
+    814463,
+    826303,
+    833375,
+    855615,
+    857247,
+    864415,
+    877823,
+    905247,
+    905567,
+    924095,
+    925823,
+    948063,
+    950015,
+    972095,
+    998015,
+    999903,
+    923935,
+    949855,
+    979903,
+    905855,
+    966335,
+    931615,
+    998687
+  ]
+}
diff --git a/cachelib/cachebench/test_configs/comparisontest/run.py b/cachelib/cachebench/test_configs/comparisontest/run.py
new file mode 100644
index 0000000000..9cf4b4f18b
--- /dev/null
+++ b/cachelib/cachebench/test_configs/comparisontest/run.py
@@ -0,0 +1,137 @@
+#!/usr/bin/python3
+
+import json
+import subprocess
+import re
+import io
+import csv
+import socket
+
+def parseRegex(regex, line):
+    match = re.search(regex, line)
+    if match:
+        return match.group().replace(',', '')
+
+
+config = {
+  "cache_config": {
+    "cacheSizeMB": 8192, 
+    "poolRebalanceIntervalSec": 0
+  }, 
+  "test_config": 
+    {
+      "addChainedRatio": 0.0, 
+      "delRatio": 0.0, 
+      "enableLookaside": True, 
+      "getRatio": 0.9746082749830369, 
+      "keySizeRange": [
+        1, 
+        8, 
+        64
+      ], 
+      "keySizeRangeProbability": [
+        0.3, 
+        0.7
+      ], 
+      "loneGetRatio": 0.025391725016963105, 
+      "numKeys": 23458726,
+        "numOps": 20000,
+      "numThreads": 1,
+      "popDistFile": "assoc_altoona1_follower_pop.json", 
+      "prepopulateCache": True, 
+      "setRatio": 0.0, 
+      "valSizeDistFile": "assoc_altoona1_follower_sizes.json"
+    }
+ 
+}
+
+
+
+fn_log = "run_"+socket.gethostname()+".csv"
+fn_conf = "run_"+socket.gethostname()+".json"
+err_log = "run_"+socket.gethostname()+"err.log"
+print("outputfiles:",fn_log,fn_conf,err_log)
+outfile = open(fn_log, 'w')
+writer = csv.writer(outfile)
+errfile = open(err_log, 'w')
+
+
+# need repetitions
+# different cache configs
+# different working set sizes
+
+for rep in range(0,10):
+    for num_keys in [23458726]: #2345872,
+        for threads in [32,64]: #1,2,4,8,16,64]:
+            for sys in ["assoc"]:  #,"fbobj"
+                for cache in ["memcached"]: #"cachelib"
+                    config["test_config"]["popDistFile"] = sys+"_altoona1_follower_pop.json"
+                    config["test_config"]["valSizeDistFile"] = sys+"_altoona1_follower_sizes.json"
+                    config["test_config"]["numThreads"] = threads
+                    config["test_config"]["numKeys"] = num_keys
+                    config["test_config"]["numOps"] = 250000000
+                    if cache=="memcached":
+                        config["test_config"]["mode"] = "micro"
+                    else:
+                        if "mode" in config["test_config"]:
+                            del config["test_config"]["mode"]
+    
+                    res = [
+                        sys,
+                        cache,
+                        config["test_config"]["numThreads"],
+                        config["test_config"]["numKeys"],
+                        config["test_config"]["numOps"],
+                        rep,
+                        -1, #6 hr
+                        -1, #7 gets
+                        -1, #8 sets
+                        -1  #9 dels
+                    ]
+                    
+                    with open(fn_conf,"w") as jfile:
+                        json.dump(config, jfile, indent=2)
+                    
+                    #sudo /home/daniel/cachelib/build/bin/cachebench_bin --json_test_config ./fbobj_altoona2_follower_model_hr.json
+                    
+                    p = subprocess.Popen(["/proj/robinhood-PG0/cachelib/build/bin/cachebench_bin","--json_test_config","./"+fn_conf], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+                
+                    warmup = True
+                    resultsout = ""
+                    for line in io.TextIOWrapper(p.stdout, encoding="utf-8"):
+                        if "Test Results" in line:
+                            warmup = False
+                        if not warmup:
+                            resultsout += line
+                            if "get" in line:
+                                # hr
+                                hr = parseRegex("[0-9.]+(?=%)", line)
+                                if hr:
+                                    res[6] = float(hr)
+                                # gets
+                                gets = parseRegex("[0-9,]+(?=/)", line)
+                                if gets:
+                                    res[7] = int(gets)
+                            if "set" in line:
+                                # sets
+                                sets = parseRegex("[0-9,]+(?=/)", line)
+                                if sets:
+                                    res[8] = int(sets)
+                            if "del" in line:
+                                # dels
+                                dels = parseRegex("[0-9,]+(?=/)", line)
+                                if dels:
+                                    res[9] = int(dels)
+
+                    if res[6]==-1 or res[7]==-1:
+                        # wasn't able to parse
+                        errfile.write(",".join(str(item) for item in res))
+                        errfile.write(resultsout)
+                        errfile.write("n".join(str(line) for line in p.stderr))
+                        errfile.flush()
+
+                    print(res)
+                    writer.writerow(res)
+                    outfile.flush()
+outfile.close()
+errfile.close()
diff --git a/cachelib/cachebench/test_configs/comparisontest/test.json b/cachelib/cachebench/test_configs/comparisontest/test.json
new file mode 100644
index 0000000000..b8e4c10c7d
--- /dev/null
+++ b/cachelib/cachebench/test_configs/comparisontest/test.json
@@ -0,0 +1,34 @@
+{
+  "cache_config" : {
+    "cacheSizeMB": 8192,
+    "poolRebalanceIntervalSec" : 0,
+    "moveOnSlabRelease" : false,
+
+    "numPools" : 2,
+    "poolSizes" : [0.5, 0.5]
+  },
+  "test_config" : {
+      "enableLookaside" : true,
+      "prepopulateCache" : true,
+      "numOps" : 10000000,
+      "numThreads" : 32,
+      "numKeys" : 1000000,
+
+      "popDistFile": "assoc_altoona1_follower_pop.json",
+      "valSizeDistFile": "assoc_altoona1_follower_sizes.json",
+
+      "opDelayBatch" : 1,
+      "opDelayNs" : 200,
+
+      "keySizeRange" : [1, 8, 64],
+      "keySizeRangeProbability" : [0.3, 0.7],
+
+      "loneGetRatio": 0.025,
+      "getRatio": 0.975,
+      "setRatio" : 0.0,
+      "delRatio" : 0.0,
+      "keyPoolDistribution": [0.5, 0.5],
+      "opPoolDistribution" : [0.5, 0.5],
+      "mode": "micro"
+    }
+}
diff --git a/cachelib/cachebench/test_configs/simple_test.json b/cachelib/cachebench/test_configs/simple_test.json
index 05dae1cf07..de8162213a 100644
--- a/cachelib/cachebench/test_configs/simple_test.json
+++ b/cachelib/cachebench/test_configs/simple_test.json
@@ -8,9 +8,10 @@
     "poolSizes" : [0.3, 0.7]
   },
   "test_config" : {
+      "enableLookaside" : true,
       "prepopulateCache" : true,
       "numOps" : 100000,
-      "numThreads" : 32,
+      "numThreads" : 24,
       "numKeys" : 1000000,
       "distribution" :  "range",
 
@@ -23,6 +24,7 @@
       "valSizeRange" : [1, 32, 10240, 409200],
       "valSizeRangeProbability" : [0.1, 0.2, 0.7],
 
+      "mode" : "micro",
       "getRatio" : 0.15,
       "setRatio" : 0.8,
       "delRatio" : 0.05,
diff --git a/cachelib/cachebench/util/Parallel.cpp b/cachelib/cachebench/util/Parallel.cpp
new file mode 100644
index 0000000000..320b4cb27b
--- /dev/null
+++ b/cachelib/cachebench/util/Parallel.cpp
@@ -0,0 +1,66 @@
+#include "cachelib/cachebench/util/Parallel.h"
+
+namespace facebook {
+namespace cachelib {
+namespace cachebench {
+namespace detail {
+std::chrono::seconds executeParallel(
+    std::function<void(size_t start, size_t end)> fn,
+    size_t numThreads,
+    size_t count,
+    size_t offset ) {
+  numThreads = std::max(numThreads, 1UL);
+  auto startTime = std::chrono::steady_clock::now();
+  const size_t perThread = count / numThreads;
+  std::vector<std::thread> processingThreads;
+  for (size_t i = 0; i < numThreads; i++) {
+    processingThreads.emplace_back([i, perThread, offset, &fn]() {
+      size_t blockStart = offset + i * perThread;
+      size_t blockEnd = blockStart + perThread;
+      fn(blockStart, blockEnd);
+    });
+  }
+  fn(offset + perThread * numThreads, offset + count);
+  for (auto& t : processingThreads) {
+    t.join();
+  }
+
+  return std::chrono::duration_cast<std::chrono::seconds>(
+      std::chrono::steady_clock::now() - startTime);
+}
+
+std::chrono::seconds executeParallel(std::function<void()> fn,
+                                     size_t numThreads) {
+  numThreads = std::max(numThreads, 1UL);
+  auto startTime = std::chrono::steady_clock::now();
+  std::vector<std::thread> processingThreads;
+  for (size_t i = 0; i < numThreads; i++) {
+    processingThreads.emplace_back([&fn]() { fn(); });
+  }
+  for (auto& t : processingThreads) {
+    t.join();
+  }
+
+  return std::chrono::duration_cast<std::chrono::seconds>(
+      std::chrono::steady_clock::now() - startTime);
+}
+
+std::chrono::seconds executeParallelWid(std::function<void(uint32_t wid)> fn,
+                                     size_t numThreads) {
+  numThreads = std::max(numThreads, 1UL);
+  auto startTime = std::chrono::steady_clock::now();
+  std::vector<std::thread> processingThreads;
+  for (size_t i = 0; i < numThreads; i++) {
+    processingThreads.emplace_back([&fn, i]() { fn(i); });
+  }
+  for (auto& t : processingThreads) {
+    t.join();
+  }
+
+  return std::chrono::duration_cast<std::chrono::seconds>(
+      std::chrono::steady_clock::now() - startTime);
+}
+} // namespace detail
+} // namespace cachebench
+} // namespace cachelib
+} // namespace facebook
diff --git a/cachelib/cachebench/util/Parallel.h b/cachelib/cachebench/util/Parallel.h
index eee86ab04b..d9b2c09456 100644
--- a/cachelib/cachebench/util/Parallel.h
+++ b/cachelib/cachebench/util/Parallel.h
@@ -12,42 +12,12 @@ std::chrono::seconds executeParallel(
     std::function<void(size_t start, size_t end)> fn,
     size_t numThreads,
     size_t count,
-    size_t offset = 0) {
-  numThreads = std::max(numThreads, 1UL);
-  auto startTime = std::chrono::steady_clock::now();
-  const size_t perThread = count / numThreads;
-  std::vector<std::thread> processingThreads;
-  for (size_t i = 0; i < numThreads; i++) {
-    processingThreads.emplace_back([i, perThread, offset, &fn]() {
-      size_t blockStart = offset + i * perThread;
-      size_t blockEnd = blockStart + perThread;
-      fn(blockStart, blockEnd);
-    });
-  }
-  fn(offset + perThread * numThreads, offset + count);
-  for (auto& t : processingThreads) {
-    t.join();
-  }
-
-  return std::chrono::duration_cast<std::chrono::seconds>(
-      std::chrono::steady_clock::now() - startTime);
-}
+    size_t offset= 0);
 
 std::chrono::seconds executeParallel(std::function<void()> fn,
-                                     size_t numThreads) {
-  numThreads = std::max(numThreads, 1UL);
-  auto startTime = std::chrono::steady_clock::now();
-  std::vector<std::thread> processingThreads;
-  for (size_t i = 0; i < numThreads; i++) {
-    processingThreads.emplace_back([&fn]() { fn(); });
-  }
-  for (auto& t : processingThreads) {
-    t.join();
-  }
-
-  return std::chrono::duration_cast<std::chrono::seconds>(
-      std::chrono::steady_clock::now() - startTime);
-}
+                                     size_t numThreads);
+std::chrono::seconds executeParallelWid(std::function<void(uint32_t wid)> fn,
+                                     size_t numThreads);
 } // namespace detail
 } // namespace cachebench
 } // namespace cachelib
diff --git a/cachelib/common/CMakeLists.txt b/cachelib/common/CMakeLists.txt
new file mode 100644
index 0000000000..d9dadba836
--- /dev/null
+++ b/cachelib/common/CMakeLists.txt
@@ -0,0 +1,82 @@
+add_library(
+  cachelib_common
+  ApproxSplitSet.h
+  AtomicCounter.h
+  BytesEqual.h
+  Cohort.h
+  CompilerUtils.h
+  CountMinSketch.h
+  Exceptions.h
+  FastStats.h
+  Hash.h
+  Iterators.h
+  MurmurHash.h
+  Mutex.h
+  PercentileStats.h
+  PeriodicWorker.h
+  Serialization.h
+  TestUtils.h
+  Throttler.h
+  Time.h
+  Utils.h
+  Cohort.cpp
+  CountMinSketch.cpp
+  FurcHash.cpp
+  PercentileStats.cpp
+  PeriodicWorker.cpp
+  Serialization.cpp
+  TestUtils.cpp
+  Utils.cpp
+
+  hothash/HotHashDetector.h
+  hothash/HotHashDetector.cpp
+  hothash/HotHashDetectorTest.cpp
+
+  piecewise/GenericPieces.h
+  piecewise/RequestRange.h
+  piecewise/GenericPieces.cpp
+  piecewise/GenericPiecesTest.cpp
+  piecewise/RequestRange.cpp
+  piecewise/RequestRangeTest.cpp
+)
+
+target_link_libraries(
+  cachelib_common
+  PUBLIC
+  ${FOLLY_LIBRARIES}
+  cachelib_allocator
+)
+
+add_executable(
+        cachelib_common_test_bin
+
+        tests/ApproxSplitSetTest.cpp
+        tests/BytesEqualTest.cpp
+        tests/CohortTests.cpp
+        tests/CountMinSketchTest.cpp
+        tests/CounterTests.cpp
+        tests/HashTests.cpp
+        tests/IteratorsTests.cpp
+        tests/MutexTests.cpp
+        tests/PeriodicWorkerTest.cpp
+        tests/SerializationTest.cpp
+        tests/UtilTests.cpp
+)
+
+target_link_libraries(
+        cachelib_common_test_bin
+
+        PUBLIC
+            navy
+            cachelib_allocator
+            GTest::GTest
+            GTest::Main
+            ${GMOCK_LIBRARIES}
+
+)
+
+add_test(
+        NAME cachelib_common_tests
+        COMMAND cachelib_common_test_bin
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
diff --git a/cachelib/common/piecewise/RequestRangeTest.cpp b/cachelib/common/piecewise/RequestRangeTest.cpp
index 23f6eb8d0f..4d85ad83f0 100644
--- a/cachelib/common/piecewise/RequestRangeTest.cpp
+++ b/cachelib/common/piecewise/RequestRangeTest.cpp
@@ -1,7 +1,6 @@
 #include <folly/portability/GTest.h>
 
 #include "cachelib/common/piecewise/RequestRange.h"
-#include "common/logging/logging.h"
 
 using facebook::cachelib::RequestRange;
 
diff --git a/cachelib/common/tests/SerializationTest.cpp b/cachelib/common/tests/SerializationTest.cpp
index 2a08787db5..53c2e874ff 100644
--- a/cachelib/common/tests/SerializationTest.cpp
+++ b/cachelib/common/tests/SerializationTest.cpp
@@ -1,6 +1,6 @@
 #include <gtest/gtest.h>
 
-#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/memory/serialize/gen-cpp2/memory_objects_types.h"
 #include "cachelib/common/Serialization.h"
 
 constexpr size_t SerializationBufferSize = 100 * 1024;
diff --git a/cachelib/compact_cache/CMakeLists.txt b/cachelib/compact_cache/CMakeLists.txt
new file mode 100644
index 0000000000..ae24b6364e
--- /dev/null
+++ b/cachelib/compact_cache/CMakeLists.txt
@@ -0,0 +1,25 @@
+add_executable(
+        cachelib_cc_test_bin
+
+        tests/CCacheTests.h
+        tests/CCacheTests.cpp
+        )
+
+target_link_libraries(
+        cachelib_cc_test_bin
+
+        PUBLIC
+            navy
+            cachelib_allocator
+            cachelib_common
+            GTest::GTest
+            GTest::Main
+            ${GMOCK_LIBRARIES}
+            ${FOLLY_LIBRARIES}
+)
+
+add_test(
+        NAME cachelib_cc_tests
+        COMMAND cachelib_cc_test_bin
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
diff --git a/cachelib/compact_cache/allocators/CCacheAllocatorBase.h b/cachelib/compact_cache/allocators/CCacheAllocatorBase.h
index 286de424ec..4b45dd476f 100644
--- a/cachelib/compact_cache/allocators/CCacheAllocatorBase.h
+++ b/cachelib/compact_cache/allocators/CCacheAllocatorBase.h
@@ -4,7 +4,7 @@
 
 #include <folly/logging/xlog.h>
 
-#include "cachelib/allocator/serialize/gen-cpp2/objects_types.h"
+#include "cachelib/allocator/serialize/gen-cpp2/allocator_objects_types.h"
 
 namespace facebook {
 namespace cachelib {
diff --git a/cachelib/datatype/CMakeLists.txt b/cachelib/datatype/CMakeLists.txt
new file mode 100644
index 0000000000..dc9c1e771a
--- /dev/null
+++ b/cachelib/datatype/CMakeLists.txt
@@ -0,0 +1,54 @@
+add_library(
+  cachelib_datatype
+
+  Buffer-inl.h
+  Buffer.h
+  DataTypes.h
+  FixedSizeArray.h
+  Map-inl.h
+  Map.h
+  RangeMap-inl.h
+  RangeMap.h
+  Buffer.cpp
+  DataTypes.cpp
+  )
+
+target_link_libraries(
+  cachelib_datatype
+  PUBLIC
+
+    cachelib_shm
+    navy
+    ${FOLLY_LIBRARIES}
+    ${FBTHRIFT_LIBRARIES}
+)
+
+add_executable(
+        datatype_test_bin
+
+        tests/DataTypeTest.h
+        tests/BufferTest.cpp
+        tests/DataTypeTest.cpp
+        tests/FixedSizeArrayTest.cpp
+        tests/MapTest.cpp
+        tests/RangeMapTest.cpp
+    )
+
+target_link_libraries(
+        datatype_test_bin
+
+        PUBLIC
+            cachelib_datatype
+            navy
+            cachelib_allocator
+            GTest::GTest
+            GTest::Main
+            ${GMOCK_LIBRARIES}
+            ${FOLLY_LIBRARIES}
+)
+
+add_test(
+        NAME datatype_tests
+        COMMAND datatype_test_bin
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
diff --git a/cachelib/external/fbthrift b/cachelib/external/fbthrift
index f6de4a237a..4856cb5047 160000
--- a/cachelib/external/fbthrift
+++ b/cachelib/external/fbthrift
@@ -1 +1 @@
-Subproject commit f6de4a237abfe9be39305f41aade116f24ddb3a1
+Subproject commit 4856cb50477ea2cb5a35a47bc7abcc5c2943cd48
diff --git a/cachelib/external/folly b/cachelib/external/folly
index 9d9dad1690..27d015c7ef 160000
--- a/cachelib/external/folly
+++ b/cachelib/external/folly
@@ -1 +1 @@
-Subproject commit 9d9dad1690926668e53c3a98be6e94126d534d00
+Subproject commit 27d015c7eff71e7d1abc56842ad6252d47420ce1
diff --git a/cachelib/external/memcached/CMakeLists.txt b/cachelib/external/memcached/CMakeLists.txt
new file mode 100644
index 0000000000..0a168ee940
--- /dev/null
+++ b/cachelib/external/memcached/CMakeLists.txt
@@ -0,0 +1,67 @@
+#https://github.com/facebook/fbthrift/blob/master/ThriftLibrary.cmake lines 88 to 121
+add_library(
+  memcached
+
+assoc.h
+authfile.h
+bipbuffer.h
+cache.h
+config.h
+crawler.h
+crc32c.h
+extstore.h
+hash.h
+items.h
+itoa_ljust.h
+jenkins_hash.h
+logger.h
+memcached.h
+murmur3_hash.h
+protocol_binary.h
+restart.h
+sasl_defs.h
+slab_automove.h
+slab_automove_extstore.h
+slabs.h
+stats_prefix.h
+storage.h
+tls.h
+trace.h
+util.h
+assoc.c
+authfile.c
+bipbuffer.c
+cache.c
+crawler.c
+crc32c.c
+daemon.c
+extstore.c
+globals.c
+hash.c
+items.c
+itoa_ljust.c
+jenkins_hash.c
+logger.c
+memcached.c
+murmur3_hash.c
+restart.c
+sizes.c
+slab_automove.c
+slab_automove_extstore.c
+slabs.c
+stats_prefix.c
+storage.c
+thread.c
+timedrun.c
+tls.c
+util.c
+testapp.c
+  )
+
+target_link_libraries(
+  memcached
+
+  PUBLIC
+  ${LIBEVENT_LIB}
+  Threads::Threads
+)
diff --git a/cachelib/external/memcached/assoc.c b/cachelib/external/memcached/assoc.c
new file mode 100644
index 0000000000..96f8cd198f
--- /dev/null
+++ b/cachelib/external/memcached/assoc.c
@@ -0,0 +1,299 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Hash table
+ *
+ * The hash function used here is by Bob Jenkins, 1996:
+ *    <http://burtleburtle.net/bob/hash/doobs.html>
+ *       "By Bob Jenkins, 1996.  bob_jenkins@burtleburtle.net.
+ *       You may use this code any way you wish, private, educational,
+ *       or commercial.  It's free."
+ *
+ * The rest of the file is licensed under the BSD license.  See LICENSE.
+ */
+
+#include "memcached.h"
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/resource.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <pthread.h>
+
+static pthread_cond_t maintenance_cond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t maintenance_lock = PTHREAD_MUTEX_INITIALIZER;
+
+typedef  uint32_t  ub4;   /* unsigned 4-byte quantities */
+typedef  unsigned       char ub1;   /* unsigned 1-byte quantities */
+
+/* how many powers of 2's worth of buckets we use */
+unsigned int hashpower = HASHPOWER_DEFAULT;
+
+#define hashsize(n) ((ub4)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+
+/* Main hash table. This is where we look except during expansion. */
+static item** primary_hashtable = 0;
+
+/*
+ * Previous hash table. During expansion, we look here for keys that haven't
+ * been moved over to the primary yet.
+ */
+static item** old_hashtable = 0;
+
+/* Flag: Are we in the middle of expanding now? */
+static bool expanding = false;
+
+/*
+ * During expansion we migrate values with bucket granularity; this is how
+ * far we've gotten so far. Ranges from 0 .. hashsize(hashpower - 1) - 1.
+ */
+static unsigned int expand_bucket = 0;
+
+void assoc_init(const int hashtable_init) {
+    if (hashtable_init) {
+        hashpower = hashtable_init;
+    }
+    primary_hashtable = calloc(hashsize(hashpower), sizeof(void *));
+    if (! primary_hashtable) {
+        fprintf(stderr, "Failed to init hashtable.\n");
+        exit(EXIT_FAILURE);
+    }
+    STATS_LOCK();
+    stats_state.hash_power_level = hashpower;
+    stats_state.hash_bytes = hashsize(hashpower) * sizeof(void *);
+    STATS_UNLOCK();
+}
+
+item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {
+    item *it;
+    unsigned int oldbucket;
+
+    if (expanding &&
+        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+    {
+        it = old_hashtable[oldbucket];
+    } else {
+        it = primary_hashtable[hv & hashmask(hashpower)];
+    }
+
+    item *ret = NULL;
+    int depth = 0;
+    while (it) {
+        if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) {
+            ret = it;
+            break;
+        }
+        it = it->h_next;
+        ++depth;
+    }
+    MEMCACHED_ASSOC_FIND(key, nkey, depth);
+    return ret;
+}
+
+/* returns the address of the item pointer before the key.  if *item == 0,
+   the item wasn't found */
+
+static item** _hashitem_before (const char *key, const size_t nkey, const uint32_t hv) {
+    item **pos;
+    unsigned int oldbucket;
+
+    if (expanding &&
+        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+    {
+        pos = &old_hashtable[oldbucket];
+    } else {
+        pos = &primary_hashtable[hv & hashmask(hashpower)];
+    }
+
+    while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) {
+        pos = &(*pos)->h_next;
+    }
+    return pos;
+}
+
+/* grows the hashtable to the next power of 2. */
+static void assoc_expand(void) {
+    old_hashtable = primary_hashtable;
+
+    primary_hashtable = calloc(hashsize(hashpower + 1), sizeof(void *));
+    if (primary_hashtable) {
+        if (settings.verbose > 1)
+            fprintf(stderr, "Hash table expansion starting\n");
+        hashpower++;
+        expanding = true;
+        expand_bucket = 0;
+        STATS_LOCK();
+        stats_state.hash_power_level = hashpower;
+        stats_state.hash_bytes += hashsize(hashpower) * sizeof(void *);
+        stats_state.hash_is_expanding = true;
+        STATS_UNLOCK();
+    } else {
+        primary_hashtable = old_hashtable;
+        /* Bad news, but we can keep running. */
+    }
+}
+
+void assoc_start_expand(uint64_t curr_items) {
+    if (pthread_mutex_trylock(&maintenance_lock) == 0) {
+        if (curr_items > (hashsize(hashpower) * 3) / 2 && hashpower < HASHPOWER_MAX) {
+            pthread_cond_signal(&maintenance_cond);
+        }
+        pthread_mutex_unlock(&maintenance_lock);
+    }
+}
+
+/* Note: this isn't an assoc_update.  The key must not already exist to call this */
+int assoc_insert(item *it, const uint32_t hv) {
+    unsigned int oldbucket;
+
+//    assert(assoc_find(ITEM_key(it), it->nkey) == 0);  /* shouldn't have duplicately named things defined */
+
+    if (expanding &&
+        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+    {
+        it->h_next = old_hashtable[oldbucket];
+        old_hashtable[oldbucket] = it;
+    } else {
+        it->h_next = primary_hashtable[hv & hashmask(hashpower)];
+        primary_hashtable[hv & hashmask(hashpower)] = it;
+    }
+
+    MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey);
+    return 1;
+}
+
+void assoc_delete(const char *key, const size_t nkey, const uint32_t hv) {
+    item **before = _hashitem_before(key, nkey, hv);
+
+    if (*before) {
+        item *nxt;
+        /* The DTrace probe cannot be triggered as the last instruction
+         * due to possible tail-optimization by the compiler
+         */
+        MEMCACHED_ASSOC_DELETE(key, nkey);
+        nxt = (*before)->h_next;
+        (*before)->h_next = 0;   /* probably pointless, but whatever. */
+        *before = nxt;
+        return;
+    }
+    /* Note:  we never actually get here.  the callers don't delete things
+       they can't find. */
+    //assert(*before != 0);
+}
+
+
+static volatile int do_run_maintenance_thread = 1;
+
+#define DEFAULT_HASH_BULK_MOVE 1
+int hash_bulk_move = DEFAULT_HASH_BULK_MOVE;
+
+static void *assoc_maintenance_thread(void *arg) {
+
+    mutex_lock(&maintenance_lock);
+    while (do_run_maintenance_thread) {
+        int ii = 0;
+
+        /* There is only one expansion thread, so no need to global lock. */
+        for (ii = 0; ii < hash_bulk_move && expanding; ++ii) {
+            item *it, *next;
+            unsigned int bucket;
+            void *item_lock = NULL;
+
+            /* bucket = hv & hashmask(hashpower) =>the bucket of hash table
+             * is the lowest N bits of the hv, and the bucket of item_locks is
+             *  also the lowest M bits of hv, and N is greater than M.
+             *  So we can process expanding with only one item_lock. cool! */
+            if ((item_lock = item_trylock(expand_bucket))) {
+                    for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {
+                        next = it->h_next;
+                        bucket = hash(ITEM_key(it), it->nkey) & hashmask(hashpower);
+                        it->h_next = primary_hashtable[bucket];
+                        primary_hashtable[bucket] = it;
+                    }
+
+                    old_hashtable[expand_bucket] = NULL;
+
+                    expand_bucket++;
+                    if (expand_bucket == hashsize(hashpower - 1)) {
+                        expanding = false;
+                        free(old_hashtable);
+                        STATS_LOCK();
+                        stats_state.hash_bytes -= hashsize(hashpower - 1) * sizeof(void *);
+                        stats_state.hash_is_expanding = false;
+                        STATS_UNLOCK();
+                        if (settings.verbose > 1)
+                            fprintf(stderr, "Hash table expansion done\n");
+                    }
+
+            } else {
+                usleep(10*1000);
+            }
+
+            if (item_lock) {
+                item_trylock_unlock(item_lock);
+                item_lock = NULL;
+            }
+        }
+
+        if (!expanding) {
+            /* We are done expanding.. just wait for next invocation */
+            pthread_cond_wait(&maintenance_cond, &maintenance_lock);
+            /* assoc_expand() swaps out the hash table entirely, so we need
+             * all threads to not hold any references related to the hash
+             * table while this happens.
+             * This is instead of a more complex, possibly slower algorithm to
+             * allow dynamic hash table expansion without causing significant
+             * wait times.
+             */
+            if (do_run_maintenance_thread) {
+                pause_threads(PAUSE_ALL_THREADS);
+                assoc_expand();
+                pause_threads(RESUME_ALL_THREADS);
+            }
+        }
+    }
+    mutex_unlock(&maintenance_lock);
+    return NULL;
+}
+
+static pthread_t maintenance_tid;
+
+int start_assoc_maintenance_thread() {
+    int ret;
+    char *env = getenv("MEMCACHED_HASH_BULK_MOVE");
+    if (env != NULL) {
+        hash_bulk_move = atoi(env);
+        if (hash_bulk_move == 0) {
+            hash_bulk_move = DEFAULT_HASH_BULK_MOVE;
+        }
+    }
+
+    if ((ret = pthread_create(&maintenance_tid, NULL,
+                              assoc_maintenance_thread, NULL)) != 0) {
+        fprintf(stderr, "Can't create thread: %s\n", strerror(ret));
+        return -1;
+    }
+    else{
+        fprintf(stderr, "created thread: %s\n", strerror(ret));
+
+    }
+    return 0;
+}
+
+void stop_assoc_maintenance_thread() {
+
+    mutex_lock(&maintenance_lock);
+    do_run_maintenance_thread = 0;
+    pthread_cond_signal(&maintenance_cond);
+    mutex_unlock(&maintenance_lock);
+    pthread_join(maintenance_tid, NULL);
+
+    /* Wait for the maintenance thread to stop */
+}
+
diff --git a/cachelib/external/memcached/assoc.h b/cachelib/external/memcached/assoc.h
new file mode 100644
index 0000000000..fb2c922fc8
--- /dev/null
+++ b/cachelib/external/memcached/assoc.h
@@ -0,0 +1,11 @@
+/* associative array */
+void assoc_init(const int hashpower_init);
+item *assoc_find(const char *key, const size_t nkey, const uint32_t hv);
+int assoc_insert(item *item, const uint32_t hv);
+void assoc_delete(const char *key, const size_t nkey, const uint32_t hv);
+void do_assoc_move_next_bucket(void);
+int start_assoc_maintenance_thread(void);
+void stop_assoc_maintenance_thread(void);
+void assoc_start_expand(uint64_t curr_items);
+extern unsigned int hashpower;
+extern unsigned int item_lock_hashpower;
diff --git a/cachelib/external/memcached/authfile.c b/cachelib/external/memcached/authfile.c
new file mode 100644
index 0000000000..f8935af1b2
--- /dev/null
+++ b/cachelib/external/memcached/authfile.c
@@ -0,0 +1,127 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "authfile.h"
+#include "util.h"
+
+// TODO: frontend needs a refactor so this can avoid global objects.
+
+#define MAX_ENTRY_LEN 256
+// Not supposed to be a huge database!
+#define MAX_ENTRIES 8
+
+typedef struct auth_entry {
+    char *user;
+    size_t ulen;
+    char *pass;
+    size_t plen;
+} auth_t;
+
+auth_t main_auth_entries[MAX_ENTRIES];
+int entry_cnt = 0;
+char *main_auth_data = NULL;
+
+enum authfile_ret authfile_load(const char *file) {
+    struct stat sb;
+    char *auth_data = NULL;
+    auth_t auth_entries[MAX_ENTRIES];
+
+    if (stat(file, &sb) == -1) {
+        return AUTHFILE_MISSING;
+    }
+
+    auth_data = calloc(1, sb.st_size);
+
+    if (auth_data == NULL) {
+        return AUTHFILE_OOM;
+    }
+
+    FILE *pwfile = fopen(file, "r");
+    if (pwfile == NULL) {
+        // not strictly necessary but to be safe.
+        free(auth_data);
+        return AUTHFILE_OPENFAIL;
+    }
+
+    char *auth_cur = auth_data;
+    auth_t *entry_cur = auth_entries;
+    int used = 0;
+
+    while ((fgets(auth_cur, MAX_ENTRY_LEN, pwfile)) != NULL) {
+        int x;
+        int found = 0;
+
+        for (x = 0; x < MAX_ENTRY_LEN; x++) {
+            if (!found && auth_cur[x] == ':') {
+                entry_cur->user = auth_cur;
+                entry_cur->ulen = x;
+                entry_cur->pass = &auth_cur[x+1];
+                found = 1;
+            } else if (found) {
+                // Find end of password.
+                if (auth_cur[x] == '\n' ||
+                    auth_cur[x] == '\r' ||
+                    auth_cur[x] == '\0') {
+                    entry_cur->plen = x - (entry_cur->ulen + 1);
+                    break;
+                }
+            }
+        }
+
+        // malformed line.
+        if (!found) {
+            (void)fclose(pwfile);
+            free(auth_data);
+            return AUTHFILE_MALFORMED;
+        }
+
+        // FIXME: no silent truncation.
+        if (++used == MAX_ENTRIES) {
+            break;
+        }
+        // EOF
+        if (auth_cur[x] == '\0')
+            break;
+
+        auth_cur += x;
+        entry_cur++;
+    }
+
+    // swap the main pointer out now, so if there's an error reloading we
+    // don't break the existing authentication.
+    if (main_auth_data != NULL) {
+        free(main_auth_data);
+    }
+
+    entry_cnt = used;
+    main_auth_data = auth_data;
+    memcpy(main_auth_entries, auth_entries, sizeof(auth_entries));
+
+    (void)fclose(pwfile);
+
+    return AUTHFILE_OK;
+}
+
+// if only loading the file could be this short...
+int authfile_check(const char *user, const char *pass) {
+    size_t ulen = strlen(user);
+    size_t plen = strlen(pass);
+
+    for (int x = 0; x < entry_cnt; x++) {
+        auth_t *e = &main_auth_entries[x];
+        if (ulen == e->ulen && plen == e->plen &&
+            safe_memcmp(user, e->user, e->ulen) &&
+            safe_memcmp(pass, e->pass, e->plen)) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
diff --git a/cachelib/external/memcached/authfile.h b/cachelib/external/memcached/authfile.h
new file mode 100644
index 0000000000..d68c494f29
--- /dev/null
+++ b/cachelib/external/memcached/authfile.h
@@ -0,0 +1,16 @@
+#ifndef AUTHFILE_H
+#define AUTHFILE_H
+
+enum authfile_ret {
+    AUTHFILE_OK = 0,
+    AUTHFILE_MISSING,
+    AUTHFILE_OOM,
+    AUTHFILE_OPENFAIL,
+    AUTHFILE_MALFORMED,
+};
+
+// FIXME: mc_authfile or something?
+enum authfile_ret authfile_load(const char *file);
+int authfile_check(const char *user, const char *pass);
+
+#endif /* AUTHFILE_H */
diff --git a/cachelib/external/memcached/bipbuffer.c b/cachelib/external/memcached/bipbuffer.c
new file mode 100644
index 0000000000..1ff1687b9e
--- /dev/null
+++ b/cachelib/external/memcached/bipbuffer.c
@@ -0,0 +1,180 @@
+/**
+ * Copyright (c) 2011, Willem-Hendrik Thiart
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE.bipbuffer file.
+ *
+ * @file
+ * @author  Willem Thiart himself@willemthiart.com
+ */
+
+#include "stdio.h"
+#include <stdlib.h>
+
+/* for memcpy */
+#include <string.h>
+
+#include "bipbuffer.h"
+
+static size_t bipbuf_sizeof(const unsigned int size)
+{
+    return sizeof(bipbuf_t) + size;
+}
+
+int bipbuf_unused(const bipbuf_t* me)
+{
+    if (1 == me->b_inuse)
+        /* distance between region B and region A */
+        return me->a_start - me->b_end;
+    else
+        return me->size - me->a_end;
+}
+
+int bipbuf_size(const bipbuf_t* me)
+{
+    return me->size;
+}
+
+int bipbuf_used(const bipbuf_t* me)
+{
+    return (me->a_end - me->a_start) + me->b_end;
+}
+
+void bipbuf_init(bipbuf_t* me, const unsigned int size)
+{
+    me->a_start = me->a_end = me->b_end = 0;
+    me->size = size;
+    me->b_inuse = 0;
+}
+
+bipbuf_t *bipbuf_new(const unsigned int size)
+{
+    bipbuf_t *me = malloc(bipbuf_sizeof(size));
+    if (!me)
+        return NULL;
+    bipbuf_init(me, size);
+    return me;
+}
+
+void bipbuf_free(bipbuf_t* me)
+{
+    free(me);
+}
+
+int bipbuf_is_empty(const bipbuf_t* me)
+{
+    return me->a_start == me->a_end;
+}
+
+/* find out if we should turn on region B
+ * ie. is the distance from A to buffer's end less than B to A? */
+static void __check_for_switch_to_b(bipbuf_t* me)
+{
+    if (me->size - me->a_end < me->a_start - me->b_end)
+        me->b_inuse = 1;
+}
+
+/* TODO: DOCUMENT THESE TWO FUNCTIONS */
+unsigned char *bipbuf_request(bipbuf_t* me, const int size)
+{
+    if (bipbuf_unused(me) < size)
+        return 0;
+    if (1 == me->b_inuse)
+    {
+        return (unsigned char *)me->data + me->b_end;
+    }
+    else
+    {
+        return (unsigned char *)me->data + me->a_end;
+    }
+}
+
+int bipbuf_push(bipbuf_t* me, const int size)
+{
+    if (bipbuf_unused(me) < size)
+        return 0;
+
+    if (1 == me->b_inuse)
+    {
+        me->b_end += size;
+    }
+    else
+    {
+        me->a_end += size;
+    }
+
+    __check_for_switch_to_b(me);
+    return size;
+}
+
+int bipbuf_offer(bipbuf_t* me, const unsigned char *data, const int size)
+{
+    /* not enough space */
+    if (bipbuf_unused(me) < size)
+        return 0;
+
+    if (1 == me->b_inuse)
+    {
+        memcpy(me->data + me->b_end, data, size);
+        me->b_end += size;
+    }
+    else
+    {
+        memcpy(me->data + me->a_end, data, size);
+        me->a_end += size;
+    }
+
+    __check_for_switch_to_b(me);
+    return size;
+}
+
+unsigned char *bipbuf_peek(const bipbuf_t* me, const unsigned int size)
+{
+    /* make sure we can actually peek at this data */
+    if (me->size < me->a_start + size)
+        return NULL;
+
+    if (bipbuf_is_empty(me))
+        return NULL;
+
+    return (unsigned char *)me->data + me->a_start;
+}
+
+unsigned char *bipbuf_peek_all(const bipbuf_t* me, unsigned int *size)
+{
+    if (bipbuf_is_empty(me))
+        return NULL;
+
+    *size = me->a_end - me->a_start;
+    return (unsigned char*)me->data + me->a_start;
+}
+
+unsigned char *bipbuf_poll(bipbuf_t* me, const unsigned int size)
+{
+    if (bipbuf_is_empty(me))
+        return NULL;
+
+    /* make sure we can actually poll this data */
+    if (me->size < me->a_start + size)
+        return NULL;
+
+    void *end = me->data + me->a_start;
+    me->a_start += size;
+
+    /* we seem to be empty.. */
+    if (me->a_start == me->a_end)
+    {
+        /* replace a with region b */
+        if (1 == me->b_inuse)
+        {
+            me->a_start = 0;
+            me->a_end = me->b_end;
+            me->b_end = me->b_inuse = 0;
+        }
+        else
+            /* safely move cursor back to the start because we are empty */
+            me->a_start = me->a_end = 0;
+    }
+
+    __check_for_switch_to_b(me);
+    return end;
+}
diff --git a/cachelib/external/memcached/bipbuffer.h b/cachelib/external/memcached/bipbuffer.h
new file mode 100644
index 0000000000..b6ee56edd9
--- /dev/null
+++ b/cachelib/external/memcached/bipbuffer.h
@@ -0,0 +1,87 @@
+#ifndef BIPBUFFER_H
+#define BIPBUFFER_H
+
+typedef struct
+{
+    unsigned long int size;
+
+    /* region A */
+    unsigned int a_start, a_end;
+
+    /* region B */
+    unsigned int b_end;
+
+    /* is B inuse? */
+    int b_inuse;
+
+    unsigned char data[];
+} bipbuf_t;
+
+/**
+ * Create a new bip buffer.
+ *
+ * malloc()s space
+ *
+ * @param[in] size The size of the buffer */
+bipbuf_t *bipbuf_new(const unsigned int size);
+
+/**
+ * Initialise a bip buffer. Use memory provided by user.
+ *
+ * No malloc()s are performed.
+ *
+ * @param[in] size The size of the array */
+void bipbuf_init(bipbuf_t* me, const unsigned int size);
+
+/**
+ * Free the bip buffer */
+void bipbuf_free(bipbuf_t *me);
+
+/* TODO: DOCUMENTATION */
+unsigned char *bipbuf_request(bipbuf_t* me, const int size);
+int bipbuf_push(bipbuf_t* me, const int size);
+
+/**
+ * @param[in] data The data to be offered to the buffer
+ * @param[in] size The size of the data to be offered
+ * @return number of bytes offered */
+int bipbuf_offer(bipbuf_t *me, const unsigned char *data, const int size);
+
+/**
+ * Look at data. Don't move cursor
+ *
+ * @param[in] len The length of the data to be peeked
+ * @return data on success, NULL if we can't peek at this much data */
+unsigned char *bipbuf_peek(const bipbuf_t* me, const unsigned int len);
+
+/**
+ * Look at data. Don't move cursor
+ *
+ * @param[in] len The length of the data returned
+ * @return data on success, NULL if nothing available */
+unsigned char *bipbuf_peek_all(const bipbuf_t* me, unsigned int *len);
+
+/**
+ * Get pointer to data to read. Move the cursor on.
+ *
+ * @param[in] len The length of the data to be polled
+ * @return pointer to data, NULL if we can't poll this much data */
+unsigned char *bipbuf_poll(bipbuf_t* me, const unsigned int size);
+
+/**
+ * @return the size of the bipbuffer */
+int bipbuf_size(const bipbuf_t* me);
+
+/**
+ * @return 1 if buffer is empty; 0 otherwise */
+int bipbuf_is_empty(const bipbuf_t* me);
+
+/**
+ * @return how much space we have assigned */
+int bipbuf_used(const bipbuf_t* cb);
+
+/**
+ * @return bytes of unused space */
+int bipbuf_unused(const bipbuf_t* me);
+
+#endif /* BIPBUFFER_H */
diff --git a/cachelib/external/memcached/cache.c b/cachelib/external/memcached/cache.c
new file mode 100644
index 0000000000..18dc8b773c
--- /dev/null
+++ b/cachelib/external/memcached/cache.c
@@ -0,0 +1,171 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#ifndef NDEBUG
+#include <signal.h>
+#endif
+
+#include "cache.h"
+
+#ifndef NDEBUG
+const uint64_t redzone_pattern = 0xdeadbeefcafebabe;
+int cache_error = 0;
+#endif
+
+const int initial_pool_size = 64;
+
+cache_t* cache_create(const char *name, size_t bufsize, size_t align,
+                      cache_constructor_t* constructor,
+                      cache_destructor_t* destructor) {
+    cache_t* ret = calloc(1, sizeof(cache_t));
+    char* nm = strdup(name);
+    void** ptr = calloc(initial_pool_size, sizeof(void*));
+    if (ret == NULL || nm == NULL || ptr == NULL ||
+        pthread_mutex_init(&ret->mutex, NULL) == -1) {
+        free(ret);
+        free(nm);
+        free(ptr);
+        return NULL;
+    }
+
+    ret->name = nm;
+    ret->ptr = ptr;
+    ret->freetotal = initial_pool_size;
+    ret->constructor = constructor;
+    ret->destructor = destructor;
+
+#ifndef NDEBUG
+    ret->bufsize = bufsize + 2 * sizeof(redzone_pattern);
+#else
+    ret->bufsize = bufsize;
+#endif
+
+    return ret;
+}
+
+void cache_set_limit(cache_t *cache, int limit) {
+    pthread_mutex_lock(&cache->mutex);
+    cache->limit = limit;
+    pthread_mutex_unlock(&cache->mutex);
+}
+
+static inline void* get_object(void *ptr) {
+#ifndef NDEBUG
+    uint64_t *pre = ptr;
+    return pre + 1;
+#else
+    return ptr;
+#endif
+}
+
+void cache_destroy(cache_t *cache) {
+    while (cache->freecurr > 0) {
+        void *ptr = cache->ptr[--cache->freecurr];
+        if (cache->destructor) {
+            cache->destructor(get_object(ptr), NULL);
+        }
+        free(ptr);
+    }
+    free(cache->name);
+    free(cache->ptr);
+    pthread_mutex_destroy(&cache->mutex);
+    free(cache);
+}
+
+void* cache_alloc(cache_t *cache) {
+    void *ret;
+    pthread_mutex_lock(&cache->mutex);
+    ret = do_cache_alloc(cache);
+    pthread_mutex_unlock(&cache->mutex);
+    return ret;
+}
+
+void* do_cache_alloc(cache_t *cache) {
+    void *ret;
+    void *object;
+    if (cache->freecurr > 0) {
+        ret = cache->ptr[--cache->freecurr];
+        object = get_object(ret);
+    } else if (cache->limit == 0 || cache->total < cache->limit) {
+        object = ret = malloc(cache->bufsize);
+        if (ret != NULL) {
+            object = get_object(ret);
+
+            if (cache->constructor != NULL &&
+                cache->constructor(object, NULL, 0) != 0) {
+                free(ret);
+                object = NULL;
+            }
+            cache->total++;
+        }
+    } else {
+        object = NULL;
+    }
+
+#ifndef NDEBUG
+    if (object != NULL) {
+        /* add a simple form of buffer-check */
+        uint64_t *pre = ret;
+        *pre = redzone_pattern;
+        ret = pre+1;
+        memcpy(((char*)ret) + cache->bufsize - (2 * sizeof(redzone_pattern)),
+               &redzone_pattern, sizeof(redzone_pattern));
+    }
+#endif
+
+    return object;
+}
+
+void cache_free(cache_t *cache, void *ptr) {
+    pthread_mutex_lock(&cache->mutex);
+    do_cache_free(cache, ptr);
+    pthread_mutex_unlock(&cache->mutex);
+}
+
+void do_cache_free(cache_t *cache, void *ptr) {
+#ifndef NDEBUG
+    /* validate redzone... */
+    if (memcmp(((char*)ptr) + cache->bufsize - (2 * sizeof(redzone_pattern)),
+               &redzone_pattern, sizeof(redzone_pattern)) != 0) {
+        raise(SIGABRT);
+        cache_error = 1;
+        return;
+    }
+    uint64_t *pre = ptr;
+    --pre;
+    if (*pre != redzone_pattern) {
+        raise(SIGABRT);
+        cache_error = -1;
+        return;
+    }
+    ptr = pre;
+#endif
+    if (cache->limit > cache->total) {
+        /* Allow freeing in case the limit was revised downward */
+        if (cache->destructor) {
+            cache->destructor(ptr, NULL);
+        }
+        free(ptr);
+        cache->total--;
+    } else if (cache->freecurr < cache->freetotal) {
+        cache->ptr[cache->freecurr++] = ptr;
+    } else {
+        /* try to enlarge free connections array */
+        size_t newtotal = cache->freetotal * 2;
+        void **new_free = realloc(cache->ptr, sizeof(char *) * newtotal);
+        if (new_free) {
+            cache->freetotal = newtotal;
+            cache->ptr = new_free;
+            cache->ptr[cache->freecurr++] = ptr;
+        } else {
+            if (cache->destructor) {
+                cache->destructor(ptr, NULL);
+            }
+            free(ptr);
+            cache->total--;
+        }
+    }
+}
+
diff --git a/cachelib/external/memcached/cache.h b/cachelib/external/memcached/cache.h
new file mode 100644
index 0000000000..43267e85c8
--- /dev/null
+++ b/cachelib/external/memcached/cache.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#ifndef CACHE_H
+#define CACHE_H
+#include <pthread.h>
+
+#ifndef NDEBUG
+/* may be used for debug purposes */
+extern int cache_error;
+#endif
+
+/**
+ * Constructor used to initialize allocated objects
+ *
+ * @param obj pointer to the object to initialized.
+ * @param notused1 This parameter is currently not used.
+ * @param notused2 This parameter is currently not used.
+ * @return you should return 0, but currently this is not checked
+ */
+typedef int cache_constructor_t(void* obj, void* notused1, int notused2);
+/**
+ * Destructor used to clean up allocated objects before they are
+ * returned to the operating system.
+ *
+ * @param obj pointer to the object to clean up.
+ * @param notused This parameter is currently not used.
+ * @return you should return 0, but currently this is not checked
+ */
+typedef void cache_destructor_t(void* obj, void* notused);
+
+/**
+ * Definition of the structure to keep track of the internal details of
+ * the cache allocator. Touching any of these variables results in
+ * undefined behavior.
+ */
+typedef struct {
+    /** Mutex to protect access to the structure */
+    pthread_mutex_t mutex;
+    /** Name of the cache objects in this cache (provided by the caller) */
+    char *name;
+    /** List of pointers to available buffers in this cache */
+    void **ptr;
+    /** The size of each element in this cache */
+    size_t bufsize;
+    /** The capacity of the list of elements */
+    int freetotal;
+    /** Total malloc'ed objects */
+    int total;
+    /** The current number of free elements */
+    int freecurr;
+    /** A limit on the total number of elements */
+    int limit;
+    /** The constructor to be called each time we allocate more memory */
+    cache_constructor_t* constructor;
+    /** The destructor to be called each time before we release memory */
+    cache_destructor_t* destructor;
+} cache_t;
+
+/**
+ * Create an object cache.
+ *
+ * The object cache will let you allocate objects of the same size. It is fully
+ * MT safe, so you may allocate objects from multiple threads without having to
+ * do any synchronization in the application code.
+ *
+ * @param name the name of the object cache. This name may be used for debug purposes
+ *             and may help you track down what kind of object you have problems with
+ *             (buffer overruns, leakage etc)
+ * @param bufsize the size of each object in the cache
+ * @param align the alignment requirements of the objects in the cache.
+ * @param constructor the function to be called to initialize memory when we need
+ *                    to allocate more memory from the os.
+ * @param destructor the function to be called before we release the memory back
+ *                   to the os.
+ * @return a handle to an object cache if successful, NULL otherwise.
+ */
+cache_t* cache_create(const char* name, size_t bufsize, size_t align,
+                      cache_constructor_t* constructor,
+                      cache_destructor_t* destructor);
+/**
+ * Destroy an object cache.
+ *
+ * Destroy and invalidate an object cache. You should return all buffers allocated
+ * with cache_alloc by using cache_free before calling this function. Not doing
+ * so results in undefined behavior (the buffers may or may not be invalidated)
+ *
+ * @param handle the handle to the object cache to destroy.
+ */
+void cache_destroy(cache_t* handle);
+/**
+ * Allocate an object from the cache.
+ *
+ * @param handle the handle to the object cache to allocate from
+ * @return a pointer to an initialized object from the cache, or NULL if
+ *         the allocation cannot be satisfied.
+ */
+void* cache_alloc(cache_t* handle);
+void* do_cache_alloc(cache_t* handle);
+/**
+ * Return an object back to the cache.
+ *
+ * The caller should return the object in an initialized state so that
+ * the object may be returned in an expected state from cache_alloc.
+ *
+ * @param handle handle to the object cache to return the object to
+ * @param ptr pointer to the object to return.
+ */
+void cache_free(cache_t* handle, void* ptr);
+void do_cache_free(cache_t* handle, void* ptr);
+/**
+ * Set or adjust a limit for the number of objects to malloc
+ *
+ * @param handle handle to the object cache to adjust
+ * @param limit the number of objects to cache before returning NULL
+ */
+void cache_set_limit(cache_t* handle, int limit);
+
+#endif
diff --git a/cachelib/external/memcached/config.h b/cachelib/external/memcached/config.h
new file mode 100644
index 0000000000..467f335e6c
--- /dev/null
+++ b/cachelib/external/memcached/config.h
@@ -0,0 +1,195 @@
+/* config.h.  Generated from config.h.in by configure.  */
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* Set to nonzero if you want to enable ARMv8 crc32 */
+/* #undef ARM_CRC32 */
+
+/* Set to nonzero if you want to compile using ASAN */
+/* #undef ASAN */
+
+/* Set to nonzero if you want to include DTRACE */
+/* #undef ENABLE_DTRACE */
+
+/* Set to nonzero if you want to include SASL */
+/* #undef ENABLE_SASL */
+
+/* Set to nonzero if you want to enable a SASL pwdb */
+/* #undef ENABLE_SASL_PWDB */
+
+/* machine is bigendian */
+/* #undef ENDIAN_BIG */
+
+/* machine is littleendian */
+#define ENDIAN_LITTLE 1
+
+/* Set to nonzero if you want to enable extstore */
+#define EXTSTORE 1
+
+/* Define to 1 if support accept4 */
+#define HAVE_ACCEPT4 1
+
+/* Define to 1 if you have the `cap_enter' function. */
+/* #undef HAVE_CAP_ENTER */
+
+/* Define to 1 if you have the `clock_gettime' function. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define this if you have an implementation of drop_privileges() */
+/* #undef HAVE_DROP_PRIVILEGES */
+
+/* Define this if you have an implementation of drop_worker_privileges() */
+/* #undef HAVE_DROP_WORKER_PRIVILEGES */
+
+/* GCC 64bit Atomics available */
+#define HAVE_GCC_64ATOMICS 1
+
+/* GCC Atomics available */
+#define HAVE_GCC_ATOMICS 1
+
+/* Define to 1 if support getopt_long */
+#define HAVE_GETOPT_LONG 1
+
+/* Define to 1 if you have the `getpagesizes' function. */
+#define HAVE_GETPAGESIZES 1
+
+/* Have ntohll */
+/* #undef HAVE_HTONLL */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `memcntl' function. */
+/* #undef HAVE_MEMCNTL */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mlockall' function. */
+#define HAVE_MLOCKALL 1
+
+/* Define to 1 if you have the `pledge' function. */
+/* #undef HAVE_PLEDGE */
+
+/* we have sasl_callback_ft */
+#define HAVE_SASL_CALLBACK_FT 1
+
+/* Set to nonzero if your SASL implementation supports SASL_CB_GETCONF */
+/* #undef HAVE_SASL_CB_GETCONF */
+
+/* Set to nonzero if your SASL implementation supports SASL_CB_GETCONFPATH */
+/* #undef HAVE_SASL_CB_GETCONFPATH */
+
+/* Define to 1 if you have the <sasl/sasl.h> header file. */
+#define HAVE_SASL_SASL_H 1
+
+/* Define to 1 if you have the `setppriv' function. */
+/* #undef HAVE_SETPPRIV */
+
+/* Define to 1 if you have the `sigignore' function. */
+#define HAVE_SIGIGNORE 1
+
+/* Define to 1 if stdbool.h conforms to C99. */
+#define HAVE_STDBOOL_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `sysconf' function. */
+#define HAVE_SYSCONF 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if the system has the type `_Bool'. */
+#define HAVE__BOOL 1
+
+/* Machine need alignment */
+/* #undef NEED_ALIGN */
+
+/* Name of package */
+#define PACKAGE "memcached"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "memcached@googlegroups.com"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "memcached"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "memcached UNKNOWN"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "memcached"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "UNKNOWN"
+
+/* The size of `void *', as computed by sizeof. */
+#define SIZEOF_VOID_P 8
+
+/* Set to nonzero if you want to compile a statically linked binary */
+/* #undef STATIC */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Set to nonzero if you want to enable TLS */
+/* #undef TLS */
+
+/* Version number of package */
+#define VERSION "UNKNOWN"
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+   significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+#  define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+/* #  undef WORDS_BIGENDIAN */
+# endif
+#endif
+
+/* find sigignore on Linux */
+#define _GNU_SOURCE 1
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* define to int if socklen_t not available */
+/* #undef socklen_t */
+
+#if HAVE_STDBOOL_H
+#include <stdbool.h>
+#else
+#define bool char
+#define false 0
+#define true 1
+#endif 
+
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
diff --git a/cachelib/external/memcached/crawler.c b/cachelib/external/memcached/crawler.c
new file mode 100644
index 0000000000..69fe7de344
--- /dev/null
+++ b/cachelib/external/memcached/crawler.c
@@ -0,0 +1,706 @@
+/*  Copyright 2016 Netflix.
+ *
+ *  Use and distribution licensed under the BSD license.  See
+ *  the LICENSE file for full text.
+ */
+
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "config.h"
+#include "memcached.h"
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <unistd.h>
+#include <poll.h>
+
+#define LARGEST_ID POWER_LARGEST
+
+typedef struct {
+    void *c; /* original connection structure. still with source thread attached. */
+    int sfd; /* client fd. */
+    bipbuf_t *buf; /* output buffer */
+    char *cbuf; /* current buffer */
+} crawler_client_t;
+
+typedef struct _crawler_module_t crawler_module_t;
+
+typedef void (*crawler_eval_func)(crawler_module_t *cm, item *it, uint32_t hv, int slab_cls);
+typedef int (*crawler_init_func)(crawler_module_t *cm, void *data); // TODO: init args?
+typedef void (*crawler_deinit_func)(crawler_module_t *cm); // TODO: extra args?
+typedef void (*crawler_doneclass_func)(crawler_module_t *cm, int slab_cls);
+typedef void (*crawler_finalize_func)(crawler_module_t *cm);
+
+typedef struct {
+    crawler_init_func init; /* run before crawl starts */
+    crawler_eval_func eval; /* runs on an item. */
+    crawler_doneclass_func doneclass; /* runs once per sub-crawler completion. */
+    crawler_finalize_func finalize; /* runs once when all sub-crawlers are done. */
+    bool needs_lock; /* whether or not we need the LRU lock held when eval is called */
+    bool needs_client; /* whether or not to grab onto the remote client */
+} crawler_module_reg_t;
+
+struct _crawler_module_t {
+    void *data; /* opaque data pointer */
+    crawler_client_t c;
+    crawler_module_reg_t *mod;
+};
+
+static int crawler_expired_init(crawler_module_t *cm, void *data);
+static void crawler_expired_doneclass(crawler_module_t *cm, int slab_cls);
+static void crawler_expired_finalize(crawler_module_t *cm);
+static void crawler_expired_eval(crawler_module_t *cm, item *search, uint32_t hv, int i);
+
+crawler_module_reg_t crawler_expired_mod = {
+    .init = crawler_expired_init,
+    .eval = crawler_expired_eval,
+    .doneclass = crawler_expired_doneclass,
+    .finalize = crawler_expired_finalize,
+    .needs_lock = true,
+    .needs_client = false
+};
+
+static void crawler_metadump_eval(crawler_module_t *cm, item *search, uint32_t hv, int i);
+static void crawler_metadump_finalize(crawler_module_t *cm);
+
+crawler_module_reg_t crawler_metadump_mod = {
+    .init = NULL,
+    .eval = crawler_metadump_eval,
+    .doneclass = NULL,
+    .finalize = crawler_metadump_finalize,
+    .needs_lock = false,
+    .needs_client = true
+};
+
+crawler_module_reg_t *crawler_mod_regs[3] = {
+    &crawler_expired_mod,
+    &crawler_expired_mod,
+    &crawler_metadump_mod
+};
+
+static int lru_crawler_client_getbuf(crawler_client_t *c);
+crawler_module_t active_crawler_mod;
+enum crawler_run_type active_crawler_type;
+
+static crawler crawlers[LARGEST_ID];
+
+static int crawler_count = 0;
+static volatile int do_run_lru_crawler_thread = 0;
+static int lru_crawler_initialized = 0;
+static pthread_mutex_t lru_crawler_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t  lru_crawler_cond = PTHREAD_COND_INITIALIZER;
+#ifdef EXTSTORE
+/* TODO: pass this around */
+static void *storage;
+#endif
+
+/* Will crawl all slab classes a minimum of once per hour */
+#define MAX_MAINTCRAWL_WAIT 60 * 60
+
+/*** LRU CRAWLER THREAD ***/
+
+#define LRU_CRAWLER_WRITEBUF 8192
+
+static void lru_crawler_close_client(crawler_client_t *c) {
+    //fprintf(stderr, "CRAWLER: Closing client\n");
+    sidethread_conn_close(c->c);
+    c->c = NULL;
+    c->cbuf = NULL;
+    bipbuf_free(c->buf);
+    c->buf = NULL;
+}
+
+static void lru_crawler_release_client(crawler_client_t *c) {
+    //fprintf(stderr, "CRAWLER: Closing client\n");
+    redispatch_conn(c->c);
+    c->c = NULL;
+    c->cbuf = NULL;
+    bipbuf_free(c->buf);
+    c->buf = NULL;
+}
+
+static int crawler_expired_init(crawler_module_t *cm, void *data) {
+    struct crawler_expired_data *d;
+    if (data != NULL) {
+        d = data;
+        d->is_external = true;
+        cm->data = data;
+    } else {
+        // allocate data.
+        d = calloc(1, sizeof(struct crawler_expired_data));
+        if (d == NULL) {
+            return -1;
+        }
+        // init lock.
+        pthread_mutex_init(&d->lock, NULL);
+        d->is_external = false;
+        d->start_time = current_time;
+
+        cm->data = d;
+    }
+    pthread_mutex_lock(&d->lock);
+    memset(&d->crawlerstats, 0, sizeof(crawlerstats_t) * POWER_LARGEST);
+    for (int x = 0; x < POWER_LARGEST; x++) {
+        d->crawlerstats[x].start_time = current_time;
+        d->crawlerstats[x].run_complete = false;
+    }
+    pthread_mutex_unlock(&d->lock);
+    return 0;
+}
+
+static void crawler_expired_doneclass(crawler_module_t *cm, int slab_cls) {
+    struct crawler_expired_data *d = (struct crawler_expired_data *) cm->data;
+    pthread_mutex_lock(&d->lock);
+    d->crawlerstats[slab_cls].end_time = current_time;
+    d->crawlerstats[slab_cls].run_complete = true;
+    pthread_mutex_unlock(&d->lock);
+}
+
+static void crawler_expired_finalize(crawler_module_t *cm) {
+    struct crawler_expired_data *d = (struct crawler_expired_data *) cm->data;
+    pthread_mutex_lock(&d->lock);
+    d->end_time = current_time;
+    d->crawl_complete = true;
+    pthread_mutex_unlock(&d->lock);
+
+    if (!d->is_external) {
+        free(d);
+    }
+}
+
+/* I pulled this out to make the main thread clearer, but it reaches into the
+ * main thread's values too much. Should rethink again.
+ */
+static void crawler_expired_eval(crawler_module_t *cm, item *search, uint32_t hv, int i) {
+    struct crawler_expired_data *d = (struct crawler_expired_data *) cm->data;
+    pthread_mutex_lock(&d->lock);
+    crawlerstats_t *s = &d->crawlerstats[i];
+    int is_flushed = item_is_flushed(search);
+#ifdef EXTSTORE
+    bool is_valid = true;
+    if (search->it_flags & ITEM_HDR) {
+        item_hdr *hdr = (item_hdr *)ITEM_data(search);
+        if (extstore_check(storage, hdr->page_id, hdr->page_version) != 0)
+            is_valid = false;
+    }
+#endif
+    if ((search->exptime != 0 && search->exptime < current_time)
+        || is_flushed
+#ifdef EXTSTORE
+        || !is_valid
+#endif
+        ) {
+        crawlers[i].reclaimed++;
+        s->reclaimed++;
+
+        if (settings.verbose > 1) {
+            int ii;
+            char *key = ITEM_key(search);
+            fprintf(stderr, "LRU crawler found an expired item (flags: %d, slab: %d): ",
+                search->it_flags, search->slabs_clsid);
+            for (ii = 0; ii < search->nkey; ++ii) {
+                fprintf(stderr, "%c", key[ii]);
+            }
+            fprintf(stderr, "\n");
+        }
+        if ((search->it_flags & ITEM_FETCHED) == 0 && !is_flushed) {
+            crawlers[i].unfetched++;
+        }
+#ifdef EXTSTORE
+        STORAGE_delete(storage, search);
+#endif
+        do_item_unlink_nolock(search, hv);
+        do_item_remove(search);
+    } else {
+        s->seen++;
+        refcount_decr(search);
+        if (search->exptime == 0) {
+            s->noexp++;
+        } else if (search->exptime - current_time > 3599) {
+            s->ttl_hourplus++;
+        } else {
+            rel_time_t ttl_remain = search->exptime - current_time;
+            int bucket = ttl_remain / 60;
+            if (bucket <= 60) {
+                s->histo[bucket]++;
+            }
+        }
+    }
+    pthread_mutex_unlock(&d->lock);
+}
+
+static void crawler_metadump_eval(crawler_module_t *cm, item *it, uint32_t hv, int i) {
+    //int slab_id = CLEAR_LRU(i);
+    char keybuf[KEY_MAX_URI_ENCODED_LENGTH];
+    int is_flushed = item_is_flushed(it);
+    /* Ignore expired content. */
+    if ((it->exptime != 0 && it->exptime < current_time)
+        || is_flushed) {
+        refcount_decr(it);
+        return;
+    }
+    // TODO: uriencode directly into the buffer.
+    uriencode(ITEM_key(it), keybuf, it->nkey, KEY_MAX_URI_ENCODED_LENGTH);
+    int total = snprintf(cm->c.cbuf, 4096,
+            "key=%s exp=%ld la=%llu cas=%llu fetch=%s cls=%u size=%lu\n",
+            keybuf,
+            (it->exptime == 0) ? -1 : (long)(it->exptime + process_started),
+            (unsigned long long)(it->time + process_started),
+            (unsigned long long)ITEM_get_cas(it),
+            (it->it_flags & ITEM_FETCHED) ? "yes" : "no",
+            ITEM_clsid(it),
+            (unsigned long) ITEM_ntotal(it));
+    refcount_decr(it);
+    // TODO: some way of tracking the errors. these are very unlikely though.
+    if (total >= LRU_CRAWLER_WRITEBUF - 1 || total <= 0) {
+        /* Failed to write, don't push it. */
+        return;
+    }
+    bipbuf_push(cm->c.buf, total);
+}
+
+static void crawler_metadump_finalize(crawler_module_t *cm) {
+    if (cm->c.c != NULL) {
+        // Ensure space for final message.
+        lru_crawler_client_getbuf(&cm->c);
+        memcpy(cm->c.cbuf, "END\r\n", 5);
+        bipbuf_push(cm->c.buf, 5);
+    }
+}
+
+static int lru_crawler_poll(crawler_client_t *c) {
+    unsigned char *data;
+    unsigned int data_size = 0;
+    struct pollfd to_poll[1];
+    to_poll[0].fd = c->sfd;
+    to_poll[0].events = POLLOUT;
+
+    int ret = poll(to_poll, 1, 1000);
+
+    if (ret < 0) {
+        // fatal.
+        return -1;
+    }
+
+    if (ret == 0) return 0;
+
+    if (to_poll[0].revents & POLLIN) {
+        char buf[1];
+        int res = ((conn*)c->c)->read(c->c, buf, 1);
+        if (res == 0 || (res == -1 && (errno != EAGAIN && errno != EWOULDBLOCK))) {
+            lru_crawler_close_client(c);
+            return -1;
+        }
+    }
+    if ((data = bipbuf_peek_all(c->buf, &data_size)) != NULL) {
+        if (to_poll[0].revents & (POLLHUP|POLLERR)) {
+            lru_crawler_close_client(c);
+            return -1;
+        } else if (to_poll[0].revents & POLLOUT) {
+            int total = ((conn*)c->c)->write(c->c, data, data_size);
+            if (total == -1) {
+                if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                    lru_crawler_close_client(c);
+                    return -1;
+                }
+            } else if (total == 0) {
+                lru_crawler_close_client(c);
+                return -1;
+            } else {
+                bipbuf_poll(c->buf, total);
+            }
+        }
+    }
+    return 0;
+}
+
+/* Grab some space to work with, if none exists, run the poll() loop and wait
+ * for it to clear up or close.
+ * Return NULL if closed.
+ */
+static int lru_crawler_client_getbuf(crawler_client_t *c) {
+    void *buf = NULL;
+    if (c->c == NULL) return -1;
+    /* not enough space. */
+    while ((buf = bipbuf_request(c->buf, LRU_CRAWLER_WRITEBUF)) == NULL) {
+        // TODO: max loops before closing.
+        int ret = lru_crawler_poll(c);
+        if (ret < 0) return ret;
+    }
+
+    c->cbuf = buf;
+    return 0;
+}
+
+static void lru_crawler_class_done(int i) {
+    crawlers[i].it_flags = 0;
+    crawler_count--;
+    do_item_unlinktail_q((item *)&crawlers[i]);
+    do_item_stats_add_crawl(i, crawlers[i].reclaimed,
+            crawlers[i].unfetched, crawlers[i].checked);
+    pthread_mutex_unlock(&lru_locks[i]);
+    if (active_crawler_mod.mod->doneclass != NULL)
+        active_crawler_mod.mod->doneclass(&active_crawler_mod, i);
+}
+
+static void *item_crawler_thread(void *arg) {
+    int i;
+    int crawls_persleep = settings.crawls_persleep;
+
+    pthread_mutex_lock(&lru_crawler_lock);
+    pthread_cond_signal(&lru_crawler_cond);
+    settings.lru_crawler = true;
+    if (settings.verbose > 2)
+        fprintf(stderr, "Starting LRU crawler background thread\n");
+    while (do_run_lru_crawler_thread) {
+    pthread_cond_wait(&lru_crawler_cond, &lru_crawler_lock);
+
+    while (crawler_count) {
+        item *search = NULL;
+        void *hold_lock = NULL;
+
+        for (i = POWER_SMALLEST; i < LARGEST_ID; i++) {
+            if (crawlers[i].it_flags != 1) {
+                continue;
+            }
+
+            /* Get memory from bipbuf, if client has no space, flush. */
+            if (active_crawler_mod.c.c != NULL) {
+                int ret = lru_crawler_client_getbuf(&active_crawler_mod.c);
+                if (ret != 0) {
+                    lru_crawler_class_done(i);
+                    continue;
+                }
+            } else if (active_crawler_mod.mod->needs_client) {
+                lru_crawler_class_done(i);
+                continue;
+            }
+            pthread_mutex_lock(&lru_locks[i]);
+            search = do_item_crawl_q((item *)&crawlers[i]);
+            if (search == NULL ||
+                (crawlers[i].remaining && --crawlers[i].remaining < 1)) {
+                if (settings.verbose > 2)
+                    fprintf(stderr, "Nothing left to crawl for %d\n", i);
+                lru_crawler_class_done(i);
+                continue;
+            }
+            uint32_t hv = hash(ITEM_key(search), search->nkey);
+            /* Attempt to hash item lock the "search" item. If locked, no
+             * other callers can incr the refcount
+             */
+            if ((hold_lock = item_trylock(hv)) == NULL) {
+                pthread_mutex_unlock(&lru_locks[i]);
+                continue;
+            }
+            /* Now see if the item is refcount locked */
+            if (refcount_incr(search) != 2) {
+                refcount_decr(search);
+                if (hold_lock)
+                    item_trylock_unlock(hold_lock);
+                pthread_mutex_unlock(&lru_locks[i]);
+                continue;
+            }
+
+            crawlers[i].checked++;
+            /* Frees the item or decrements the refcount. */
+            /* Interface for this could improve: do the free/decr here
+             * instead? */
+            if (!active_crawler_mod.mod->needs_lock) {
+                pthread_mutex_unlock(&lru_locks[i]);
+            }
+
+            active_crawler_mod.mod->eval(&active_crawler_mod, search, hv, i);
+
+            if (hold_lock)
+                item_trylock_unlock(hold_lock);
+            if (active_crawler_mod.mod->needs_lock) {
+                pthread_mutex_unlock(&lru_locks[i]);
+            }
+
+            if (crawls_persleep-- <= 0 && settings.lru_crawler_sleep) {
+                pthread_mutex_unlock(&lru_crawler_lock);
+                usleep(settings.lru_crawler_sleep);
+                pthread_mutex_lock(&lru_crawler_lock);
+                crawls_persleep = settings.crawls_persleep;
+            } else if (!settings.lru_crawler_sleep) {
+                // TODO: only cycle lock every N?
+                pthread_mutex_unlock(&lru_crawler_lock);
+                pthread_mutex_lock(&lru_crawler_lock);
+            }
+        }
+    }
+
+    if (active_crawler_mod.mod != NULL) {
+        if (active_crawler_mod.mod->finalize != NULL)
+            active_crawler_mod.mod->finalize(&active_crawler_mod);
+        while (active_crawler_mod.c.c != NULL && bipbuf_used(active_crawler_mod.c.buf)) {
+            lru_crawler_poll(&active_crawler_mod.c);
+        }
+        // Double checking in case the client closed during the poll
+        if (active_crawler_mod.c.c != NULL) {
+            lru_crawler_release_client(&active_crawler_mod.c);
+        }
+        active_crawler_mod.mod = NULL;
+    }
+
+    if (settings.verbose > 2)
+        fprintf(stderr, "LRU crawler thread sleeping\n");
+
+    STATS_LOCK();
+    stats_state.lru_crawler_running = false;
+    STATS_UNLOCK();
+    }
+    pthread_mutex_unlock(&lru_crawler_lock);
+    if (settings.verbose > 2)
+        fprintf(stderr, "LRU crawler thread stopping\n");
+    settings.lru_crawler = false;
+
+    return NULL;
+}
+
+static pthread_t item_crawler_tid;
+
+int stop_item_crawler_thread(bool wait) {
+    int ret;
+    pthread_mutex_lock(&lru_crawler_lock);
+    if (do_run_lru_crawler_thread == 0) {
+        pthread_mutex_unlock(&lru_crawler_lock);
+        return 0;
+    }
+    do_run_lru_crawler_thread = 0;
+    pthread_cond_signal(&lru_crawler_cond);
+    pthread_mutex_unlock(&lru_crawler_lock);
+    if (wait && (ret = pthread_join(item_crawler_tid, NULL)) != 0) {
+        fprintf(stderr, "Failed to stop LRU crawler thread: %s\n", strerror(ret));
+        return -1;
+    }
+    return 0;
+}
+
+/* Lock dance to "block" until thread is waiting on its condition:
+ * caller locks mtx. caller spawns thread.
+ * thread blocks on mutex.
+ * caller waits on condition, releases lock.
+ * thread gets lock, sends signal.
+ * caller can't wait, as thread has lock.
+ * thread waits on condition, releases lock
+ * caller wakes on condition, gets lock.
+ * caller immediately releases lock.
+ * thread is now safely waiting on condition before the caller returns.
+ */
+int start_item_crawler_thread(void) {
+    int ret;
+
+    if (settings.lru_crawler)
+        return -1;
+    pthread_mutex_lock(&lru_crawler_lock);
+    do_run_lru_crawler_thread = 1;
+    if ((ret = pthread_create(&item_crawler_tid, NULL,
+        item_crawler_thread, NULL)) != 0) {
+        fprintf(stderr, "Can't create LRU crawler thread: %s\n",
+            strerror(ret));
+        pthread_mutex_unlock(&lru_crawler_lock);
+        return -1;
+    }
+    /* Avoid returning until the crawler has actually started */
+    pthread_cond_wait(&lru_crawler_cond, &lru_crawler_lock);
+    pthread_mutex_unlock(&lru_crawler_lock);
+
+    return 0;
+}
+
+/* 'remaining' is passed in so the LRU maintainer thread can scrub the whole
+ * LRU every time.
+ */
+static int do_lru_crawler_start(uint32_t id, uint32_t remaining) {
+    uint32_t sid = id;
+    int starts = 0;
+
+    pthread_mutex_lock(&lru_locks[sid]);
+    if (crawlers[sid].it_flags == 0) {
+        if (settings.verbose > 2)
+            fprintf(stderr, "Kicking LRU crawler off for LRU %u\n", sid);
+        crawlers[sid].nbytes = 0;
+        crawlers[sid].nkey = 0;
+        crawlers[sid].it_flags = 1; /* For a crawler, this means enabled. */
+        crawlers[sid].next = 0;
+        crawlers[sid].prev = 0;
+        crawlers[sid].time = 0;
+        if (remaining == LRU_CRAWLER_CAP_REMAINING) {
+            remaining = do_get_lru_size(sid);
+        }
+        /* Values for remaining:
+         * remaining = 0
+         * - scan all elements, until a NULL is reached
+         * - if empty, NULL is reached right away
+         * remaining = n + 1
+         * - first n elements are parsed (or until a NULL is reached)
+         */
+        if (remaining) remaining++;
+        crawlers[sid].remaining = remaining;
+        crawlers[sid].slabs_clsid = sid;
+        crawlers[sid].reclaimed = 0;
+        crawlers[sid].unfetched = 0;
+        crawlers[sid].checked = 0;
+        do_item_linktail_q((item *)&crawlers[sid]);
+        crawler_count++;
+        starts++;
+    }
+    pthread_mutex_unlock(&lru_locks[sid]);
+    if (starts) {
+        STATS_LOCK();
+        stats_state.lru_crawler_running = true;
+        stats.lru_crawler_starts++;
+        STATS_UNLOCK();
+    }
+    return starts;
+}
+
+static int lru_crawler_set_client(crawler_module_t *cm, void *c, const int sfd) {
+    crawler_client_t *crawlc = &cm->c;
+    if (crawlc->c != NULL) {
+        return -1;
+    }
+    crawlc->c = c;
+    crawlc->sfd = sfd;
+
+    crawlc->buf = bipbuf_new(1024 * 128);
+    if (crawlc->buf == NULL) {
+        return -2;
+    }
+    return 0;
+}
+
+int lru_crawler_start(uint8_t *ids, uint32_t remaining,
+                             const enum crawler_run_type type, void *data,
+                             void *c, const int sfd) {
+    int starts = 0;
+    bool is_running;
+    static rel_time_t block_ae_until = 0;
+    pthread_mutex_lock(&lru_crawler_lock);
+    STATS_LOCK();
+    is_running = stats_state.lru_crawler_running;
+    STATS_UNLOCK();
+    if (do_run_lru_crawler_thread == 0) {
+        pthread_mutex_unlock(&lru_crawler_lock);
+        return -2;
+    }
+
+    if (is_running &&
+            !(type == CRAWLER_AUTOEXPIRE && active_crawler_type == CRAWLER_AUTOEXPIRE)) {
+        pthread_mutex_unlock(&lru_crawler_lock);
+        block_ae_until = current_time + 60;
+        return -1;
+    }
+
+    if (type == CRAWLER_AUTOEXPIRE && block_ae_until > current_time) {
+        pthread_mutex_unlock(&lru_crawler_lock);
+        return -1;
+    }
+
+    /* Configure the module */
+    if (!is_running) {
+        assert(crawler_mod_regs[type] != NULL);
+        active_crawler_mod.mod = crawler_mod_regs[type];
+        active_crawler_type = type;
+        if (active_crawler_mod.mod->init != NULL) {
+            active_crawler_mod.mod->init(&active_crawler_mod, data);
+        }
+        if (active_crawler_mod.mod->needs_client) {
+            if (c == NULL || sfd == 0) {
+                pthread_mutex_unlock(&lru_crawler_lock);
+                return -2;
+            }
+            if (lru_crawler_set_client(&active_crawler_mod, c, sfd) != 0) {
+                pthread_mutex_unlock(&lru_crawler_lock);
+                return -2;
+            }
+        }
+    }
+
+    /* we allow the autocrawler to restart sub-LRU's before completion */
+    for (int sid = POWER_SMALLEST; sid < POWER_LARGEST; sid++) {
+        if (ids[sid])
+            starts += do_lru_crawler_start(sid, remaining);
+    }
+    if (starts) {
+        pthread_cond_signal(&lru_crawler_cond);
+    }
+    pthread_mutex_unlock(&lru_crawler_lock);
+    return starts;
+}
+
+/*
+ * Also only clear the crawlerstats once per sid.
+ */
+enum crawler_result_type lru_crawler_crawl(char *slabs, const enum crawler_run_type type,
+        void *c, const int sfd, unsigned int remaining) {
+    char *b = NULL;
+    uint32_t sid = 0;
+    int starts = 0;
+    uint8_t tocrawl[POWER_LARGEST];
+
+    /* FIXME: I added this while debugging. Don't think it's needed? */
+    memset(tocrawl, 0, sizeof(uint8_t) * POWER_LARGEST);
+    if (strcmp(slabs, "all") == 0) {
+        for (sid = 0; sid < POWER_LARGEST; sid++) {
+            tocrawl[sid] = 1;
+        }
+    } else {
+        for (char *p = strtok_r(slabs, ",", &b);
+             p != NULL;
+             p = strtok_r(NULL, ",", &b)) {
+
+            if (!safe_strtoul(p, &sid) || sid < POWER_SMALLEST
+                    || sid >= MAX_NUMBER_OF_SLAB_CLASSES) {
+                pthread_mutex_unlock(&lru_crawler_lock);
+                return CRAWLER_BADCLASS;
+            }
+            tocrawl[sid | TEMP_LRU] = 1;
+            tocrawl[sid | HOT_LRU] = 1;
+            tocrawl[sid | WARM_LRU] = 1;
+            tocrawl[sid | COLD_LRU] = 1;
+        }
+    }
+
+    starts = lru_crawler_start(tocrawl, remaining, type, NULL, c, sfd);
+    if (starts == -1) {
+        return CRAWLER_RUNNING;
+    } else if (starts == -2) {
+        return CRAWLER_ERROR; /* FIXME: not very helpful. */
+    } else if (starts) {
+        return CRAWLER_OK;
+    } else {
+        return CRAWLER_NOTSTARTED;
+    }
+}
+
+/* If we hold this lock, crawler can't wake up or move */
+void lru_crawler_pause(void) {
+    pthread_mutex_lock(&lru_crawler_lock);
+}
+
+void lru_crawler_resume(void) {
+    pthread_mutex_unlock(&lru_crawler_lock);
+}
+
+int init_lru_crawler(void *arg) {
+    if (lru_crawler_initialized == 0) {
+#ifdef EXTSTORE
+        storage = arg;
+#endif
+        active_crawler_mod.c.c = NULL;
+        active_crawler_mod.mod = NULL;
+        active_crawler_mod.data = NULL;
+        lru_crawler_initialized = 1;
+    }
+    return 0;
+}
diff --git a/cachelib/external/memcached/crawler.h b/cachelib/external/memcached/crawler.h
new file mode 100644
index 0000000000..e949a832fa
--- /dev/null
+++ b/cachelib/external/memcached/crawler.h
@@ -0,0 +1,44 @@
+#ifndef CRAWLER_H
+#define CRAWLER_H
+
+#define LRU_CRAWLER_CAP_REMAINING -1
+#include <stdbool.h>
+
+typedef struct {
+    uint64_t histo[61];
+    uint64_t ttl_hourplus;
+    uint64_t noexp;
+    uint64_t reclaimed;
+    uint64_t seen;
+    rel_time_t start_time;
+    rel_time_t end_time;
+    bool run_complete;
+} crawlerstats_t;
+
+struct crawler_expired_data {
+    pthread_mutex_t lock;
+    crawlerstats_t crawlerstats[POWER_LARGEST];
+    /* redundant with crawlerstats_t so we can get overall start/stop/done */
+    rel_time_t start_time;
+    rel_time_t end_time;
+    bool crawl_complete;
+    bool is_external; /* whether this was an alloc local or remote to the module. */
+};
+
+enum crawler_result_type {
+    CRAWLER_OK=0, CRAWLER_RUNNING, CRAWLER_BADCLASS, CRAWLER_NOTSTARTED, CRAWLER_ERROR
+};
+int start_item_crawler_thread(void);
+#define CRAWLER_WAIT true
+#define CRAWLER_NOWAIT false
+int stop_item_crawler_thread(bool wait);
+int init_lru_crawler(void *arg);
+enum crawler_result_type lru_crawler_crawl(char *slabs, enum crawler_run_type,
+        void *c, const int sfd, unsigned int remaining);
+int lru_crawler_start(uint8_t *ids, uint32_t remaining,
+                             const enum crawler_run_type type, void *data,
+                             void *c, const int sfd);
+void lru_crawler_pause(void);
+void lru_crawler_resume(void);
+
+#endif
diff --git a/cachelib/external/memcached/crc32c.c b/cachelib/external/memcached/crc32c.c
new file mode 100644
index 0000000000..a3c526eb37
--- /dev/null
+++ b/cachelib/external/memcached/crc32c.c
@@ -0,0 +1,415 @@
+/* crc32c.c -- compute CRC-32C using the Intel crc32 instruction
+ * Copyright (C) 2013 Mark Adler
+ * Version 1.1  1 Aug 2013  Mark Adler
+ */
+
+/*
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the author be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  Mark Adler
+  madler@alumni.caltech.edu
+ */
+
+/* Use hardware CRC instruction on Intel SSE 4.2 processors.  This computes a
+   CRC-32C, *not* the CRC-32 used by Ethernet and zip, gzip, etc.  A software
+   version is provided as a fall-back, as well as for speed comparisons. */
+
+/* Version history:
+   1.0  10 Feb 2013  First version
+   1.1   1 Aug 2013  Correct comments on why three crc instructions in parallel
+ */
+
+/* This version has been modified by dormando for inclusion in memcached */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <pthread.h>
+#include "config.h"
+#if defined(__linux__) && defined(__aarch64__)
+#include <sys/auxv.h>
+#endif
+#include "crc32c.h"
+
+crc_func crc32c;
+
+/* CRC-32C (iSCSI) polynomial in reversed bit order. */
+#define POLY 0x82f63b78
+
+/* Table for a quadword-at-a-time software crc. */
+static pthread_once_t crc32c_once_sw = PTHREAD_ONCE_INIT;
+static uint32_t crc32c_table[8][256];
+
+/* Construct table for software CRC-32C calculation. */
+static void crc32c_init_sw(void)
+{
+    uint32_t n, crc, k;
+
+    for (n = 0; n < 256; n++) {
+        crc = n;
+        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
+        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
+        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
+        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
+        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
+        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
+        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
+        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
+        crc32c_table[0][n] = crc;
+    }
+    for (n = 0; n < 256; n++) {
+        crc = crc32c_table[0][n];
+        for (k = 1; k < 8; k++) {
+            crc = crc32c_table[0][crc & 0xff] ^ (crc >> 8);
+            crc32c_table[k][n] = crc;
+        }
+    }
+}
+
+/* Table-driven software version as a fall-back.  This is about 15 times slower
+   than using the hardware instructions.  This assumes little-endian integers,
+   as is the case on Intel processors that the assembler code here is for. */
+static uint32_t crc32c_sw(uint32_t crci, const void *buf, size_t len)
+{
+    const unsigned char *next = buf;
+    uint64_t crc;
+
+    pthread_once(&crc32c_once_sw, crc32c_init_sw);
+    crc = crci ^ 0xffffffff;
+    while (len && ((uintptr_t)next & 7) != 0) {
+        crc = crc32c_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
+        len--;
+    }
+    while (len >= 8) {
+        crc ^= *(uint64_t *)next;
+        crc = crc32c_table[7][crc & 0xff] ^
+              crc32c_table[6][(crc >> 8) & 0xff] ^
+              crc32c_table[5][(crc >> 16) & 0xff] ^
+              crc32c_table[4][(crc >> 24) & 0xff] ^
+              crc32c_table[3][(crc >> 32) & 0xff] ^
+              crc32c_table[2][(crc >> 40) & 0xff] ^
+              crc32c_table[1][(crc >> 48) & 0xff] ^
+              crc32c_table[0][crc >> 56];
+        next += 8;
+        len -= 8;
+    }
+    while (len) {
+        crc = crc32c_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
+        len--;
+    }
+    return (uint32_t)crc ^ 0xffffffff;
+}
+
+/* Hardware CRC support for aarch64 platform */
+#if defined(__linux__) && defined(__aarch64__) && defined(ARM_CRC32)
+
+#define CRC32CX(crc, value) __asm__("crc32cx %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(+value))
+#define CRC32CW(crc, value) __asm__("crc32cw %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(+value))
+#define CRC32CH(crc, value) __asm__("crc32ch %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(+value))
+#define CRC32CB(crc, value) __asm__("crc32cb %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(+value))
+
+#ifndef HWCAP_CRC32
+#define HWCAP_CRC32             (1 << 7)
+#endif /* HWCAP for crc32 */
+
+static uint32_t crc32c_hw_aarch64(uint32_t crc, const void* buf, size_t len)
+{
+        const uint8_t* p_buf = buf;
+        uint64_t crc64bit = crc;
+        for (size_t i = 0; i < len / sizeof(uint64_t); i++) {
+                CRC32CX(crc64bit, *(uint64_t*) p_buf);
+                p_buf += sizeof(uint64_t);
+        }
+
+        uint32_t crc32bit = (uint32_t) crc64bit;
+        len &= sizeof(uint64_t) - 1;
+        switch (len) {
+        case 7:
+                CRC32CB(crc32bit, *p_buf++);
+        case 6:
+                CRC32CH(crc32bit, *(uint16_t*) p_buf);
+                p_buf += 2;
+        case 4:
+                CRC32CW(crc32bit, *(uint32_t*) p_buf);
+                break;
+        case 3:
+                CRC32CB(crc32bit, *p_buf++);
+        case 2:
+                CRC32CH(crc32bit, *(uint16_t*) p_buf);
+                break;
+        case 5:
+                CRC32CW(crc32bit, *(uint32_t*) p_buf);
+                p_buf += 4;
+        case 1:
+                CRC32CB(crc32bit, *p_buf);
+                break;
+        case 0:
+                break;
+        }
+
+        return crc32bit;
+}
+#endif
+
+/* Apply if the platform is intel */
+#if defined(__X86_64__)||defined(__x86_64__)||defined(__ia64__)
+
+/* Multiply a matrix times a vector over the Galois field of two elements,
+   GF(2).  Each element is a bit in an unsigned integer.  mat must have at
+   least as many entries as the power of two for most significant one bit in
+   vec. */
+static inline uint32_t gf2_matrix_times(uint32_t *mat, uint32_t vec)
+{
+    uint32_t sum;
+
+    sum = 0;
+    while (vec) {
+        if (vec & 1)
+            sum ^= *mat;
+        vec >>= 1;
+        mat++;
+    }
+    return sum;
+}
+
+/* Multiply a matrix by itself over GF(2).  Both mat and square must have 32
+   rows. */
+static inline void gf2_matrix_square(uint32_t *square, uint32_t *mat)
+{
+    int n;
+
+    for (n = 0; n < 32; n++)
+        square[n] = gf2_matrix_times(mat, mat[n]);
+}
+
+/* Construct an operator to apply len zeros to a crc.  len must be a power of
+   two.  If len is not a power of two, then the result is the same as for the
+   largest power of two less than len.  The result for len == 0 is the same as
+   for len == 1.  A version of this routine could be easily written for any
+   len, but that is not needed for this application. */
+static void crc32c_zeros_op(uint32_t *even, size_t len)
+{
+    int n;
+    uint32_t row;
+    uint32_t odd[32];       /* odd-power-of-two zeros operator */
+
+    /* put operator for one zero bit in odd */
+    odd[0] = POLY;              /* CRC-32C polynomial */
+    row = 1;
+    for (n = 1; n < 32; n++) {
+        odd[n] = row;
+        row <<= 1;
+    }
+
+    /* put operator for two zero bits in even */
+    gf2_matrix_square(even, odd);
+
+    /* put operator for four zero bits in odd */
+    gf2_matrix_square(odd, even);
+
+    /* first square will put the operator for one zero byte (eight zero bits),
+       in even -- next square puts operator for two zero bytes in odd, and so
+       on, until len has been rotated down to zero */
+    do {
+        gf2_matrix_square(even, odd);
+        len >>= 1;
+        if (len == 0)
+            return;
+        gf2_matrix_square(odd, even);
+        len >>= 1;
+    } while (len);
+
+    /* answer ended up in odd -- copy to even */
+    for (n = 0; n < 32; n++)
+        even[n] = odd[n];
+}
+
+/* Take a length and build four lookup tables for applying the zeros operator
+   for that length, byte-by-byte on the operand. */
+static void crc32c_zeros(uint32_t zeros[][256], size_t len)
+{
+    uint32_t n;
+    uint32_t op[32];
+
+    crc32c_zeros_op(op, len);
+    for (n = 0; n < 256; n++) {
+        zeros[0][n] = gf2_matrix_times(op, n);
+        zeros[1][n] = gf2_matrix_times(op, n << 8);
+        zeros[2][n] = gf2_matrix_times(op, n << 16);
+        zeros[3][n] = gf2_matrix_times(op, n << 24);
+    }
+}
+
+/* Apply the zeros operator table to crc. */
+static inline uint32_t crc32c_shift(uint32_t zeros[][256], uint32_t crc)
+{
+    return zeros[0][crc & 0xff] ^ zeros[1][(crc >> 8) & 0xff] ^
+           zeros[2][(crc >> 16) & 0xff] ^ zeros[3][crc >> 24];
+}
+
+/* Block sizes for three-way parallel crc computation.  LONG and SHORT must
+   both be powers of two.  The associated string constants must be set
+   accordingly, for use in constructing the assembler instructions. */
+#define LONG 8192
+#define LONGx1 "8192"
+#define LONGx2 "16384"
+#define SHORT 256
+#define SHORTx1 "256"
+#define SHORTx2 "512"
+
+/* Tables for hardware crc that shift a crc by LONG and SHORT zeros. */
+static pthread_once_t crc32c_once_hw = PTHREAD_ONCE_INIT;
+static uint32_t crc32c_long[4][256];
+static uint32_t crc32c_short[4][256];
+
+/* Initialize tables for shifting crcs. */
+static void crc32c_init_hw(void)
+{
+    crc32c_zeros(crc32c_long, LONG);
+    crc32c_zeros(crc32c_short, SHORT);
+}
+
+/* Compute CRC-32C using the Intel hardware instruction. */
+static uint32_t crc32c_hw(uint32_t crc, const void *buf, size_t len)
+{
+    const unsigned char *next = buf;
+    const unsigned char *end;
+    uint64_t crc0, crc1, crc2;      /* need to be 64 bits for crc32q */
+
+    /* populate shift tables the first time through */
+    pthread_once(&crc32c_once_hw, crc32c_init_hw);
+
+    /* pre-process the crc */
+    crc0 = crc ^ 0xffffffff;
+
+    /* compute the crc for up to seven leading bytes to bring the data pointer
+       to an eight-byte boundary */
+    while (len && ((uintptr_t)next & 7) != 0) {
+        __asm__("crc32b\t" "(%1), %0"
+                : "=r"(crc0)
+                : "r"(next), "0"(crc0));
+        next++;
+        len--;
+    }
+
+    /* compute the crc on sets of LONG*3 bytes, executing three independent crc
+       instructions, each on LONG bytes -- this is optimized for the Nehalem,
+       Westmere, Sandy Bridge, and Ivy Bridge architectures, which have a
+       throughput of one crc per cycle, but a latency of three cycles */
+    while (len >= LONG*3) {
+        crc1 = 0;
+        crc2 = 0;
+        end = next + LONG;
+        do {
+            __asm__("crc32q\t" "(%3), %0\n\t"
+                    "crc32q\t" LONGx1 "(%3), %1\n\t"
+                    "crc32q\t" LONGx2 "(%3), %2"
+                    : "=r"(crc0), "=r"(crc1), "=r"(crc2)
+                    : "r"(next), "0"(crc0), "1"(crc1), "2"(crc2));
+            next += 8;
+        } while (next < end);
+        crc0 = crc32c_shift(crc32c_long, crc0) ^ crc1;
+        crc0 = crc32c_shift(crc32c_long, crc0) ^ crc2;
+        next += LONG*2;
+        len -= LONG*3;
+    }
+
+    /* do the same thing, but now on SHORT*3 blocks for the remaining data less
+       than a LONG*3 block */
+    while (len >= SHORT*3) {
+        crc1 = 0;
+        crc2 = 0;
+        end = next + SHORT;
+        do {
+            __asm__("crc32q\t" "(%3), %0\n\t"
+                    "crc32q\t" SHORTx1 "(%3), %1\n\t"
+                    "crc32q\t" SHORTx2 "(%3), %2"
+                    : "=r"(crc0), "=r"(crc1), "=r"(crc2)
+                    : "r"(next), "0"(crc0), "1"(crc1), "2"(crc2));
+            next += 8;
+        } while (next < end);
+        crc0 = crc32c_shift(crc32c_short, crc0) ^ crc1;
+        crc0 = crc32c_shift(crc32c_short, crc0) ^ crc2;
+        next += SHORT*2;
+        len -= SHORT*3;
+    }
+
+    /* compute the crc on the remaining eight-byte units less than a SHORT*3
+       block */
+    end = next + (len - (len & 7));
+    while (next < end) {
+        __asm__("crc32q\t" "(%1), %0"
+                : "=r"(crc0)
+                : "r"(next), "0"(crc0));
+        next += 8;
+    }
+    len &= 7;
+
+    /* compute the crc for up to seven trailing bytes */
+    while (len) {
+        __asm__("crc32b\t" "(%1), %0"
+                : "=r"(crc0)
+                : "r"(next), "0"(crc0));
+        next++;
+        len--;
+    }
+
+    /* return a post-processed crc */
+    return (uint32_t)crc0 ^ 0xffffffff;
+}
+
+/* Check for SSE 4.2.  SSE 4.2 was first supported in Nehalem processors
+   introduced in November, 2008.  This does not check for the existence of the
+   cpuid instruction itself, which was introduced on the 486SL in 1992, so this
+   will fail on earlier x86 processors.  cpuid works on all Pentium and later
+   processors. */
+#define SSE42(have) \
+    do { \
+        uint32_t eax, ecx; \
+        eax = 1; \
+        __asm__("cpuid" \
+                : "=c"(ecx) \
+                : "a"(eax) \
+                : "%ebx", "%edx"); \
+        (have) = (ecx >> 20) & 1; \
+    } while (0)
+
+#endif
+/* Compute a CRC-32C.  If the crc32 instruction is available, use the hardware
+   version.  Otherwise, use the software version. */
+void crc32c_init(void) {
+    #if defined(__X86_64__)||defined(__x86_64__)||defined(__ia64__)
+    int sse42;
+    SSE42(sse42);
+
+    if (sse42) {
+        crc32c = crc32c_hw;
+    } else
+    #endif
+    /* Check if CRC instructions supported by aarch64 */
+    #if defined(__linux__) && defined(__aarch64__) && defined(ARM_CRC32)
+    unsigned long hwcap = getauxval(AT_HWCAP);
+
+    if (hwcap & HWCAP_CRC32) {
+        crc32c = crc32c_hw_aarch64;
+    } else
+    #endif
+    {
+        crc32c = crc32c_sw;
+    }
+}
diff --git a/cachelib/external/memcached/crc32c.h b/cachelib/external/memcached/crc32c.h
new file mode 100644
index 0000000000..c09cb42350
--- /dev/null
+++ b/cachelib/external/memcached/crc32c.h
@@ -0,0 +1,9 @@
+#ifndef CRC32C_H
+#define    CRC32C_H
+
+typedef uint32_t (*crc_func)(uint32_t crc, const void *buf, size_t len);
+extern crc_func crc32c;
+
+void crc32c_init(void);
+
+#endif    /* CRC32C_H */
diff --git a/cachelib/external/memcached/daemon.c b/cachelib/external/memcached/daemon.c
new file mode 100644
index 0000000000..0485453eda
--- /dev/null
+++ b/cachelib/external/memcached/daemon.c
@@ -0,0 +1,89 @@
+/*    $Header: /cvsroot/wikipedia/willow/src/bin/willow/daemon.c,v 1.1 2005/05/02 19:15:21 kateturner Exp $    */
+/*    $NetBSD: daemon.c,v 1.9 2003/08/07 16:42:46 agc Exp $    */
+/*-
+ * Copyright (c) 1990, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined __SUNPRO_C || defined __DECC || defined __HP_cc
+# pragma ident "@(#)$Header: /cvsroot/wikipedia/willow/src/bin/willow/daemon.c,v 1.1 2005/05/02 19:15:21 kateturner Exp $"
+# pragma ident "$NetBSD: daemon.c,v 1.9 2003/08/07 16:42:46 agc Exp $"
+#endif
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "memcached.h"
+
+int daemonize(int nochdir, int noclose)
+{
+    int fd;
+
+    switch (fork()) {
+    case -1:
+        return (-1);
+    case 0:
+        break;
+    default:
+        _exit(EXIT_SUCCESS);
+    }
+
+    if (setsid() == -1)
+        return (-1);
+
+    if (nochdir == 0) {
+        if(chdir("/") != 0) {
+            perror("chdir");
+            return (-1);
+        }
+    }
+
+    if (noclose == 0 && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
+        if(dup2(fd, STDIN_FILENO) < 0) {
+            perror("dup2 stdin");
+            return (-1);
+        }
+        if(dup2(fd, STDOUT_FILENO) < 0) {
+            perror("dup2 stdout");
+            return (-1);
+        }
+        if(dup2(fd, STDERR_FILENO) < 0) {
+            perror("dup2 stderr");
+            return (-1);
+        }
+
+        if (fd > STDERR_FILENO) {
+            if(close(fd) < 0) {
+                perror("close");
+                return (-1);
+            }
+        }
+    }
+    return (0);
+}
diff --git a/cachelib/external/memcached/extstore.c b/cachelib/external/memcached/extstore.c
new file mode 100644
index 0000000000..e825667e42
--- /dev/null
+++ b/cachelib/external/memcached/extstore.c
@@ -0,0 +1,966 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+// FIXME: config.h?
+#include <stdint.h>
+#include <stdbool.h>
+// end FIXME
+#include <stdlib.h>
+#include <limits.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include "extstore.h"
+
+// TODO: better if an init option turns this on/off.
+#ifdef EXTSTORE_DEBUG
+#define E_DEBUG(...) \
+    do { \
+        fprintf(stderr, __VA_ARGS__); \
+    } while (0)
+#else
+#define E_DEBUG(...)
+#endif
+
+#define STAT_L(e) pthread_mutex_lock(&e->stats_mutex);
+#define STAT_UL(e) pthread_mutex_unlock(&e->stats_mutex);
+#define STAT_INCR(e, stat, amount) { \
+    pthread_mutex_lock(&e->stats_mutex); \
+    e->stats.stat += amount; \
+    pthread_mutex_unlock(&e->stats_mutex); \
+}
+
+#define STAT_DECR(e, stat, amount) { \
+    pthread_mutex_lock(&e->stats_mutex); \
+    e->stats.stat -= amount; \
+    pthread_mutex_unlock(&e->stats_mutex); \
+}
+
+typedef struct __store_wbuf {
+    struct __store_wbuf *next;
+    char *buf;
+    char *buf_pos;
+    unsigned int free;
+    unsigned int size;
+    unsigned int offset; /* offset into page this write starts at */
+    bool full; /* done writing to this page */
+    bool flushed; /* whether wbuf has been flushed to disk */
+} _store_wbuf;
+
+typedef struct _store_page {
+    pthread_mutex_t mutex; /* Need to be held for most operations */
+    uint64_t obj_count; /* _delete can decrease post-closing */
+    uint64_t bytes_used; /* _delete can decrease post-closing */
+    uint64_t offset; /* starting address of page within fd */
+    unsigned int version;
+    unsigned int refcount;
+    unsigned int allocated;
+    unsigned int written; /* item offsets can be past written if wbuf not flushed */
+    unsigned int bucket; /* which bucket the page is linked into */
+    unsigned int free_bucket; /* which bucket this page returns to when freed */
+    int fd;
+    unsigned short id;
+    bool active; /* actively being written to */
+    bool closed; /* closed and draining before free */
+    bool free; /* on freelist */
+    _store_wbuf *wbuf; /* currently active wbuf from the stack */
+    struct _store_page *next;
+} store_page;
+
+typedef struct store_engine store_engine;
+typedef struct {
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+    obj_io *queue;
+    store_engine *e;
+    unsigned int depth; // queue depth
+} store_io_thread;
+
+typedef struct {
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+    store_engine *e;
+} store_maint_thread;
+
+struct store_engine {
+    pthread_mutex_t mutex; /* covers internal stacks and variables */
+    store_page *pages; /* directly addressable page list */
+    _store_wbuf *wbuf_stack; /* wbuf freelist */
+    obj_io *io_stack; /* IO's to use with submitting wbuf's */
+    store_io_thread *io_threads;
+    store_maint_thread *maint_thread;
+    store_page *page_freelist;
+    store_page **page_buckets; /* stack of pages currently allocated to each bucket */
+    store_page **free_page_buckets; /* stack of use-case isolated free pages */
+    size_t page_size;
+    unsigned int version; /* global version counter */
+    unsigned int last_io_thread; /* round robin the IO threads */
+    unsigned int io_threadcount; /* count of IO threads */
+    unsigned int page_count;
+    unsigned int page_free; /* unallocated pages */
+    unsigned int page_bucketcount; /* count of potential page buckets */
+    unsigned int free_page_bucketcount; /* count of free page buckets */
+    unsigned int io_depth; /* FIXME: Might cache into thr struct */
+    pthread_mutex_t stats_mutex;
+    struct extstore_stats stats;
+};
+
+static _store_wbuf *wbuf_new(size_t size) {
+    _store_wbuf *b = calloc(1, sizeof(_store_wbuf));
+    if (b == NULL)
+        return NULL;
+    b->buf = malloc(size);
+    if (b->buf == NULL) {
+        free(b);
+        return NULL;
+    }
+    b->buf_pos = b->buf;
+    b->free = size;
+    b->size = size;
+    return b;
+}
+
+static store_io_thread *_get_io_thread(store_engine *e) {
+    int tid = -1;
+    long long int low = LLONG_MAX;
+    pthread_mutex_lock(&e->mutex);
+    // find smallest queue. ignoring lock since being wrong isn't fatal.
+    // TODO: if average queue depth can be quickly tracked, can break as soon
+    // as we see a thread that's less than average, and start from last_io_thread
+    for (int x = 0; x < e->io_threadcount; x++) {
+        if (e->io_threads[x].depth == 0) {
+            tid = x;
+            break;
+        } else if (e->io_threads[x].depth < low) {
+                tid = x;
+            low = e->io_threads[x].depth;
+        }
+    }
+    pthread_mutex_unlock(&e->mutex);
+
+    return &e->io_threads[tid];
+}
+
+static uint64_t _next_version(store_engine *e) {
+    return e->version++;
+}
+
+static void *extstore_io_thread(void *arg);
+static void *extstore_maint_thread(void *arg);
+
+/* Copies stats internal to engine and computes any derived values */
+void extstore_get_stats(void *ptr, struct extstore_stats *st) {
+    store_engine *e = (store_engine *)ptr;
+    STAT_L(e);
+    memcpy(st, &e->stats, sizeof(struct extstore_stats));
+    STAT_UL(e);
+
+    // grab pages_free/pages_used
+    pthread_mutex_lock(&e->mutex);
+    st->pages_free = e->page_free;
+    st->pages_used = e->page_count - e->page_free;
+    pthread_mutex_unlock(&e->mutex);
+    st->io_queue = 0;
+    for (int x = 0; x < e->io_threadcount; x++) {
+        pthread_mutex_lock(&e->io_threads[x].mutex);
+        st->io_queue += e->io_threads[x].depth;
+        pthread_mutex_unlock(&e->io_threads[x].mutex);
+    }
+    // calculate bytes_fragmented.
+    // note that open and yet-filled pages count against fragmentation.
+    st->bytes_fragmented = st->pages_used * e->page_size -
+        st->bytes_used;
+}
+
+void extstore_get_page_data(void *ptr, struct extstore_stats *st) {
+    store_engine *e = (store_engine *)ptr;
+    STAT_L(e);
+    memcpy(st->page_data, e->stats.page_data,
+            sizeof(struct extstore_page_data) * e->page_count);
+    STAT_UL(e);
+}
+
+const char *extstore_err(enum extstore_res res) {
+    const char *rv = "unknown error";
+    switch (res) {
+        case EXTSTORE_INIT_BAD_WBUF_SIZE:
+            rv = "page_size must be divisible by wbuf_size";
+            break;
+        case EXTSTORE_INIT_NEED_MORE_WBUF:
+            rv = "wbuf_count must be >= page_buckets";
+            break;
+        case EXTSTORE_INIT_NEED_MORE_BUCKETS:
+            rv = "page_buckets must be > 0";
+            break;
+        case EXTSTORE_INIT_PAGE_WBUF_ALIGNMENT:
+            rv = "page_size and wbuf_size must be divisible by 1024*1024*2";
+            break;
+        case EXTSTORE_INIT_TOO_MANY_PAGES:
+            rv = "page_count must total to < 65536. Increase page_size or lower path sizes";
+            break;
+        case EXTSTORE_INIT_OOM:
+            rv = "failed calloc for engine";
+            break;
+        case EXTSTORE_INIT_OPEN_FAIL:
+            rv = "failed to open file";
+            break;
+        case EXTSTORE_INIT_THREAD_FAIL:
+            break;
+    }
+    return rv;
+}
+
+// TODO: #define's for DEFAULT_BUCKET, FREE_VERSION, etc
+void *extstore_init(struct extstore_conf_file *fh, struct extstore_conf *cf,
+        enum extstore_res *res) {
+    int i;
+    struct extstore_conf_file *f = NULL;
+    pthread_t thread;
+
+    if (cf->page_size % cf->wbuf_size != 0) {
+        *res = EXTSTORE_INIT_BAD_WBUF_SIZE;
+        return NULL;
+    }
+    // Should ensure at least one write buffer per potential page
+    if (cf->page_buckets > cf->wbuf_count) {
+        *res = EXTSTORE_INIT_NEED_MORE_WBUF;
+        return NULL;
+    }
+    if (cf->page_buckets < 1) {
+        *res = EXTSTORE_INIT_NEED_MORE_BUCKETS;
+        return NULL;
+    }
+
+    // TODO: More intelligence around alignment of flash erasure block sizes
+    if (cf->page_size % (1024 * 1024 * 2) != 0 ||
+        cf->wbuf_size % (1024 * 1024 * 2) != 0) {
+        *res = EXTSTORE_INIT_PAGE_WBUF_ALIGNMENT;
+        return NULL;
+    }
+
+    store_engine *e = calloc(1, sizeof(store_engine));
+    if (e == NULL) {
+        *res = EXTSTORE_INIT_OOM;
+        return NULL;
+    }
+
+    e->page_size = cf->page_size;
+    uint64_t temp_page_count = 0;
+    for (f = fh; f != NULL; f = f->next) {
+        f->fd = open(f->file, O_RDWR | O_CREAT | O_TRUNC, 0644);
+        if (f->fd < 0) {
+            *res = EXTSTORE_INIT_OPEN_FAIL;
+#ifdef EXTSTORE_DEBUG
+            perror("open");
+#endif
+            free(e);
+            return NULL;
+        }
+        temp_page_count += f->page_count;
+        f->offset = 0;
+    }
+
+    if (temp_page_count >= UINT16_MAX) {
+        *res = EXTSTORE_INIT_TOO_MANY_PAGES;
+        free(e);
+        return NULL;
+    }
+    e->page_count = temp_page_count;
+
+    e->pages = calloc(e->page_count, sizeof(store_page));
+    if (e->pages == NULL) {
+        *res = EXTSTORE_INIT_OOM;
+        // FIXME: loop-close. make error label
+        free(e);
+        return NULL;
+    }
+
+    // interleave the pages between devices
+    f = NULL; // start at the first device.
+    for (i = 0; i < e->page_count; i++) {
+        // find next device with available pages
+        while (1) {
+            // restart the loop
+            if (f == NULL || f->next == NULL) {
+                f = fh;
+            } else {
+                f = f->next;
+            }
+            if (f->page_count) {
+                f->page_count--;
+                break;
+            }
+        }
+        pthread_mutex_init(&e->pages[i].mutex, NULL);
+        e->pages[i].id = i;
+        e->pages[i].fd = f->fd;
+        e->pages[i].free_bucket = f->free_bucket;
+        e->pages[i].offset = f->offset;
+        e->pages[i].free = true;
+        f->offset += e->page_size;
+    }
+
+    // free page buckets allows the app to organize devices by use case
+    e->free_page_buckets = calloc(cf->page_buckets, sizeof(store_page *));
+    e->page_bucketcount = cf->page_buckets;
+
+    for (i = e->page_count-1; i > 0; i--) {
+        e->page_free++;
+        if (e->pages[i].free_bucket == 0) {
+            e->pages[i].next = e->page_freelist;
+            e->page_freelist = &e->pages[i];
+        } else {
+            int fb = e->pages[i].free_bucket;
+            e->pages[i].next = e->free_page_buckets[fb];
+            e->free_page_buckets[fb] = &e->pages[i];
+        }
+    }
+
+    // 0 is magic "page is freed" version
+    e->version = 1;
+
+    // scratch data for stats. TODO: malloc failure handle
+    e->stats.page_data =
+        calloc(e->page_count, sizeof(struct extstore_page_data));
+    e->stats.page_count = e->page_count;
+    e->stats.page_size = e->page_size;
+
+    // page buckets lazily have pages assigned into them
+    e->page_buckets = calloc(cf->page_buckets, sizeof(store_page *));
+    e->page_bucketcount = cf->page_buckets;
+
+    // allocate write buffers
+    // also IO's to use for shipping to IO thread
+    for (i = 0; i < cf->wbuf_count; i++) {
+        _store_wbuf *w = wbuf_new(cf->wbuf_size);
+        obj_io *io = calloc(1, sizeof(obj_io));
+        /* TODO: on error, loop again and free stack. */
+        w->next = e->wbuf_stack;
+        e->wbuf_stack = w;
+        io->next = e->io_stack;
+        e->io_stack = io;
+    }
+
+    pthread_mutex_init(&e->mutex, NULL);
+    pthread_mutex_init(&e->stats_mutex, NULL);
+
+    e->io_depth = cf->io_depth;
+
+    // spawn threads
+    e->io_threads = calloc(cf->io_threadcount, sizeof(store_io_thread));
+    for (i = 0; i < cf->io_threadcount; i++) {
+        pthread_mutex_init(&e->io_threads[i].mutex, NULL);
+        pthread_cond_init(&e->io_threads[i].cond, NULL);
+        e->io_threads[i].e = e;
+        // FIXME: error handling
+        pthread_create(&thread, NULL, extstore_io_thread, &e->io_threads[i]);
+    }
+    e->io_threadcount = cf->io_threadcount;
+
+    e->maint_thread = calloc(1, sizeof(store_maint_thread));
+    e->maint_thread->e = e;
+    // FIXME: error handling
+    pthread_mutex_init(&e->maint_thread->mutex, NULL);
+    pthread_cond_init(&e->maint_thread->cond, NULL);
+    pthread_create(&thread, NULL, extstore_maint_thread, e->maint_thread);
+
+    extstore_run_maint(e);
+
+    return (void *)e;
+}
+
+void extstore_run_maint(void *ptr) {
+    store_engine *e = (store_engine *)ptr;
+    pthread_cond_signal(&e->maint_thread->cond);
+}
+
+// call with *e locked
+static store_page *_allocate_page(store_engine *e, unsigned int bucket,
+        unsigned int free_bucket) {
+    assert(!e->page_buckets[bucket] || e->page_buckets[bucket]->allocated == e->page_size);
+    store_page *tmp = NULL;
+    // if a specific free bucket was requested, check there first
+    if (free_bucket != 0 && e->free_page_buckets[free_bucket] != NULL) {
+        assert(e->page_free > 0);
+        tmp = e->free_page_buckets[free_bucket];
+        e->free_page_buckets[free_bucket] = tmp->next;
+    }
+    // failing that, try the global list.
+    if (tmp == NULL && e->page_freelist != NULL) {
+        tmp = e->page_freelist;
+        e->page_freelist = tmp->next;
+    }
+    E_DEBUG("EXTSTORE: allocating new page\n");
+    // page_freelist can be empty if the only free pages are specialized and
+    // we didn't just request one.
+    if (e->page_free > 0 && tmp != NULL) {
+        tmp->next = e->page_buckets[bucket];
+        e->page_buckets[bucket] = tmp;
+        tmp->active = true;
+        tmp->free = false;
+        tmp->closed = false;
+        tmp->version = _next_version(e);
+        tmp->bucket = bucket;
+        e->page_free--;
+        STAT_INCR(e, page_allocs, 1);
+    } else {
+        extstore_run_maint(e);
+    }
+    if (tmp)
+        E_DEBUG("EXTSTORE: got page %u\n", tmp->id);
+    return tmp;
+}
+
+// call with *p locked. locks *e
+static void _allocate_wbuf(store_engine *e, store_page *p) {
+    _store_wbuf *wbuf = NULL;
+    assert(!p->wbuf);
+    pthread_mutex_lock(&e->mutex);
+    if (e->wbuf_stack) {
+        wbuf = e->wbuf_stack;
+        e->wbuf_stack = wbuf->next;
+        wbuf->next = 0;
+    }
+    pthread_mutex_unlock(&e->mutex);
+    if (wbuf) {
+        wbuf->offset = p->allocated;
+        p->allocated += wbuf->size;
+        wbuf->free = wbuf->size;
+        wbuf->buf_pos = wbuf->buf;
+        wbuf->full = false;
+        wbuf->flushed = false;
+
+        p->wbuf = wbuf;
+    }
+}
+
+/* callback after wbuf is flushed. can only remove wbuf's from the head onward
+ * if successfully flushed, which complicates this routine. each callback
+ * attempts to free the wbuf stack, which is finally done when the head wbuf's
+ * callback happens.
+ * It's rare flushes would happen out of order.
+ */
+static void _wbuf_cb(void *ep, obj_io *io, int ret) {
+    store_engine *e = (store_engine *)ep;
+    store_page *p = &e->pages[io->page_id];
+    _store_wbuf *w = (_store_wbuf *) io->data;
+
+    // TODO: Examine return code. Not entirely sure how to handle errors.
+    // Naive first-pass should probably cause the page to close/free.
+    w->flushed = true;
+    pthread_mutex_lock(&p->mutex);
+    assert(p->wbuf != NULL && p->wbuf == w);
+    assert(p->written == w->offset);
+    p->written += w->size;
+    p->wbuf = NULL;
+
+    if (p->written == e->page_size)
+        p->active = false;
+
+    // return the wbuf
+    pthread_mutex_lock(&e->mutex);
+    w->next = e->wbuf_stack;
+    e->wbuf_stack = w;
+    // also return the IO we just used.
+    io->next = e->io_stack;
+    e->io_stack = io;
+    pthread_mutex_unlock(&e->mutex);
+    pthread_mutex_unlock(&p->mutex);
+}
+
+/* Wraps pages current wbuf in an io and submits to IO thread.
+ * Called with p locked, locks e.
+ */
+static void _submit_wbuf(store_engine *e, store_page *p) {
+    _store_wbuf *w;
+    pthread_mutex_lock(&e->mutex);
+    obj_io *io = e->io_stack;
+    e->io_stack = io->next;
+    pthread_mutex_unlock(&e->mutex);
+    w = p->wbuf;
+
+    // zero out the end of the wbuf to allow blind readback of data.
+    memset(w->buf + (w->size - w->free), 0, w->free);
+
+    io->next = NULL;
+    io->mode = OBJ_IO_WRITE;
+    io->page_id = p->id;
+    io->data = w;
+    io->offset = w->offset;
+    io->len = w->size;
+    io->buf = w->buf;
+    io->cb = _wbuf_cb;
+
+    extstore_submit(e, io);
+}
+
+/* engine write function; takes engine, item_io.
+ * fast fail if no available write buffer (flushing)
+ * lock engine context, find active page, unlock
+ * if page full, submit page/buffer to io thread.
+ *
+ * write is designed to be flaky; if page full, caller must try again to get
+ * new page. best if used from a background thread that can harmlessly retry.
+ */
+
+int extstore_write_request(void *ptr, unsigned int bucket,
+        unsigned int free_bucket, obj_io *io) {
+    store_engine *e = (store_engine *)ptr;
+    store_page *p;
+    int ret = -1;
+    if (bucket >= e->page_bucketcount)
+        return ret;
+
+    pthread_mutex_lock(&e->mutex);
+    p = e->page_buckets[bucket];
+    if (!p) {
+        p = _allocate_page(e, bucket, free_bucket);
+    }
+    pthread_mutex_unlock(&e->mutex);
+    if (!p)
+        return ret;
+
+    pthread_mutex_lock(&p->mutex);
+
+    // FIXME: can't null out page_buckets!!!
+    // page is full, clear bucket and retry later.
+    if (!p->active ||
+            ((!p->wbuf || p->wbuf->full) && p->allocated >= e->page_size)) {
+        pthread_mutex_unlock(&p->mutex);
+        pthread_mutex_lock(&e->mutex);
+        _allocate_page(e, bucket, free_bucket);
+        pthread_mutex_unlock(&e->mutex);
+        return ret;
+    }
+
+    // if io won't fit, submit IO for wbuf and find new one.
+    if (p->wbuf && p->wbuf->free < io->len && !p->wbuf->full) {
+        _submit_wbuf(e, p);
+        p->wbuf->full = true;
+    }
+
+    if (!p->wbuf && p->allocated < e->page_size) {
+        _allocate_wbuf(e, p);
+    }
+
+    // hand over buffer for caller to copy into
+    // leaves p locked.
+    if (p->wbuf && !p->wbuf->full && p->wbuf->free >= io->len) {
+        io->buf = p->wbuf->buf_pos;
+        io->page_id = p->id;
+        return 0;
+    }
+
+    pthread_mutex_unlock(&p->mutex);
+    // p->written is incremented post-wbuf flush
+    return ret;
+}
+
+/* _must_ be called after a successful write_request.
+ * fills the rest of io structure.
+ */
+void extstore_write(void *ptr, obj_io *io) {
+    store_engine *e = (store_engine *)ptr;
+    store_page *p = &e->pages[io->page_id];
+
+    io->offset = p->wbuf->offset + (p->wbuf->size - p->wbuf->free);
+    io->page_version = p->version;
+    p->wbuf->buf_pos += io->len;
+    p->wbuf->free -= io->len;
+    p->bytes_used += io->len;
+    p->obj_count++;
+    STAT_L(e);
+    e->stats.bytes_written += io->len;
+    e->stats.bytes_used += io->len;
+    e->stats.objects_written++;
+    e->stats.objects_used++;
+    STAT_UL(e);
+
+    pthread_mutex_unlock(&p->mutex);
+}
+
+/* engine submit function; takes engine, item_io stack.
+ * lock io_thread context and add stack?
+ * signal io thread to wake.
+ * return success.
+ */
+int extstore_submit(void *ptr, obj_io *io) {
+    store_engine *e = (store_engine *)ptr;
+    store_io_thread *t = _get_io_thread(e);
+
+    pthread_mutex_lock(&t->mutex);
+    if (t->queue == NULL) {
+        t->queue = io;
+    } else {
+        /* Have to put the *io stack at the end of current queue.
+         * FIXME: Optimize by tracking tail.
+         */
+        obj_io *tmp = t->queue;
+        while (tmp->next != NULL) {
+            tmp = tmp->next;
+            assert(tmp != t->queue);
+        }
+        tmp->next = io;
+    }
+    // TODO: extstore_submit(ptr, io, count)
+    obj_io *tio = io;
+    while (tio != NULL) {
+        t->depth++;
+        tio = tio->next;
+    }
+    pthread_mutex_unlock(&t->mutex);
+
+    //pthread_mutex_lock(&t->mutex);
+    pthread_cond_signal(&t->cond);
+    //pthread_mutex_unlock(&t->mutex);
+    return 0;
+}
+
+/* engine note delete function: takes engine, page id, size?
+ * note that an item in this page is no longer valid
+ */
+int extstore_delete(void *ptr, unsigned int page_id, uint64_t page_version,
+        unsigned int count, unsigned int bytes) {
+    store_engine *e = (store_engine *)ptr;
+    // FIXME: validate page_id in bounds
+    store_page *p = &e->pages[page_id];
+    int ret = 0;
+
+    pthread_mutex_lock(&p->mutex);
+    if (!p->closed && p->version == page_version) {
+        if (p->bytes_used >= bytes) {
+            p->bytes_used -= bytes;
+        } else {
+            p->bytes_used = 0;
+        }
+
+        if (p->obj_count >= count) {
+            p->obj_count -= count;
+        } else {
+            p->obj_count = 0; // caller has bad accounting?
+        }
+        STAT_L(e);
+        e->stats.bytes_used -= bytes;
+        e->stats.objects_used -= count;
+        STAT_UL(e);
+
+        if (p->obj_count == 0) {
+            extstore_run_maint(e);
+        }
+    } else {
+        ret = -1;
+    }
+    pthread_mutex_unlock(&p->mutex);
+    return ret;
+}
+
+int extstore_check(void *ptr, unsigned int page_id, uint64_t page_version) {
+    store_engine *e = (store_engine *)ptr;
+    store_page *p = &e->pages[page_id];
+    int ret = 0;
+
+    pthread_mutex_lock(&p->mutex);
+    if (p->version != page_version)
+        ret = -1;
+    pthread_mutex_unlock(&p->mutex);
+    return ret;
+}
+
+/* allows a compactor to say "we're done with this page, kill it. */
+void extstore_close_page(void *ptr, unsigned int page_id, uint64_t page_version) {
+    store_engine *e = (store_engine *)ptr;
+    store_page *p = &e->pages[page_id];
+
+    pthread_mutex_lock(&p->mutex);
+    if (!p->closed && p->version == page_version) {
+        p->closed = true;
+        extstore_run_maint(e);
+    }
+    pthread_mutex_unlock(&p->mutex);
+}
+
+/* Finds an attached wbuf that can satisfy the read.
+ * Since wbufs can potentially be flushed to disk out of order, they are only
+ * removed as the head of the list successfully flushes to disk.
+ */
+// call with *p locked
+// FIXME: protect from reading past wbuf
+static inline int _read_from_wbuf(store_page *p, obj_io *io) {
+    _store_wbuf *wbuf = p->wbuf;
+    assert(wbuf != NULL);
+    assert(io->offset < p->written + wbuf->size);
+    if (io->iov == NULL) {
+        memcpy(io->buf, wbuf->buf + (io->offset - wbuf->offset), io->len);
+    } else {
+        int x;
+        unsigned int off = io->offset - wbuf->offset;
+        // need to loop fill iovecs
+        for (x = 0; x < io->iovcnt; x++) {
+            struct iovec *iov = &io->iov[x];
+            memcpy(iov->iov_base, wbuf->buf + off, iov->iov_len);
+            off += iov->iov_len;
+        }
+    }
+    return io->len;
+}
+
+/* engine IO thread; takes engine context
+ * manage writes/reads
+ * runs IO callbacks inline after each IO
+ */
+// FIXME: protect from reading past page
+static void *extstore_io_thread(void *arg) {
+    store_io_thread *me = (store_io_thread *)arg;
+    store_engine *e = me->e;
+    while (1) {
+        obj_io *io_stack = NULL;
+        pthread_mutex_lock(&me->mutex);
+        if (me->queue == NULL) {
+            pthread_cond_wait(&me->cond, &me->mutex);
+        }
+
+        // Pull and disconnect a batch from the queue
+        if (me->queue != NULL) {
+            int i;
+            obj_io *end = NULL;
+            io_stack = me->queue;
+            end = io_stack;
+            for (i = 1; i < e->io_depth; i++) {
+                if (end->next) {
+                    end = end->next;
+                } else {
+                    break;
+                }
+            }
+            me->depth -= i;
+            me->queue = end->next;
+            end->next = NULL;
+        }
+        pthread_mutex_unlock(&me->mutex);
+
+        obj_io *cur_io = io_stack;
+        while (cur_io) {
+            // We need to note next before the callback in case the obj_io
+            // gets reused.
+            obj_io *next = cur_io->next;
+            int ret = 0;
+            int do_op = 1;
+            store_page *p = &e->pages[cur_io->page_id];
+            // TODO: loop if not enough bytes were read/written.
+            switch (cur_io->mode) {
+                case OBJ_IO_READ:
+                    // Page is currently open. deal if read is past the end.
+                    pthread_mutex_lock(&p->mutex);
+                    if (!p->free && !p->closed && p->version == cur_io->page_version) {
+                        if (p->active && cur_io->offset >= p->written) {
+                            ret = _read_from_wbuf(p, cur_io);
+                            do_op = 0;
+                        } else {
+                            p->refcount++;
+                        }
+                        STAT_L(e);
+                        e->stats.bytes_read += cur_io->len;
+                        e->stats.objects_read++;
+                        STAT_UL(e);
+                    } else {
+                        do_op = 0;
+                        ret = -2; // TODO: enum in IO for status?
+                    }
+                    pthread_mutex_unlock(&p->mutex);
+                    if (do_op) {
+#ifdef __APPLE__
+                        ret = lseek(p->fd, SEEK_SET, p->offset + cur_io->offset);
+                        if (ret >= 0) {
+                            if (cur_io->iov == NULL) {
+                                ret = read(p->fd, cur_io->buf, cur_io->len);
+                            } else {
+                                ret = readv(p->fd, cur_io->iov, cur_io->iovcnt);
+                            }
+                        }
+#else
+                        if (cur_io->iov == NULL) {
+                            ret = pread(p->fd, cur_io->buf, cur_io->len, p->offset + cur_io->offset);
+                        } else {
+                            ret = preadv(p->fd, cur_io->iov, cur_io->iovcnt, p->offset + cur_io->offset);
+                        }
+#endif
+                    }
+                    break;
+                case OBJ_IO_WRITE:
+                    do_op = 0;
+                    // FIXME: Should hold refcount during write. doesn't
+                    // currently matter since page can't free while active.
+                    ret = pwrite(p->fd, cur_io->buf, cur_io->len, p->offset + cur_io->offset);
+                    break;
+            }
+            if (ret == 0) {
+                E_DEBUG("read returned nothing\n");
+            }
+
+#ifdef EXTSTORE_DEBUG
+            if (ret == -1) {
+                perror("read/write op failed");
+            }
+#endif
+            cur_io->cb(e, cur_io, ret);
+            if (do_op) {
+                pthread_mutex_lock(&p->mutex);
+                p->refcount--;
+                pthread_mutex_unlock(&p->mutex);
+            }
+            cur_io = next;
+        }
+    }
+
+    return NULL;
+}
+
+// call with *p locked.
+static void _free_page(store_engine *e, store_page *p) {
+    store_page *tmp = NULL;
+    store_page *prev = NULL;
+    E_DEBUG("EXTSTORE: freeing page %u\n", p->id);
+    STAT_L(e);
+    e->stats.objects_used -= p->obj_count;
+    e->stats.bytes_used -= p->bytes_used;
+    e->stats.page_reclaims++;
+    STAT_UL(e);
+    pthread_mutex_lock(&e->mutex);
+    // unlink page from bucket list
+    tmp = e->page_buckets[p->bucket];
+    while (tmp) {
+        if (tmp == p) {
+            if (prev) {
+                prev->next = tmp->next;
+            } else {
+                e->page_buckets[p->bucket] = tmp->next;
+            }
+            tmp->next = NULL;
+            break;
+        }
+        prev = tmp;
+        tmp = tmp->next;
+    }
+    // reset most values
+    p->version = 0;
+    p->obj_count = 0;
+    p->bytes_used = 0;
+    p->allocated = 0;
+    p->written = 0;
+    p->bucket = 0;
+    p->active = false;
+    p->closed = false;
+    p->free = true;
+    // add to page stack
+    // TODO: free_page_buckets first class and remove redundancy?
+    if (p->free_bucket != 0) {
+        p->next = e->free_page_buckets[p->free_bucket];
+        e->free_page_buckets[p->free_bucket] = p;
+    } else {
+        p->next = e->page_freelist;
+        e->page_freelist = p;
+    }
+    e->page_free++;
+    pthread_mutex_unlock(&e->mutex);
+}
+
+/* engine maint thread; takes engine context.
+ * Uses version to ensure oldest possible objects are being evicted.
+ * Needs interface to inform owner of pages with fewer objects or most space
+ * free, which can then be actively compacted to avoid eviction.
+ *
+ * This gets called asynchronously after every page allocation. Could run less
+ * often if more pages are free.
+ *
+ * Another allocation call is required if an attempted free didn't happen
+ * due to the page having a refcount.
+ */
+
+// TODO: Don't over-evict pages if waiting on refcounts to drop
+static void *extstore_maint_thread(void *arg) {
+    store_maint_thread *me = (store_maint_thread *)arg;
+    store_engine *e = me->e;
+    struct extstore_page_data *pd =
+        calloc(e->page_count, sizeof(struct extstore_page_data));
+    pthread_mutex_lock(&me->mutex);
+    while (1) {
+        int i;
+        bool do_evict = false;
+        unsigned int low_page = 0;
+        uint64_t low_version = ULLONG_MAX;
+
+        pthread_cond_wait(&me->cond, &me->mutex);
+        pthread_mutex_lock(&e->mutex);
+        // default freelist requires at least one page free.
+        // specialized freelists fall back to default once full.
+        if (e->page_free == 0 || e->page_freelist == NULL) {
+            do_evict = true;
+        }
+        pthread_mutex_unlock(&e->mutex);
+        memset(pd, 0, sizeof(struct extstore_page_data) * e->page_count);
+
+        for (i = 0; i < e->page_count; i++) {
+            store_page *p = &e->pages[i];
+            pthread_mutex_lock(&p->mutex);
+            pd[p->id].free_bucket = p->free_bucket;
+            if (p->active || p->free) {
+                pthread_mutex_unlock(&p->mutex);
+                continue;
+            }
+            if (p->obj_count > 0 && !p->closed) {
+                pd[p->id].version = p->version;
+                pd[p->id].bytes_used = p->bytes_used;
+                pd[p->id].bucket = p->bucket;
+                // low_version/low_page are only used in the eviction
+                // scenario. when we evict, it's only to fill the default page
+                // bucket again.
+                // TODO: experiment with allowing evicting up to a single page
+                // for any specific free bucket. this is *probably* required
+                // since it could cause a load bias on default-only devices?
+                if (p->free_bucket == 0 && p->version < low_version) {
+                    low_version = p->version;
+                    low_page = i;
+                }
+            }
+            if ((p->obj_count == 0 || p->closed) && p->refcount == 0) {
+                _free_page(e, p);
+                // Found a page to free, no longer need to evict.
+                do_evict = false;
+            }
+            pthread_mutex_unlock(&p->mutex);
+        }
+
+        if (do_evict && low_version != ULLONG_MAX) {
+            store_page *p = &e->pages[low_page];
+            E_DEBUG("EXTSTORE: evicting page [%d] [v: %llu]\n",
+                    p->id, (unsigned long long) p->version);
+            pthread_mutex_lock(&p->mutex);
+            if (!p->closed) {
+                p->closed = true;
+                STAT_L(e);
+                e->stats.page_evictions++;
+                e->stats.objects_evicted += p->obj_count;
+                e->stats.bytes_evicted += p->bytes_used;
+                STAT_UL(e);
+                if (p->refcount == 0) {
+                    _free_page(e, p);
+                }
+            }
+            pthread_mutex_unlock(&p->mutex);
+        }
+
+        // copy the page data into engine context so callers can use it from
+        // the stats lock.
+        STAT_L(e);
+        memcpy(e->stats.page_data, pd,
+                sizeof(struct extstore_page_data) * e->page_count);
+        STAT_UL(e);
+    }
+
+    return NULL;
+}
diff --git a/cachelib/external/memcached/extstore.h b/cachelib/external/memcached/extstore.h
new file mode 100644
index 0000000000..bf0b0a10b9
--- /dev/null
+++ b/cachelib/external/memcached/extstore.h
@@ -0,0 +1,122 @@
+#ifndef EXTSTORE_H
+#define EXTSTORE_H
+
+/* A safe-to-read dataset for determining compaction.
+ * id is the array index.
+ */
+struct extstore_page_data {
+    uint64_t version;
+    uint64_t bytes_used;
+    unsigned int bucket;
+    unsigned int free_bucket;
+};
+
+/* Pages can have objects deleted from them at any time. This creates holes
+ * that can't be reused until the page is either evicted or all objects are
+ * deleted.
+ * bytes_fragmented is the total bytes for all of these holes.
+ * It is the size of all used pages minus each page's bytes_used value.
+ */
+struct extstore_stats {
+    uint64_t page_allocs;
+    uint64_t page_count; /* total page count */
+    uint64_t page_evictions;
+    uint64_t page_reclaims;
+    uint64_t page_size; /* size in bytes per page (supplied by caller) */
+    uint64_t pages_free; /* currently unallocated/unused pages */
+    uint64_t pages_used;
+    uint64_t objects_evicted;
+    uint64_t objects_read;
+    uint64_t objects_written;
+    uint64_t objects_used; /* total number of objects stored */
+    uint64_t bytes_evicted;
+    uint64_t bytes_written;
+    uint64_t bytes_read; /* wbuf - read -> bytes read from storage */
+    uint64_t bytes_used; /* total number of bytes stored */
+    uint64_t bytes_fragmented; /* see above comment */
+    uint64_t io_queue;
+    struct extstore_page_data *page_data;
+};
+
+// TODO: Temporary configuration structure. A "real" library should have an
+// extstore_set(enum, void *ptr) which hides the implementation.
+// this is plenty for quick development.
+struct extstore_conf {
+    unsigned int page_size; // ideally 64-256M in size
+    unsigned int page_count;
+    unsigned int page_buckets; // number of different writeable pages
+    unsigned int free_page_buckets; // buckets of dedicated pages (see code)
+    unsigned int wbuf_size; // must divide cleanly into page_size
+    unsigned int wbuf_count; // this might get locked to "2 per active page"
+    unsigned int io_threadcount;
+    unsigned int io_depth; // with normal I/O, hits locks less. req'd for AIO
+};
+
+struct extstore_conf_file {
+    unsigned int page_count;
+    char *file;
+    int fd; // internal usage
+    uint64_t offset; // internal usage
+    unsigned int bucket; // free page bucket
+    unsigned int free_bucket; // specialized free bucket
+    struct extstore_conf_file *next;
+};
+
+enum obj_io_mode {
+    OBJ_IO_READ = 0,
+    OBJ_IO_WRITE,
+};
+
+typedef struct _obj_io obj_io;
+typedef void (*obj_io_cb)(void *e, obj_io *io, int ret);
+
+/* An object for both reads and writes to the storage engine.
+ * Once an IO is submitted, ->next may be changed by the IO thread. It is not
+ * safe to further modify the IO stack until the entire request is completed.
+ */
+struct _obj_io {
+    void *data; /* user supplied data pointer */
+    struct _obj_io *next;
+    char *buf;  /* buffer of data to read or write to */
+    struct iovec *iov; /* alternatively, use this iovec */
+    unsigned int iovcnt; /* number of IOV's */
+    unsigned int page_version;     /* page version for read mode */
+    unsigned int len;     /* for both modes */
+    unsigned int offset;  /* for read mode */
+    unsigned short page_id; /* for read mode */
+    enum obj_io_mode mode;
+    /* callback pointers? */
+    obj_io_cb cb;
+};
+
+enum extstore_res {
+    EXTSTORE_INIT_BAD_WBUF_SIZE = 1,
+    EXTSTORE_INIT_NEED_MORE_WBUF,
+    EXTSTORE_INIT_NEED_MORE_BUCKETS,
+    EXTSTORE_INIT_PAGE_WBUF_ALIGNMENT,
+    EXTSTORE_INIT_TOO_MANY_PAGES,
+    EXTSTORE_INIT_OOM,
+    EXTSTORE_INIT_OPEN_FAIL,
+    EXTSTORE_INIT_THREAD_FAIL
+};
+
+const char *extstore_err(enum extstore_res res);
+void *extstore_init(struct extstore_conf_file *fh, struct extstore_conf *cf, enum extstore_res *res);
+int extstore_write_request(void *ptr, unsigned int bucket, unsigned int free_bucket, obj_io *io);
+void extstore_write(void *ptr, obj_io *io);
+int extstore_submit(void *ptr, obj_io *io);
+/* count are the number of objects being removed, bytes are the original
+ * length of those objects. Bytes is optional but you can't track
+ * fragmentation without it.
+ */
+int extstore_check(void *ptr, unsigned int page_id, uint64_t page_version);
+int extstore_delete(void *ptr, unsigned int page_id, uint64_t page_version, unsigned int count, unsigned int bytes);
+void extstore_get_stats(void *ptr, struct extstore_stats *st);
+/* add page data array to a stats structure.
+ * caller must allocate its stats.page_data memory first.
+ */
+void extstore_get_page_data(void *ptr, struct extstore_stats *st);
+void extstore_run_maint(void *ptr);
+void extstore_close_page(void *ptr, unsigned int page_id, uint64_t page_version);
+
+#endif
diff --git a/cachelib/external/memcached/freebsd_priv.c b/cachelib/external/memcached/freebsd_priv.c
new file mode 100644
index 0000000000..5d9fa57545
--- /dev/null
+++ b/cachelib/external/memcached/freebsd_priv.c
@@ -0,0 +1,49 @@
+#include <sys/capsicum.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "memcached.h"
+
+/*
+ * dropping privileges is entering in capability mode
+ * in FreeBSD vocabulary.
+ */
+void drop_privileges() {
+    cap_rights_t wd, rd;
+
+    if (cap_rights_init(&wd, CAP_WRITE, CAP_READ) == NULL) {
+        fprintf(stderr, "cap_rights_init write protection failed: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    if (cap_rights_init(&rd, CAP_FCNTL, CAP_READ, CAP_EVENT) == NULL) {
+        fprintf(stderr, "cap_rights_init read protection failed: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    if (cap_rights_limit(STDIN_FILENO, &rd) != 0) {
+        fprintf(stderr, "cap_rights_limit stdin failed: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    if (cap_rights_limit(STDOUT_FILENO, &wd) != 0) {
+        fprintf(stderr, "cap_rights_limit stdout failed: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    if (cap_rights_limit(STDERR_FILENO, &wd) != 0) {
+        fprintf(stderr, "cap_rights_limit stderr failed: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    if (cap_enter() != 0) {
+        fprintf(stderr, "cap_enter failed: %s\n", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+}
+
+void setup_privilege_violations_handler(void) {
+   // not needed
+}
diff --git a/cachelib/external/memcached/globals.c b/cachelib/external/memcached/globals.c
new file mode 100644
index 0000000000..1a6d04c692
--- /dev/null
+++ b/cachelib/external/memcached/globals.c
@@ -0,0 +1,26 @@
+#include "memcached.h"
+
+/*
+ * This file contains global variables shared across the rest of the
+ * memcached codebase.  These were originally in memcached.c but had
+ * to be removed to make the rest of the object files linkable into
+ * the test infrastructure.
+ *
+ */
+
+/*
+ * We keep the current time of day in a global variable that's updated by a
+ * timer event. This saves us a bunch of time() system calls (we really only
+ * need to get the time once a second, whereas there can be tens of thousands
+ * of requests a second) and allows us to use server-start-relative timestamps
+ * rather than absolute UNIX timestamps, a space savings on systems where
+ * sizeof(time_t) > sizeof(unsigned int).
+ */
+volatile rel_time_t current_time;
+
+/** exported globals **/
+struct stats stats;
+struct stats_state stats_state;
+struct settings settings;
+struct slab_rebalance slab_rebal;
+volatile int slab_rebalance_signal;
diff --git a/cachelib/external/memcached/hash.c b/cachelib/external/memcached/hash.c
new file mode 100644
index 0000000000..b5ff28ae4b
--- /dev/null
+++ b/cachelib/external/memcached/hash.c
@@ -0,0 +1,23 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+#include "memcached.h"
+#include "jenkins_hash.h"
+#include "murmur3_hash.h"
+
+hash_func hash;
+
+int hash_init(enum hashfunc_type type) {
+    switch(type) {
+        case JENKINS_HASH:
+            hash = jenkins_hash;
+            settings.hash_algorithm = "jenkins";
+            break;
+        case MURMUR3_HASH:
+            hash = MurmurHash3_x86_32;
+            settings.hash_algorithm = "murmur3";
+            break;
+        default:
+            return -1;
+    }
+    return 0;
+}
diff --git a/cachelib/external/memcached/hash.h b/cachelib/external/memcached/hash.h
new file mode 100644
index 0000000000..3b2a984782
--- /dev/null
+++ b/cachelib/external/memcached/hash.h
@@ -0,0 +1,14 @@
+#ifndef HASH_H
+#define    HASH_H
+
+typedef uint32_t (*hash_func)(const void *key, size_t length);
+extern hash_func hash;
+
+enum hashfunc_type {
+    JENKINS_HASH=0, MURMUR3_HASH
+};
+
+int hash_init(enum hashfunc_type type);
+
+#endif    /* HASH_H */
+
diff --git a/cachelib/external/memcached/items.c b/cachelib/external/memcached/items.c
new file mode 100644
index 0000000000..d67f9d2a46
--- /dev/null
+++ b/cachelib/external/memcached/items.c
@@ -0,0 +1,1827 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "config.h"
+#include "memcached.h"
+#include "bipbuffer.h"
+#include "slab_automove.h"
+#ifdef EXTSTORE
+#include "storage.h"
+#include "slab_automove_extstore.h"
+#endif
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <unistd.h>
+#include <poll.h>
+
+/* Forward Declarations */
+static void item_link_q(item *it);
+static void item_unlink_q(item *it);
+
+static unsigned int lru_type_map[4] = {HOT_LRU, WARM_LRU, COLD_LRU, TEMP_LRU};
+
+#define LARGEST_ID POWER_LARGEST
+typedef struct {
+    uint64_t evicted;
+    uint64_t evicted_nonzero;
+    uint64_t reclaimed;
+    uint64_t outofmemory;
+    uint64_t tailrepairs;
+    uint64_t expired_unfetched; /* items reclaimed but never touched */
+    uint64_t evicted_unfetched; /* items evicted but never touched */
+    uint64_t evicted_active; /* items evicted that should have been shuffled */
+    uint64_t crawler_reclaimed;
+    uint64_t crawler_items_checked;
+    uint64_t lrutail_reflocked;
+    uint64_t moves_to_cold;
+    uint64_t moves_to_warm;
+    uint64_t moves_within_lru;
+    uint64_t direct_reclaims;
+    uint64_t hits_to_hot;
+    uint64_t hits_to_warm;
+    uint64_t hits_to_cold;
+    uint64_t hits_to_temp;
+    uint64_t mem_requested;
+    rel_time_t evicted_time;
+} itemstats_t;
+
+static item *heads[LARGEST_ID];
+static item *tails[LARGEST_ID];
+static itemstats_t itemstats[LARGEST_ID];
+static unsigned int sizes[LARGEST_ID];
+static uint64_t sizes_bytes[LARGEST_ID];
+static unsigned int *stats_sizes_hist = NULL;
+static uint64_t stats_sizes_cas_min = 0;
+static int stats_sizes_buckets = 0;
+static uint64_t cas_id = 0;
+
+static volatile int do_run_lru_maintainer_thread = 0;
+static int lru_maintainer_initialized = 0;
+static pthread_mutex_t lru_maintainer_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t cas_id_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t stats_sizes_lock = PTHREAD_MUTEX_INITIALIZER;
+
+void item_stats_reset(void) {
+    int i;
+    for (i = 0; i < LARGEST_ID; i++) {
+        pthread_mutex_lock(&lru_locks[i]);
+        memset(&itemstats[i], 0, sizeof(itemstats_t));
+        pthread_mutex_unlock(&lru_locks[i]);
+    }
+}
+
+/* called with class lru lock held */
+void do_item_stats_add_crawl(const int i, const uint64_t reclaimed,
+        const uint64_t unfetched, const uint64_t checked) {
+    itemstats[i].crawler_reclaimed += reclaimed;
+    itemstats[i].expired_unfetched += unfetched;
+    itemstats[i].crawler_items_checked += checked;
+}
+
+typedef struct _lru_bump_buf {
+    struct _lru_bump_buf *prev;
+    struct _lru_bump_buf *next;
+    pthread_mutex_t mutex;
+    bipbuf_t *buf;
+    uint64_t dropped;
+} lru_bump_buf;
+
+typedef struct {
+    item *it;
+    uint32_t hv;
+} lru_bump_entry;
+
+static lru_bump_buf *bump_buf_head = NULL;
+static lru_bump_buf *bump_buf_tail = NULL;
+static pthread_mutex_t bump_buf_lock = PTHREAD_MUTEX_INITIALIZER;
+/* TODO: tunable? Need bench results */
+#define LRU_BUMP_BUF_SIZE 8192
+
+static bool lru_bump_async(lru_bump_buf *b, item *it, uint32_t hv);
+static uint64_t lru_total_bumps_dropped(void);
+
+/* Get the next CAS id for a new item. */
+/* TODO: refactor some atomics for this. */
+uint64_t get_cas_id(void) {
+    pthread_mutex_lock(&cas_id_lock);
+    uint64_t next_id = ++cas_id;
+    pthread_mutex_unlock(&cas_id_lock);
+    return next_id;
+}
+
+void set_cas_id(uint64_t new_cas) {
+    pthread_mutex_lock(&cas_id_lock);
+    cas_id = new_cas;
+    pthread_mutex_unlock(&cas_id_lock);
+}
+
+int item_is_flushed(item *it) {
+    rel_time_t oldest_live = settings.oldest_live;
+    uint64_t cas = ITEM_get_cas(it);
+    uint64_t oldest_cas = settings.oldest_cas;
+    if (oldest_live == 0 || oldest_live > current_time)
+        return 0;
+    if ((it->time <= oldest_live)
+            || (oldest_cas != 0 && cas != 0 && cas < oldest_cas)) {
+        return 1;
+    }
+    return 0;
+}
+
+/* must be locked before call */
+unsigned int do_get_lru_size(uint32_t id) {
+    return sizes[id];
+}
+
+/* Enable this for reference-count debugging. */
+#if 0
+# define DEBUG_REFCNT(it,op) \
+                fprintf(stderr, "item %x refcnt(%c) %d %c%c%c\n", \
+                        it, op, it->refcount, \
+                        (it->it_flags & ITEM_LINKED) ? 'L' : ' ', \
+                        (it->it_flags & ITEM_SLABBED) ? 'S' : ' ')
+#else
+# define DEBUG_REFCNT(it,op) while(0)
+#endif
+
+/**
+ * Generates the variable-sized part of the header for an object.
+ *
+ * nkey    - The length of the key
+ * flags   - key flags
+ * nbytes  - Number of bytes to hold value and addition CRLF terminator
+ * suffix  - Buffer for the "VALUE" line suffix (flags, size).
+ * nsuffix - The length of the suffix is stored here.
+ *
+ * Returns the total size of the header.
+ */
+static size_t item_make_header(const uint8_t nkey, const unsigned int flags, const int nbytes,
+                     char *suffix, uint8_t *nsuffix) {
+    if (flags == 0) {
+        *nsuffix = 0;
+    } else {
+        *nsuffix = sizeof(flags);
+    }
+    return sizeof(item) + nkey + *nsuffix + nbytes;
+}
+
+item *do_item_alloc_pull(const size_t ntotal, const unsigned int id) {
+    item *it = NULL;
+    int i;
+    /* If no memory is available, attempt a direct LRU juggle/eviction */
+    /* This is a race in order to simplify lru_pull_tail; in cases where
+     * locked items are on the tail, you want them to fall out and cause
+     * occasional OOM's, rather than internally work around them.
+     * This also gives one fewer code path for slab alloc/free
+     */
+    for (i = 0; i < 10; i++) {
+        /* Try to reclaim memory first */
+        if (!settings.lru_segmented) {
+            lru_pull_tail(id, COLD_LRU, 0, 0, 0, NULL);
+        }
+        it = slabs_alloc(ntotal, id, 0);
+
+        if (it == NULL) {
+            // We send '0' in for "total_bytes" as this routine is always
+            // pulling to evict, or forcing HOT -> COLD migration.
+            // As of this writing, total_bytes isn't at all used with COLD_LRU.
+            if (lru_pull_tail(id, COLD_LRU, 0, LRU_PULL_EVICT, 0, NULL) <= 0) {
+                if (settings.lru_segmented) {
+                    lru_pull_tail(id, HOT_LRU, 0, 0, 0, NULL);
+                } else {
+                    break;
+                }
+            }
+        } else {
+            break;
+        }
+    }
+
+    if (i > 0) {
+        pthread_mutex_lock(&lru_locks[id]);
+        itemstats[id].direct_reclaims += i;
+        pthread_mutex_unlock(&lru_locks[id]);
+    }
+
+    return it;
+}
+
+/* Chain another chunk onto this chunk. */
+/* slab mover: if it finds a chunk without ITEM_CHUNK flag, and no ITEM_LINKED
+ * flag, it counts as busy and skips.
+ * I think it might still not be safe to do linking outside of the slab lock
+ */
+item_chunk *do_item_alloc_chunk(item_chunk *ch, const size_t bytes_remain) {
+    // TODO: Should be a cleaner way of finding real size with slabber calls
+    size_t size = bytes_remain + sizeof(item_chunk);
+    if (size > settings.slab_chunk_size_max)
+        size = settings.slab_chunk_size_max;
+    unsigned int id = slabs_clsid(size);
+
+    item_chunk *nch = (item_chunk *) do_item_alloc_pull(size, id);
+    if (nch == NULL)
+        return NULL;
+
+    // link in.
+    // ITEM_CHUNK[ED] bits need to be protected by the slabs lock.
+    slabs_mlock();
+    nch->head = ch->head;
+    ch->next = nch;
+    nch->prev = ch;
+    nch->next = 0;
+    nch->used = 0;
+    nch->slabs_clsid = id;
+    nch->size = size - sizeof(item_chunk);
+    nch->it_flags |= ITEM_CHUNK;
+    slabs_munlock();
+    return nch;
+}
+
+item *do_item_alloc(char *key, const size_t nkey, const unsigned int flags,
+                    const rel_time_t exptime, const int nbytes) {
+    uint8_t nsuffix;
+    item *it = NULL;
+    char suffix[40];
+    // Avoid potential underflows.
+    if (nbytes < 2)
+        return 0;
+
+    size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);
+    if (settings.use_cas) {
+        ntotal += sizeof(uint64_t);
+    }
+
+    unsigned int id = slabs_clsid(ntotal);
+    unsigned int hdr_id = 0;
+    if (id == 0){
+        return 0;
+    }
+
+    /* This is a large item. Allocate a header object now, lazily allocate
+     *  chunks while reading the upload.
+     */
+    if (ntotal > settings.slab_chunk_size_max) {
+        /* We still link this item into the LRU for the larger slab class, but
+         * we're pulling a header from an entirely different slab class. The
+         * free routines handle large items specifically.
+         */
+        int htotal = nkey + 1 + nsuffix + sizeof(item) + sizeof(item_chunk);
+        if (settings.use_cas) {
+            htotal += sizeof(uint64_t);
+        }
+#ifdef NEED_ALIGN
+        // header chunk needs to be padded on some systems
+        int remain = htotal % 8;
+        if (remain != 0) {
+            htotal += 8 - remain;
+        }
+#endif
+        hdr_id = slabs_clsid(htotal);
+        it = do_item_alloc_pull(htotal, hdr_id);
+        /* setting ITEM_CHUNKED is fine here because we aren't LINKED yet. */
+        if (it != NULL)
+            it->it_flags |= ITEM_CHUNKED;
+    } else {
+        it = do_item_alloc_pull(ntotal, id);
+    }
+
+    if (it == NULL) {
+        pthread_mutex_lock(&lru_locks[id]);
+        itemstats[id].outofmemory++;
+        pthread_mutex_unlock(&lru_locks[id]);
+        return NULL;
+    }
+
+    assert(it->it_flags == 0 || it->it_flags == ITEM_CHUNKED);
+    //assert(it != heads[id]);
+
+    /* Refcount is seeded to 1 by slabs_alloc() */
+    it->next = it->prev = 0;
+
+    /* Items are initially loaded into the HOT_LRU. This is '0' but I want at
+     * least a note here. Compiler (hopefully?) optimizes this out.
+     */
+    if (settings.temp_lru &&
+            exptime - current_time <= settings.temporary_ttl) {
+        id |= TEMP_LRU;
+    } else if (settings.lru_segmented) {
+        id |= HOT_LRU;
+    } else {
+        /* There is only COLD in compat-mode */
+        id |= COLD_LRU;
+    }
+    it->slabs_clsid = id;
+
+    DEBUG_REFCNT(it, '*');
+    it->it_flags |= settings.use_cas ? ITEM_CAS : 0;
+    it->it_flags |= nsuffix != 0 ? ITEM_CFLAGS : 0;
+    it->nkey = nkey;
+    it->nbytes = nbytes;
+    memcpy(ITEM_key(it), key, nkey);
+    it->exptime = exptime;
+    if (nsuffix > 0) {
+        memcpy(ITEM_suffix(it), &flags, sizeof(flags));
+    }
+
+    /* Initialize internal chunk. */
+    if (it->it_flags & ITEM_CHUNKED) {
+        item_chunk *chunk = (item_chunk *) ITEM_schunk(it);
+
+        chunk->next = 0;
+        chunk->prev = 0;
+        chunk->used = 0;
+        chunk->size = 0;
+        chunk->head = it;
+        chunk->orig_clsid = hdr_id;
+    }
+    it->h_next = 0;
+
+    return it;
+}
+
+void item_free(item *it) {
+    size_t ntotal = ITEM_ntotal(it);
+    unsigned int clsid;
+    assert((it->it_flags & ITEM_LINKED) == 0);
+    assert(it != heads[it->slabs_clsid]);
+    assert(it != tails[it->slabs_clsid]);
+    assert(it->refcount == 0);
+
+    /* so slab size changer can tell later if item is already free or not */
+    clsid = ITEM_clsid(it);
+    DEBUG_REFCNT(it, 'F');
+    slabs_free(it, ntotal, clsid);
+}
+
+/**
+ * Returns true if an item will fit in the cache (its size does not exceed
+ * the maximum for a cache entry.)
+ */
+bool item_size_ok(const size_t nkey, const int flags, const int nbytes) {
+    char prefix[40];
+    uint8_t nsuffix;
+    if (nbytes < 2)
+        return false;
+
+    size_t ntotal = item_make_header(nkey + 1, flags, nbytes,
+                                     prefix, &nsuffix);
+    if (settings.use_cas) {
+        ntotal += sizeof(uint64_t);
+    }
+
+    return slabs_clsid(ntotal) != 0;
+}
+
+/* fixing stats/references during warm start */
+void do_item_link_fixup(item *it) {
+    item **head, **tail;
+    int ntotal = ITEM_ntotal(it);
+    uint32_t hv = hash(ITEM_key(it), it->nkey);
+    assoc_insert(it, hv);
+
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+    if (it->prev == 0 && *head == 0) *head = it;
+    if (it->next == 0 && *tail == 0) *tail = it;
+    sizes[it->slabs_clsid]++;
+    sizes_bytes[it->slabs_clsid] += ntotal;
+
+    STATS_LOCK();
+    stats_state.curr_bytes += ntotal;
+    stats_state.curr_items += 1;
+    stats.total_items += 1;
+    STATS_UNLOCK();
+
+    item_stats_sizes_add(it);
+
+    return;
+}
+
+static void do_item_link_q(item *it) { /* item is the new head */
+    item **head, **tail;
+    assert((it->it_flags & ITEM_SLABBED) == 0);
+
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+    assert(it != *head);
+    assert((*head && *tail) || (*head == 0 && *tail == 0));
+    it->prev = 0;
+    it->next = *head;
+    if (it->next) it->next->prev = it;
+    *head = it;
+    if (*tail == 0) *tail = it;
+    sizes[it->slabs_clsid]++;
+#ifdef EXTSTORE
+    if (it->it_flags & ITEM_HDR) {
+        sizes_bytes[it->slabs_clsid] += (ITEM_ntotal(it) - it->nbytes) + sizeof(item_hdr);
+    } else {
+        sizes_bytes[it->slabs_clsid] += ITEM_ntotal(it);
+    }
+#else
+    sizes_bytes[it->slabs_clsid] += ITEM_ntotal(it);
+#endif
+
+    return;
+}
+
+static void item_link_q(item *it) {
+    pthread_mutex_lock(&lru_locks[it->slabs_clsid]);
+    do_item_link_q(it);
+    pthread_mutex_unlock(&lru_locks[it->slabs_clsid]);
+}
+
+static void item_link_q_warm(item *it) {
+    pthread_mutex_lock(&lru_locks[it->slabs_clsid]);
+    do_item_link_q(it);
+    itemstats[it->slabs_clsid].moves_to_warm++;
+    pthread_mutex_unlock(&lru_locks[it->slabs_clsid]);
+}
+
+static void do_item_unlink_q(item *it) {
+    item **head, **tail;
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+
+    if (*head == it) {
+        assert(it->prev == 0);
+        *head = it->next;
+    }
+    if (*tail == it) {
+        assert(it->next == 0);
+        *tail = it->prev;
+    }
+    assert(it->next != it);
+    assert(it->prev != it);
+
+    if (it->next) it->next->prev = it->prev;
+    if (it->prev) it->prev->next = it->next;
+    sizes[it->slabs_clsid]--;
+#ifdef EXTSTORE
+    if (it->it_flags & ITEM_HDR) {
+        sizes_bytes[it->slabs_clsid] -= (ITEM_ntotal(it) - it->nbytes) + sizeof(item_hdr);
+    } else {
+        sizes_bytes[it->slabs_clsid] -= ITEM_ntotal(it);
+    }
+#else
+    sizes_bytes[it->slabs_clsid] -= ITEM_ntotal(it);
+#endif
+
+    return;
+}
+
+static void item_unlink_q(item *it) {
+    pthread_mutex_lock(&lru_locks[it->slabs_clsid]);
+    do_item_unlink_q(it);
+    pthread_mutex_unlock(&lru_locks[it->slabs_clsid]);
+}
+
+int do_item_link(item *it, const uint32_t hv) {
+    MEMCACHED_ITEM_LINK(ITEM_key(it), it->nkey, it->nbytes);
+    assert((it->it_flags & (ITEM_LINKED|ITEM_SLABBED)) == 0);
+    it->it_flags |= ITEM_LINKED;
+    it->time = current_time;
+
+    STATS_LOCK();
+    stats_state.curr_bytes += ITEM_ntotal(it);
+    stats_state.curr_items += 1;
+    stats.total_items += 1;
+    STATS_UNLOCK();
+
+    /* Allocate a new CAS ID on link. */
+    ITEM_set_cas(it, (settings.use_cas) ? get_cas_id() : 0);
+    assoc_insert(it, hv);
+    item_link_q(it);
+    refcount_incr(it);
+    item_stats_sizes_add(it);
+
+    return 1;
+}
+
+void do_item_unlink(item *it, const uint32_t hv) {
+    MEMCACHED_ITEM_UNLINK(ITEM_key(it), it->nkey, it->nbytes);
+    if ((it->it_flags & ITEM_LINKED) != 0) {
+        it->it_flags &= ~ITEM_LINKED;
+        STATS_LOCK();
+        stats_state.curr_bytes -= ITEM_ntotal(it);
+        stats_state.curr_items -= 1;
+        STATS_UNLOCK();
+        item_stats_sizes_remove(it);
+        assoc_delete(ITEM_key(it), it->nkey, hv);
+        item_unlink_q(it);
+        do_item_remove(it);
+    }
+}
+
+/* FIXME: Is it necessary to keep this copy/pasted code? */
+void do_item_unlink_nolock(item *it, const uint32_t hv) {
+    MEMCACHED_ITEM_UNLINK(ITEM_key(it), it->nkey, it->nbytes);
+    if ((it->it_flags & ITEM_LINKED) != 0) {
+        it->it_flags &= ~ITEM_LINKED;
+        STATS_LOCK();
+        stats_state.curr_bytes -= ITEM_ntotal(it);
+        stats_state.curr_items -= 1;
+        STATS_UNLOCK();
+        item_stats_sizes_remove(it);
+        assoc_delete(ITEM_key(it), it->nkey, hv);
+        do_item_unlink_q(it);
+        do_item_remove(it);
+    }
+}
+
+void do_item_remove(item *it) {
+    MEMCACHED_ITEM_REMOVE(ITEM_key(it), it->nkey, it->nbytes);
+    assert((it->it_flags & ITEM_SLABBED) == 0);
+    assert(it->refcount > 0);
+
+    if (refcount_decr(it) == 0) {
+        item_free(it);
+    }
+}
+
+/* Bump the last accessed time, or relink if we're in compat mode */
+void do_item_update(item *it) {
+    MEMCACHED_ITEM_UPDATE(ITEM_key(it), it->nkey, it->nbytes);
+
+    /* Hits to COLD_LRU immediately move to WARM. */
+    if (settings.lru_segmented) {
+        assert((it->it_flags & ITEM_SLABBED) == 0);
+        if ((it->it_flags & ITEM_LINKED) != 0) {
+            if (ITEM_lruid(it) == COLD_LRU && (it->it_flags & ITEM_ACTIVE)) {
+                it->time = current_time;
+                item_unlink_q(it);
+                it->slabs_clsid = ITEM_clsid(it);
+                it->slabs_clsid |= WARM_LRU;
+                it->it_flags &= ~ITEM_ACTIVE;
+                item_link_q_warm(it);
+            } else {
+                it->time = current_time;
+            }
+        }
+    } else if (it->time < current_time - ITEM_UPDATE_INTERVAL) {
+        assert((it->it_flags & ITEM_SLABBED) == 0);
+
+        if ((it->it_flags & ITEM_LINKED) != 0) {
+            it->time = current_time;
+            item_unlink_q(it);
+            item_link_q(it);
+        }
+    }
+}
+
+int do_item_replace(item *it, item *new_it, const uint32_t hv) {
+    MEMCACHED_ITEM_REPLACE(ITEM_key(it), it->nkey, it->nbytes,
+                           ITEM_key(new_it), new_it->nkey, new_it->nbytes);
+    assert((it->it_flags & ITEM_SLABBED) == 0);
+
+    do_item_unlink(it, hv);
+    return do_item_link(new_it, hv);
+}
+
+/*@null@*/
+/* This is walking the line of violating lock order, but I think it's safe.
+ * If the LRU lock is held, an item in the LRU cannot be wiped and freed.
+ * The data could possibly be overwritten, but this is only accessing the
+ * headers.
+ * It may not be the best idea to leave it like this, but for now it's safe.
+ */
+char *item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes) {
+    unsigned int memlimit = 2 * 1024 * 1024;   /* 2MB max response size */
+    char *buffer;
+    unsigned int bufcurr;
+    item *it;
+    unsigned int len;
+    unsigned int shown = 0;
+    char key_temp[KEY_MAX_LENGTH + 1];
+    char temp[512];
+    unsigned int id = slabs_clsid;
+    id |= COLD_LRU;
+
+    pthread_mutex_lock(&lru_locks[id]);
+    it = heads[id];
+
+    buffer = malloc((size_t)memlimit);
+    if (buffer == 0) {
+        pthread_mutex_unlock(&lru_locks[id]);
+        return NULL;
+    }
+    bufcurr = 0;
+
+    while (it != NULL && (limit == 0 || shown < limit)) {
+        assert(it->nkey <= KEY_MAX_LENGTH);
+        if (it->nbytes == 0 && it->nkey == 0) {
+            it = it->next;
+            continue;
+        }
+        /* Copy the key since it may not be null-terminated in the struct */
+        strncpy(key_temp, ITEM_key(it), it->nkey);
+        key_temp[it->nkey] = 0x00; /* terminate */
+        len = snprintf(temp, sizeof(temp), "ITEM %s [%d b; %llu s]\r\n",
+                       key_temp, it->nbytes - 2,
+                       it->exptime == 0 ? 0 :
+                       (unsigned long long)it->exptime + process_started);
+        if (bufcurr + len + 6 > memlimit)  /* 6 is END\r\n\0 */
+            break;
+        memcpy(buffer + bufcurr, temp, len);
+        bufcurr += len;
+        shown++;
+        it = it->next;
+    }
+
+    memcpy(buffer + bufcurr, "END\r\n", 6);
+    bufcurr += 5;
+
+    *bytes = bufcurr;
+    pthread_mutex_unlock(&lru_locks[id]);
+    return buffer;
+}
+
+/* With refactoring of the various stats code the automover won't need a
+ * custom function here.
+ */
+void fill_item_stats_automove(item_stats_automove *am) {
+    int n;
+    for (n = 0; n < MAX_NUMBER_OF_SLAB_CLASSES; n++) {
+        item_stats_automove *cur = &am[n];
+
+        // outofmemory records into HOT
+        int i = n | HOT_LRU;
+        pthread_mutex_lock(&lru_locks[i]);
+        cur->outofmemory = itemstats[i].outofmemory;
+        pthread_mutex_unlock(&lru_locks[i]);
+
+        // evictions and tail age are from COLD
+        i = n | COLD_LRU;
+        pthread_mutex_lock(&lru_locks[i]);
+        cur->evicted = itemstats[i].evicted;
+        if (tails[i]) {
+            cur->age = current_time - tails[i]->time;
+        } else {
+            cur->age = 0;
+        }
+        pthread_mutex_unlock(&lru_locks[i]);
+     }
+}
+
+void item_stats_totals(ADD_STAT add_stats, void *c) {
+    itemstats_t totals;
+    memset(&totals, 0, sizeof(itemstats_t));
+    int n;
+    for (n = 0; n < MAX_NUMBER_OF_SLAB_CLASSES; n++) {
+        int x;
+        int i;
+        for (x = 0; x < 4; x++) {
+            i = n | lru_type_map[x];
+            pthread_mutex_lock(&lru_locks[i]);
+            totals.expired_unfetched += itemstats[i].expired_unfetched;
+            totals.evicted_unfetched += itemstats[i].evicted_unfetched;
+            totals.evicted_active += itemstats[i].evicted_active;
+            totals.evicted += itemstats[i].evicted;
+            totals.reclaimed += itemstats[i].reclaimed;
+            totals.crawler_reclaimed += itemstats[i].crawler_reclaimed;
+            totals.crawler_items_checked += itemstats[i].crawler_items_checked;
+            totals.lrutail_reflocked += itemstats[i].lrutail_reflocked;
+            totals.moves_to_cold += itemstats[i].moves_to_cold;
+            totals.moves_to_warm += itemstats[i].moves_to_warm;
+            totals.moves_within_lru += itemstats[i].moves_within_lru;
+            totals.direct_reclaims += itemstats[i].direct_reclaims;
+            pthread_mutex_unlock(&lru_locks[i]);
+        }
+    }
+    APPEND_STAT("expired_unfetched", "%llu",
+                (unsigned long long)totals.expired_unfetched);
+    APPEND_STAT("evicted_unfetched", "%llu",
+                (unsigned long long)totals.evicted_unfetched);
+    if (settings.lru_maintainer_thread) {
+        APPEND_STAT("evicted_active", "%llu",
+                    (unsigned long long)totals.evicted_active);
+    }
+    APPEND_STAT("evictions", "%llu",
+                (unsigned long long)totals.evicted);
+    APPEND_STAT("reclaimed", "%llu",
+                (unsigned long long)totals.reclaimed);
+    APPEND_STAT("crawler_reclaimed", "%llu",
+                (unsigned long long)totals.crawler_reclaimed);
+    APPEND_STAT("crawler_items_checked", "%llu",
+                (unsigned long long)totals.crawler_items_checked);
+    APPEND_STAT("lrutail_reflocked", "%llu",
+                (unsigned long long)totals.lrutail_reflocked);
+    if (settings.lru_maintainer_thread) {
+        APPEND_STAT("moves_to_cold", "%llu",
+                    (unsigned long long)totals.moves_to_cold);
+        APPEND_STAT("moves_to_warm", "%llu",
+                    (unsigned long long)totals.moves_to_warm);
+        APPEND_STAT("moves_within_lru", "%llu",
+                    (unsigned long long)totals.moves_within_lru);
+        APPEND_STAT("direct_reclaims", "%llu",
+                    (unsigned long long)totals.direct_reclaims);
+        APPEND_STAT("lru_bumps_dropped", "%llu",
+                    (unsigned long long)lru_total_bumps_dropped());
+    }
+}
+
+void item_stats(ADD_STAT add_stats, void *c) {
+    struct thread_stats thread_stats;
+    threadlocal_stats_aggregate(&thread_stats);
+    itemstats_t totals;
+    int n;
+    for (n = 0; n < MAX_NUMBER_OF_SLAB_CLASSES; n++) {
+        memset(&totals, 0, sizeof(itemstats_t));
+        int x;
+        int i;
+        unsigned int size = 0;
+        unsigned int age  = 0;
+        unsigned int age_hot = 0;
+        unsigned int age_warm = 0;
+        unsigned int lru_size_map[4];
+        const char *fmt = "items:%d:%s";
+        char key_str[STAT_KEY_LEN];
+        char val_str[STAT_VAL_LEN];
+        int klen = 0, vlen = 0;
+        for (x = 0; x < 4; x++) {
+            i = n | lru_type_map[x];
+            pthread_mutex_lock(&lru_locks[i]);
+            totals.evicted += itemstats[i].evicted;
+            totals.evicted_nonzero += itemstats[i].evicted_nonzero;
+            totals.outofmemory += itemstats[i].outofmemory;
+            totals.tailrepairs += itemstats[i].tailrepairs;
+            totals.reclaimed += itemstats[i].reclaimed;
+            totals.expired_unfetched += itemstats[i].expired_unfetched;
+            totals.evicted_unfetched += itemstats[i].evicted_unfetched;
+            totals.evicted_active += itemstats[i].evicted_active;
+            totals.crawler_reclaimed += itemstats[i].crawler_reclaimed;
+            totals.crawler_items_checked += itemstats[i].crawler_items_checked;
+            totals.lrutail_reflocked += itemstats[i].lrutail_reflocked;
+            totals.moves_to_cold += itemstats[i].moves_to_cold;
+            totals.moves_to_warm += itemstats[i].moves_to_warm;
+            totals.moves_within_lru += itemstats[i].moves_within_lru;
+            totals.direct_reclaims += itemstats[i].direct_reclaims;
+            totals.mem_requested += sizes_bytes[i];
+            size += sizes[i];
+            lru_size_map[x] = sizes[i];
+            if (lru_type_map[x] == COLD_LRU && tails[i] != NULL) {
+                age = current_time - tails[i]->time;
+            } else if (lru_type_map[x] == HOT_LRU && tails[i] != NULL) {
+                age_hot = current_time - tails[i]->time;
+            } else if (lru_type_map[x] == WARM_LRU && tails[i] != NULL) {
+                age_warm = current_time - tails[i]->time;
+            }
+            if (lru_type_map[x] == COLD_LRU)
+                totals.evicted_time = itemstats[i].evicted_time;
+            switch (lru_type_map[x]) {
+                case HOT_LRU:
+                    totals.hits_to_hot = thread_stats.lru_hits[i];
+                    break;
+                case WARM_LRU:
+                    totals.hits_to_warm = thread_stats.lru_hits[i];
+                    break;
+                case COLD_LRU:
+                    totals.hits_to_cold = thread_stats.lru_hits[i];
+                    break;
+                case TEMP_LRU:
+                    totals.hits_to_temp = thread_stats.lru_hits[i];
+                    break;
+            }
+            pthread_mutex_unlock(&lru_locks[i]);
+        }
+        if (size == 0)
+            continue;
+        APPEND_NUM_FMT_STAT(fmt, n, "number", "%u", size);
+        if (settings.lru_maintainer_thread) {
+            APPEND_NUM_FMT_STAT(fmt, n, "number_hot", "%u", lru_size_map[0]);
+            APPEND_NUM_FMT_STAT(fmt, n, "number_warm", "%u", lru_size_map[1]);
+            APPEND_NUM_FMT_STAT(fmt, n, "number_cold", "%u", lru_size_map[2]);
+            if (settings.temp_lru) {
+                APPEND_NUM_FMT_STAT(fmt, n, "number_temp", "%u", lru_size_map[3]);
+            }
+            APPEND_NUM_FMT_STAT(fmt, n, "age_hot", "%u", age_hot);
+            APPEND_NUM_FMT_STAT(fmt, n, "age_warm", "%u", age_warm);
+        }
+        APPEND_NUM_FMT_STAT(fmt, n, "age", "%u", age);
+        APPEND_NUM_FMT_STAT(fmt, n, "mem_requested", "%llu", (unsigned long long)totals.mem_requested);
+        APPEND_NUM_FMT_STAT(fmt, n, "evicted",
+                            "%llu", (unsigned long long)totals.evicted);
+        APPEND_NUM_FMT_STAT(fmt, n, "evicted_nonzero",
+                            "%llu", (unsigned long long)totals.evicted_nonzero);
+        APPEND_NUM_FMT_STAT(fmt, n, "evicted_time",
+                            "%u", totals.evicted_time);
+        APPEND_NUM_FMT_STAT(fmt, n, "outofmemory",
+                            "%llu", (unsigned long long)totals.outofmemory);
+        APPEND_NUM_FMT_STAT(fmt, n, "tailrepairs",
+                            "%llu", (unsigned long long)totals.tailrepairs);
+        APPEND_NUM_FMT_STAT(fmt, n, "reclaimed",
+                            "%llu", (unsigned long long)totals.reclaimed);
+        APPEND_NUM_FMT_STAT(fmt, n, "expired_unfetched",
+                            "%llu", (unsigned long long)totals.expired_unfetched);
+        APPEND_NUM_FMT_STAT(fmt, n, "evicted_unfetched",
+                            "%llu", (unsigned long long)totals.evicted_unfetched);
+        if (settings.lru_maintainer_thread) {
+            APPEND_NUM_FMT_STAT(fmt, n, "evicted_active",
+                                "%llu", (unsigned long long)totals.evicted_active);
+        }
+        APPEND_NUM_FMT_STAT(fmt, n, "crawler_reclaimed",
+                            "%llu", (unsigned long long)totals.crawler_reclaimed);
+        APPEND_NUM_FMT_STAT(fmt, n, "crawler_items_checked",
+                            "%llu", (unsigned long long)totals.crawler_items_checked);
+        APPEND_NUM_FMT_STAT(fmt, n, "lrutail_reflocked",
+                            "%llu", (unsigned long long)totals.lrutail_reflocked);
+        if (settings.lru_maintainer_thread) {
+            APPEND_NUM_FMT_STAT(fmt, n, "moves_to_cold",
+                                "%llu", (unsigned long long)totals.moves_to_cold);
+            APPEND_NUM_FMT_STAT(fmt, n, "moves_to_warm",
+                                "%llu", (unsigned long long)totals.moves_to_warm);
+            APPEND_NUM_FMT_STAT(fmt, n, "moves_within_lru",
+                                "%llu", (unsigned long long)totals.moves_within_lru);
+            APPEND_NUM_FMT_STAT(fmt, n, "direct_reclaims",
+                                "%llu", (unsigned long long)totals.direct_reclaims);
+            APPEND_NUM_FMT_STAT(fmt, n, "hits_to_hot",
+                                "%llu", (unsigned long long)totals.hits_to_hot);
+
+            APPEND_NUM_FMT_STAT(fmt, n, "hits_to_warm",
+                                "%llu", (unsigned long long)totals.hits_to_warm);
+
+            APPEND_NUM_FMT_STAT(fmt, n, "hits_to_cold",
+                                "%llu", (unsigned long long)totals.hits_to_cold);
+
+            APPEND_NUM_FMT_STAT(fmt, n, "hits_to_temp",
+                                "%llu", (unsigned long long)totals.hits_to_temp);
+
+        }
+    }
+
+    /* getting here means both ascii and binary terminators fit */
+    add_stats(NULL, 0, NULL, 0, c);
+}
+
+bool item_stats_sizes_status(void) {
+    bool ret = false;
+    mutex_lock(&stats_sizes_lock);
+    if (stats_sizes_hist != NULL)
+        ret = true;
+    mutex_unlock(&stats_sizes_lock);
+    return ret;
+}
+
+void item_stats_sizes_init(void) {
+    if (stats_sizes_hist != NULL)
+        return;
+    stats_sizes_buckets = settings.item_size_max / 32 + 1;
+    stats_sizes_hist = calloc(stats_sizes_buckets, sizeof(int));
+    stats_sizes_cas_min = (settings.use_cas) ? get_cas_id() : 0;
+}
+
+void item_stats_sizes_enable(ADD_STAT add_stats, void *c) {
+    mutex_lock(&stats_sizes_lock);
+    if (!settings.use_cas) {
+        APPEND_STAT("sizes_status", "error", "");
+        APPEND_STAT("sizes_error", "cas_support_disabled", "");
+    } else if (stats_sizes_hist == NULL) {
+        item_stats_sizes_init();
+        if (stats_sizes_hist != NULL) {
+            APPEND_STAT("sizes_status", "enabled", "");
+        } else {
+            APPEND_STAT("sizes_status", "error", "");
+            APPEND_STAT("sizes_error", "no_memory", "");
+        }
+    } else {
+        APPEND_STAT("sizes_status", "enabled", "");
+    }
+    mutex_unlock(&stats_sizes_lock);
+}
+
+void item_stats_sizes_disable(ADD_STAT add_stats, void *c) {
+    mutex_lock(&stats_sizes_lock);
+    if (stats_sizes_hist != NULL) {
+        free(stats_sizes_hist);
+        stats_sizes_hist = NULL;
+    }
+    APPEND_STAT("sizes_status", "disabled", "");
+    mutex_unlock(&stats_sizes_lock);
+}
+
+void item_stats_sizes_add(item *it) {
+    if (stats_sizes_hist == NULL || stats_sizes_cas_min > ITEM_get_cas(it))
+        return;
+    int ntotal = ITEM_ntotal(it);
+    int bucket = ntotal / 32;
+    if ((ntotal % 32) != 0) bucket++;
+    if (bucket < stats_sizes_buckets) stats_sizes_hist[bucket]++;
+}
+
+/* I think there's no way for this to be accurate without using the CAS value.
+ * Since items getting their time value bumped will pass this validation.
+ */
+void item_stats_sizes_remove(item *it) {
+    if (stats_sizes_hist == NULL || stats_sizes_cas_min > ITEM_get_cas(it))
+        return;
+    int ntotal = ITEM_ntotal(it);
+    int bucket = ntotal / 32;
+    if ((ntotal % 32) != 0) bucket++;
+    if (bucket < stats_sizes_buckets) stats_sizes_hist[bucket]--;
+}
+
+/** dumps out a list of objects of each size, with granularity of 32 bytes */
+/*@null@*/
+/* Locks are correct based on a technicality. Holds LRU lock while doing the
+ * work, so items can't go invalid, and it's only looking at header sizes
+ * which don't change.
+ */
+void item_stats_sizes(ADD_STAT add_stats, void *c) {
+    mutex_lock(&stats_sizes_lock);
+
+    if (stats_sizes_hist != NULL) {
+        int i;
+        for (i = 0; i < stats_sizes_buckets; i++) {
+            if (stats_sizes_hist[i] != 0) {
+                char key[12];
+                snprintf(key, sizeof(key), "%d", i * 32);
+                APPEND_STAT(key, "%u", stats_sizes_hist[i]);
+            }
+        }
+    } else {
+        APPEND_STAT("sizes_status", "disabled", "");
+    }
+
+    add_stats(NULL, 0, NULL, 0, c);
+    mutex_unlock(&stats_sizes_lock);
+}
+
+/** wrapper around assoc_find which does the lazy expiration logic */
+item *do_item_get(const char *key, const size_t nkey, const uint32_t hv, conn *c, const bool do_update) {
+    item *it = assoc_find(key, nkey, hv);
+    if (it != NULL) {
+        refcount_incr(it);
+        /* Optimization for slab reassignment. prevents popular items from
+         * jamming in busy wait. Can only do this here to satisfy lock order
+         * of item_lock, slabs_lock. */
+        /* This was made unsafe by removal of the cache_lock:
+         * slab_rebalance_signal and slab_rebal.* are modified in a separate
+         * thread under slabs_lock. If slab_rebalance_signal = 1, slab_start =
+         * NULL (0), but slab_end is still equal to some value, this would end
+         * up unlinking every item fetched.
+         * This is either an acceptable loss, or if slab_rebalance_signal is
+         * true, slab_start/slab_end should be put behind the slabs_lock.
+         * Which would cause a huge potential slowdown.
+         * Could also use a specific lock for slab_rebal.* and
+         * slab_rebalance_signal (shorter lock?)
+         */
+        /*if (slab_rebalance_signal &&
+            ((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) {
+            do_item_unlink(it, hv);
+            do_item_remove(it);
+            it = NULL;
+        }*/
+    }
+    int was_found = 0;
+
+    if (settings.verbose > 2) {
+        int ii;
+        if (it == NULL) {
+            fprintf(stderr, "> NOT FOUND ");
+        } else {
+            fprintf(stderr, "> FOUND KEY ");
+        }
+        for (ii = 0; ii < nkey; ++ii) {
+            fprintf(stderr, "%c", key[ii]);
+        }
+    }
+
+    if (it != NULL) {
+        was_found = 1;
+        if (item_is_flushed(it)) {
+            do_item_unlink(it, hv);
+            STORAGE_delete(c->thread->storage, it);
+            do_item_remove(it);
+            it = NULL;
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.get_flushed++;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+            if (settings.verbose > 2) {
+                fprintf(stderr, " -nuked by flush");
+            }
+            was_found = 2;
+        } else if (it->exptime != 0 && it->exptime <= current_time) {
+            do_item_unlink(it, hv);
+            STORAGE_delete(c->thread->storage, it);
+            do_item_remove(it);
+            it = NULL;
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.get_expired++;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+            if (settings.verbose > 2) {
+                fprintf(stderr, " -nuked by expire");
+            }
+            was_found = 3;
+        } else {
+            if (do_update) {
+                do_item_bump(c, it, hv);
+            }
+            DEBUG_REFCNT(it, '+');
+        }
+    }
+
+    if (settings.verbose > 2)
+        fprintf(stderr, "\n");
+    /* For now this is in addition to the above verbose logging. */
+    //LOGGER_LOG(c->thread->l, LOG_FETCHERS, LOGGER_ITEM_GET, NULL, was_found, key, nkey,
+    //         (it) ? ITEM_clsid(it) : 0, c->sfd);
+
+    return it;
+}
+
+// Requires lock held for item.
+// Split out of do_item_get() to allow mget functions to look through header
+// data before losing state modified via the bump function.
+void do_item_bump(conn *c, item *it, const uint32_t hv) {
+    /* We update the hit markers only during fetches.
+     * An item needs to be hit twice overall to be considered
+     * ACTIVE, but only needs a single hit to maintain activity
+     * afterward.
+     * FETCHED tells if an item has ever been active.
+     */
+    if (settings.lru_segmented) {
+        if ((it->it_flags & ITEM_ACTIVE) == 0) {
+            if ((it->it_flags & ITEM_FETCHED) == 0) {
+                it->it_flags |= ITEM_FETCHED;
+            } else {
+                it->it_flags |= ITEM_ACTIVE;
+                if (ITEM_lruid(it) != COLD_LRU) {
+                    it->time = current_time; // only need to bump time.
+                } else if (!lru_bump_async(c->thread->lru_bump_buf, it, hv)) {
+                    // add flag before async bump to avoid race.
+                    it->it_flags &= ~ITEM_ACTIVE;
+                }
+            }
+        }
+    } else {
+        it->it_flags |= ITEM_FETCHED;
+        do_item_update(it);
+    }
+}
+
+item *do_item_touch(const char *key, size_t nkey, uint32_t exptime,
+                    const uint32_t hv, conn *c) {
+    item *it = do_item_get(key, nkey, hv, c, DO_UPDATE);
+    if (it != NULL) {
+        it->exptime = exptime;
+    }
+    return it;
+}
+
+/*** LRU MAINTENANCE THREAD ***/
+
+/* Returns number of items remove, expired, or evicted.
+ * Callable from worker threads or the LRU maintainer thread */
+int lru_pull_tail(const int orig_id, const int cur_lru,
+        const uint64_t total_bytes, const uint8_t flags, const rel_time_t max_age,
+        struct lru_pull_tail_return *ret_it) {
+    item *it = NULL;
+    int id = orig_id;
+    int removed = 0;
+    if (id == 0)
+        return 0;
+
+    int tries = 5;
+    item *search;
+    item *next_it;
+    void *hold_lock = NULL;
+    unsigned int move_to_lru = 0;
+    uint64_t limit = 0;
+
+    id |= cur_lru;
+    pthread_mutex_lock(&lru_locks[id]);
+    search = tails[id];
+    /* We walk up *only* for locked items, and if bottom is expired. */
+    for (; tries > 0 && search != NULL; tries--, search=next_it) {
+        /* we might relink search mid-loop, so search->prev isn't reliable */
+        next_it = search->prev;
+        if (search->nbytes == 0 && search->nkey == 0 && search->it_flags == 1) {
+            /* We are a crawler, ignore it. */
+            if (flags & LRU_PULL_CRAWL_BLOCKS) {
+                pthread_mutex_unlock(&lru_locks[id]);
+                return 0;
+            }
+            tries++;
+            continue;
+        }
+        uint32_t hv = hash(ITEM_key(search), search->nkey);
+        /* Attempt to hash item lock the "search" item. If locked, no
+         * other callers can incr the refcount. Also skip ourselves. */
+        if ((hold_lock = item_trylock(hv)) == NULL)
+            continue;
+        /* Now see if the item is refcount locked */
+        if (refcount_incr(search) != 2) {
+            /* Note pathological case with ref'ed items in tail.
+             * Can still unlink the item, but it won't be reusable yet */
+            itemstats[id].lrutail_reflocked++;
+            /* In case of refcount leaks, enable for quick workaround. */
+            /* WARNING: This can cause terrible corruption */
+            if (settings.tail_repair_time &&
+                    search->time + settings.tail_repair_time < current_time) {
+                itemstats[id].tailrepairs++;
+                search->refcount = 1;
+                /* This will call item_remove -> item_free since refcnt is 1 */
+                STORAGE_delete(ext_storage, search);
+                do_item_unlink_nolock(search, hv);
+                item_trylock_unlock(hold_lock);
+                continue;
+            }
+        }
+
+        /* Expired or flushed */
+        if ((search->exptime != 0 && search->exptime < current_time)
+            || item_is_flushed(search)) {
+            itemstats[id].reclaimed++;
+            if ((search->it_flags & ITEM_FETCHED) == 0) {
+                itemstats[id].expired_unfetched++;
+            }
+            /* refcnt 2 -> 1 */
+            do_item_unlink_nolock(search, hv);
+            STORAGE_delete(ext_storage, search);
+            /* refcnt 1 -> 0 -> item_free */
+            do_item_remove(search);
+            item_trylock_unlock(hold_lock);
+            removed++;
+
+            /* If all we're finding are expired, can keep going */
+            continue;
+        }
+
+        /* If we're HOT_LRU or WARM_LRU and over size limit, send to COLD_LRU.
+         * If we're COLD_LRU, send to WARM_LRU unless we need to evict
+         */
+        switch (cur_lru) {
+            case HOT_LRU:
+                limit = total_bytes * settings.hot_lru_pct / 100;
+            case WARM_LRU:
+                if (limit == 0)
+                    limit = total_bytes * settings.warm_lru_pct / 100;
+                /* Rescue ACTIVE items aggressively */
+                if ((search->it_flags & ITEM_ACTIVE) != 0) {
+                    search->it_flags &= ~ITEM_ACTIVE;
+                    removed++;
+                    if (cur_lru == WARM_LRU) {
+                        itemstats[id].moves_within_lru++;
+                        do_item_unlink_q(search);
+                        do_item_link_q(search);
+                        do_item_remove(search);
+                        item_trylock_unlock(hold_lock);
+                    } else {
+                        /* Active HOT_LRU items flow to WARM */
+                        itemstats[id].moves_to_warm++;
+                        move_to_lru = WARM_LRU;
+                        do_item_unlink_q(search);
+                        it = search;
+                    }
+                } else if (sizes_bytes[id] > limit ||
+                           current_time - search->time > max_age) {
+                    itemstats[id].moves_to_cold++;
+                    move_to_lru = COLD_LRU;
+                    do_item_unlink_q(search);
+                    it = search;
+                    removed++;
+                    break;
+                } else {
+                    /* Don't want to move to COLD, not active, bail out */
+                    it = search;
+                }
+                break;
+            case COLD_LRU:
+                it = search; /* No matter what, we're stopping */
+                if (flags & LRU_PULL_EVICT) {
+                    if (settings.evict_to_free == 0) {
+                        /* Don't think we need a counter for this. It'll OOM.  */
+                        break;
+                    }
+                    itemstats[id].evicted++;
+                    itemstats[id].evicted_time = current_time - search->time;
+                    if (search->exptime != 0)
+                        itemstats[id].evicted_nonzero++;
+                    if ((search->it_flags & ITEM_FETCHED) == 0) {
+                        itemstats[id].evicted_unfetched++;
+                    }
+                    if ((search->it_flags & ITEM_ACTIVE)) {
+                        itemstats[id].evicted_active++;
+                    }
+                    //LOGGER_LOG(NULL, LOG_EVICTIONS, LOGGER_EVICTION, search);
+                    STORAGE_delete(ext_storage, search);
+                    do_item_unlink_nolock(search, hv);
+                    removed++;
+                    if (settings.slab_automove == 2) {
+                        slabs_reassign(-1, orig_id);
+                    }
+                } else if (flags & LRU_PULL_RETURN_ITEM) {
+                    /* Keep a reference to this item and return it. */
+                    ret_it->it = it;
+                    ret_it->hv = hv;
+                } else if ((search->it_flags & ITEM_ACTIVE) != 0
+                        && settings.lru_segmented) {
+                    itemstats[id].moves_to_warm++;
+                    search->it_flags &= ~ITEM_ACTIVE;
+                    move_to_lru = WARM_LRU;
+                    do_item_unlink_q(search);
+                    removed++;
+                }
+                break;
+            case TEMP_LRU:
+                it = search; /* Kill the loop. Parent only interested in reclaims */
+                break;
+        }
+        if (it != NULL)
+            break;
+    }
+
+    pthread_mutex_unlock(&lru_locks[id]);
+
+    if (it != NULL) {
+        if (move_to_lru) {
+            it->slabs_clsid = ITEM_clsid(it);
+            it->slabs_clsid |= move_to_lru;
+            item_link_q(it);
+        }
+        if ((flags & LRU_PULL_RETURN_ITEM) == 0) {
+            do_item_remove(it);
+            item_trylock_unlock(hold_lock);
+        }
+    }
+
+    return removed;
+}
+
+
+/* TODO: Third place this code needs to be deduped */
+static void lru_bump_buf_link_q(lru_bump_buf *b) {
+    pthread_mutex_lock(&bump_buf_lock);
+    assert(b != bump_buf_head);
+
+    b->prev = 0;
+    b->next = bump_buf_head;
+    if (b->next) b->next->prev = b;
+    bump_buf_head = b;
+    if (bump_buf_tail == 0) bump_buf_tail = b;
+    pthread_mutex_unlock(&bump_buf_lock);
+    return;
+}
+
+void *item_lru_bump_buf_create(void) {
+    lru_bump_buf *b = calloc(1, sizeof(lru_bump_buf));
+    if (b == NULL) {
+        return NULL;
+    }
+
+    b->buf = bipbuf_new(sizeof(lru_bump_entry) * LRU_BUMP_BUF_SIZE);
+    if (b->buf == NULL) {
+        free(b);
+        return NULL;
+    }
+
+    pthread_mutex_init(&b->mutex, NULL);
+
+    lru_bump_buf_link_q(b);
+    return b;
+}
+
+static bool lru_bump_async(lru_bump_buf *b, item *it, uint32_t hv) {
+    bool ret = true;
+    refcount_incr(it);
+    pthread_mutex_lock(&b->mutex);
+    lru_bump_entry *be = (lru_bump_entry *) bipbuf_request(b->buf, sizeof(lru_bump_entry));
+    if (be != NULL) {
+        be->it = it;
+        be->hv = hv;
+        if (bipbuf_push(b->buf, sizeof(lru_bump_entry)) == 0) {
+            ret = false;
+            b->dropped++;
+        }
+    } else {
+        ret = false;
+        b->dropped++;
+    }
+    if (!ret) {
+        refcount_decr(it);
+    }
+    pthread_mutex_unlock(&b->mutex);
+    return ret;
+}
+
+/* TODO: Might be worth a micro-optimization of having bump buffers link
+ * themselves back into the central queue when queue goes from zero to
+ * non-zero, then remove from list if zero more than N times.
+ * If very few hits on cold this would avoid extra memory barriers from LRU
+ * maintainer thread. If many hits, they'll just stay in the list.
+ */
+static bool lru_maintainer_bumps(void) {
+    lru_bump_buf *b;
+    lru_bump_entry *be;
+    unsigned int size;
+    unsigned int todo;
+    bool bumped = false;
+    pthread_mutex_lock(&bump_buf_lock);
+    for (b = bump_buf_head; b != NULL; b=b->next) {
+        pthread_mutex_lock(&b->mutex);
+        be = (lru_bump_entry *) bipbuf_peek_all(b->buf, &size);
+        pthread_mutex_unlock(&b->mutex);
+
+        if (be == NULL) {
+            continue;
+        }
+        todo = size;
+        bumped = true;
+
+        while (todo) {
+            item_lock(be->hv);
+            do_item_update(be->it);
+            do_item_remove(be->it);
+            item_unlock(be->hv);
+            be++;
+            todo -= sizeof(lru_bump_entry);
+        }
+
+        pthread_mutex_lock(&b->mutex);
+        be = (lru_bump_entry *) bipbuf_poll(b->buf, size);
+        pthread_mutex_unlock(&b->mutex);
+    }
+    pthread_mutex_unlock(&bump_buf_lock);
+    return bumped;
+}
+
+static uint64_t lru_total_bumps_dropped(void) {
+    uint64_t total = 0;
+    lru_bump_buf *b;
+    pthread_mutex_lock(&bump_buf_lock);
+    for (b = bump_buf_head; b != NULL; b=b->next) {
+        pthread_mutex_lock(&b->mutex);
+        total += b->dropped;
+        pthread_mutex_unlock(&b->mutex);
+    }
+    pthread_mutex_unlock(&bump_buf_lock);
+    return total;
+}
+
+/* Loop up to N times:
+ * If too many items are in HOT_LRU, push to COLD_LRU
+ * If too many items are in WARM_LRU, push to COLD_LRU
+ * If too many items are in COLD_LRU, poke COLD_LRU tail
+ * 1000 loops with 1ms min sleep gives us under 1m items shifted/sec. The
+ * locks can't handle much more than that. Leaving a TODO for how to
+ * autoadjust in the future.
+ */
+static int lru_maintainer_juggle(const int slabs_clsid) {
+    int i;
+    int did_moves = 0;
+    uint64_t total_bytes = 0;
+    unsigned int chunks_perslab = 0;
+    //unsigned int chunks_free = 0;
+    /* TODO: if free_chunks below high watermark, increase aggressiveness */
+    slabs_available_chunks(slabs_clsid, NULL,
+            &chunks_perslab);
+    if (settings.temp_lru) {
+        /* Only looking for reclaims. Run before we size the LRU. */
+        for (i = 0; i < 500; i++) {
+            if (lru_pull_tail(slabs_clsid, TEMP_LRU, 0, 0, 0, NULL) <= 0) {
+                break;
+            } else {
+                did_moves++;
+            }
+        }
+    }
+
+    rel_time_t cold_age = 0;
+    rel_time_t hot_age = 0;
+    rel_time_t warm_age = 0;
+    /* If LRU is in flat mode, force items to drain into COLD via max age of 0 */
+    if (settings.lru_segmented) {
+        pthread_mutex_lock(&lru_locks[slabs_clsid|COLD_LRU]);
+        if (tails[slabs_clsid|COLD_LRU]) {
+            cold_age = current_time - tails[slabs_clsid|COLD_LRU]->time;
+        }
+        // Also build up total_bytes for the classes.
+        total_bytes += sizes_bytes[slabs_clsid|COLD_LRU];
+        pthread_mutex_unlock(&lru_locks[slabs_clsid|COLD_LRU]);
+
+        hot_age = cold_age * settings.hot_max_factor;
+        warm_age = cold_age * settings.warm_max_factor;
+
+        // total_bytes doesn't have to be exact. cache it for the juggles.
+        pthread_mutex_lock(&lru_locks[slabs_clsid|HOT_LRU]);
+        total_bytes += sizes_bytes[slabs_clsid|HOT_LRU];
+        pthread_mutex_unlock(&lru_locks[slabs_clsid|HOT_LRU]);
+
+        pthread_mutex_lock(&lru_locks[slabs_clsid|WARM_LRU]);
+        total_bytes += sizes_bytes[slabs_clsid|WARM_LRU];
+        pthread_mutex_unlock(&lru_locks[slabs_clsid|WARM_LRU]);
+    }
+
+    /* Juggle HOT/WARM up to N times */
+    for (i = 0; i < 500; i++) {
+        int do_more = 0;
+        if (lru_pull_tail(slabs_clsid, HOT_LRU, total_bytes, LRU_PULL_CRAWL_BLOCKS, hot_age, NULL) ||
+            lru_pull_tail(slabs_clsid, WARM_LRU, total_bytes, LRU_PULL_CRAWL_BLOCKS, warm_age, NULL)) {
+            do_more++;
+        }
+        if (settings.lru_segmented) {
+            do_more += lru_pull_tail(slabs_clsid, COLD_LRU, total_bytes, LRU_PULL_CRAWL_BLOCKS, 0, NULL);
+        }
+        if (do_more == 0)
+            break;
+        did_moves++;
+    }
+    return did_moves;
+}
+
+/* Will crawl all slab classes a minimum of once per hour */
+#define MAX_MAINTCRAWL_WAIT 60 * 60
+
+/* Hoping user input will improve this function. This is all a wild guess.
+ * Operation: Kicks crawler for each slab id. Crawlers take some statistics as
+ * to items with nonzero expirations. It then buckets how many items will
+ * expire per minute for the next hour.
+ * This function checks the results of a run, and if it things more than 1% of
+ * expirable objects are ready to go, kick the crawler again to reap.
+ * It will also kick the crawler once per minute regardless, waiting a minute
+ * longer for each time it has no work to do, up to an hour wait time.
+ * The latter is to avoid newly started daemons from waiting too long before
+ * retrying a crawl.
+ */
+static void lru_maintainer_crawler_check(struct crawler_expired_data *cdata, logger *l) {
+    int i;
+    static rel_time_t next_crawls[POWER_LARGEST];
+    static rel_time_t next_crawl_wait[POWER_LARGEST];
+    uint8_t todo[POWER_LARGEST];
+    memset(todo, 0, sizeof(uint8_t) * POWER_LARGEST);
+    bool do_run = false;
+    unsigned int tocrawl_limit = 0;
+
+    // TODO: If not segmented LRU, skip non-cold
+    for (i = POWER_SMALLEST; i < POWER_LARGEST; i++) {
+        crawlerstats_t *s = &cdata->crawlerstats[i];
+        /* We've not successfully kicked off a crawl yet. */
+        if (s->run_complete) {
+            char *lru_name = "na";
+            pthread_mutex_lock(&cdata->lock);
+            int x;
+            /* Should we crawl again? */
+            uint64_t possible_reclaims = s->seen - s->noexp;
+            uint64_t available_reclaims = 0;
+            /* Need to think we can free at least 1% of the items before
+             * crawling. */
+            /* FIXME: Configurable? */
+            uint64_t low_watermark = (possible_reclaims / 100) + 1;
+            rel_time_t since_run = current_time - s->end_time;
+            /* Don't bother if the payoff is too low. */
+            for (x = 0; x < 60; x++) {
+                available_reclaims += s->histo[x];
+                if (available_reclaims > low_watermark) {
+                    if (next_crawl_wait[i] < (x * 60)) {
+                        next_crawl_wait[i] += 60;
+                    } else if (next_crawl_wait[i] >= 60) {
+                        next_crawl_wait[i] -= 60;
+                    }
+                    break;
+                }
+            }
+
+            if (available_reclaims == 0) {
+                next_crawl_wait[i] += 60;
+            }
+
+            if (next_crawl_wait[i] > MAX_MAINTCRAWL_WAIT) {
+                next_crawl_wait[i] = MAX_MAINTCRAWL_WAIT;
+            }
+
+            next_crawls[i] = current_time + next_crawl_wait[i] + 5;
+            switch (GET_LRU(i)) {
+                case HOT_LRU:
+                    lru_name = "hot";
+                    break;
+                case WARM_LRU:
+                    lru_name = "warm";
+                    break;
+                case COLD_LRU:
+                    lru_name = "cold";
+                    break;
+                case TEMP_LRU:
+                    lru_name = "temp";
+                    break;
+            }
+            LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_CRAWLER_STATUS, NULL,
+                    CLEAR_LRU(i),
+                    lru_name,
+                    (unsigned long long)low_watermark,
+                    (unsigned long long)available_reclaims,
+                    (unsigned int)since_run,
+                    next_crawls[i] - current_time,
+                    s->end_time - s->start_time,
+                    s->seen,
+                    s->reclaimed);
+            // Got our calculation, avoid running until next actual run.
+            s->run_complete = false;
+            pthread_mutex_unlock(&cdata->lock);
+        }
+        if (current_time > next_crawls[i]) {
+            pthread_mutex_lock(&lru_locks[i]);
+            if (sizes[i] > tocrawl_limit) {
+                tocrawl_limit = sizes[i];
+            }
+            pthread_mutex_unlock(&lru_locks[i]);
+            todo[i] = 1;
+            do_run = true;
+            next_crawls[i] = current_time + 5; // minimum retry wait.
+        }
+    }
+    if (do_run) {
+        if (settings.lru_crawler_tocrawl && settings.lru_crawler_tocrawl < tocrawl_limit) {
+            tocrawl_limit = settings.lru_crawler_tocrawl;
+        }
+        lru_crawler_start(todo, tocrawl_limit, CRAWLER_AUTOEXPIRE, cdata, NULL, 0);
+    }
+}
+
+slab_automove_reg_t slab_automove_default = {
+    .init = slab_automove_init,
+    .free = slab_automove_free,
+    .run = slab_automove_run
+};
+#ifdef EXTSTORE
+slab_automove_reg_t slab_automove_extstore = {
+    .init = slab_automove_extstore_init,
+    .free = slab_automove_extstore_free,
+    .run = slab_automove_extstore_run
+};
+#endif
+static pthread_t lru_maintainer_tid;
+
+#define MAX_LRU_MAINTAINER_SLEEP 1000000
+#define MIN_LRU_MAINTAINER_SLEEP 1000
+
+static void *lru_maintainer_thread(void *arg) {
+    slab_automove_reg_t *sam = &slab_automove_default;
+#ifdef EXTSTORE
+    void *storage = arg;
+    if (storage != NULL)
+        sam = &slab_automove_extstore;
+#endif
+    int i;
+    useconds_t to_sleep = MIN_LRU_MAINTAINER_SLEEP;
+    useconds_t last_sleep = MIN_LRU_MAINTAINER_SLEEP;
+    rel_time_t last_crawler_check = 0;
+    rel_time_t last_automove_check = 0;
+    useconds_t next_juggles[MAX_NUMBER_OF_SLAB_CLASSES] = {0};
+    useconds_t backoff_juggles[MAX_NUMBER_OF_SLAB_CLASSES] = {0};
+    struct crawler_expired_data *cdata =
+        calloc(1, sizeof(struct crawler_expired_data));
+    if (cdata == NULL) {
+        fprintf(stderr, "Failed to allocate crawler data for LRU maintainer thread\n");
+        abort();
+    }
+    pthread_mutex_init(&cdata->lock, NULL);
+    cdata->crawl_complete = true; // kick off the crawler.
+    logger *l = logger_create();
+    if (l == NULL) {
+        fprintf(stderr, "Failed to allocate logger for LRU maintainer thread\n");
+        abort();
+    }
+
+    double last_ratio = settings.slab_automove_ratio;
+    void *am = sam->init(&settings);
+
+    pthread_mutex_lock(&lru_maintainer_lock);
+    if (settings.verbose > 2)
+        fprintf(stderr, "Starting LRU maintainer background thread\n");
+    while (do_run_lru_maintainer_thread) {
+        pthread_mutex_unlock(&lru_maintainer_lock);
+        if (to_sleep)
+            usleep(to_sleep);
+        pthread_mutex_lock(&lru_maintainer_lock);
+        /* A sleep of zero counts as a minimum of a 1ms wait */
+        last_sleep = to_sleep > 1000 ? to_sleep : 1000;
+        to_sleep = MAX_LRU_MAINTAINER_SLEEP;
+
+        STATS_LOCK();
+        stats.lru_maintainer_juggles++;
+        STATS_UNLOCK();
+
+        /* Each slab class gets its own sleep to avoid hammering locks */
+        for (i = POWER_SMALLEST; i < MAX_NUMBER_OF_SLAB_CLASSES; i++) {
+            next_juggles[i] = next_juggles[i] > last_sleep ? next_juggles[i] - last_sleep : 0;
+
+            if (next_juggles[i] > 0) {
+                // Sleep the thread just for the minimum amount (or not at all)
+                if (next_juggles[i] < to_sleep)
+                    to_sleep = next_juggles[i];
+                continue;
+            }
+
+            int did_moves = lru_maintainer_juggle(i);
+            if (did_moves == 0) {
+                if (backoff_juggles[i] != 0) {
+                    backoff_juggles[i] += backoff_juggles[i] / 8;
+                } else {
+                    backoff_juggles[i] = MIN_LRU_MAINTAINER_SLEEP;
+                }
+                if (backoff_juggles[i] > MAX_LRU_MAINTAINER_SLEEP)
+                    backoff_juggles[i] = MAX_LRU_MAINTAINER_SLEEP;
+            } else if (backoff_juggles[i] > 0) {
+                backoff_juggles[i] /= 2;
+                if (backoff_juggles[i] < MIN_LRU_MAINTAINER_SLEEP) {
+                    backoff_juggles[i] = 0;
+                }
+            }
+            next_juggles[i] = backoff_juggles[i];
+            if (next_juggles[i] < to_sleep)
+                to_sleep = next_juggles[i];
+        }
+
+        /* Minimize the sleep if we had async LRU bumps to process */
+        if (settings.lru_segmented && lru_maintainer_bumps() && to_sleep > 1000) {
+            to_sleep = 1000;
+        }
+
+        /* Once per second at most */
+        if (settings.lru_crawler && last_crawler_check != current_time) {
+            lru_maintainer_crawler_check(cdata, l);
+            last_crawler_check = current_time;
+        }
+
+        if (settings.slab_automove == 1 && last_automove_check != current_time) {
+            if (last_ratio != settings.slab_automove_ratio) {
+                sam->free(am);
+                am = sam->init(&settings);
+                last_ratio = settings.slab_automove_ratio;
+            }
+            int src, dst;
+            sam->run(am, &src, &dst);
+            if (src != -1 && dst != -1) {
+                slabs_reassign(src, dst);
+                LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_SLAB_MOVE, NULL,
+                        src, dst);
+            }
+            // dst == 0 means reclaim to global pool, be more aggressive
+            if (dst != 0) {
+                last_automove_check = current_time;
+            } else if (dst == 0) {
+                // also ensure we minimize the thread sleep
+                to_sleep = 1000;
+            }
+        }
+    }
+    pthread_mutex_unlock(&lru_maintainer_lock);
+    sam->free(am);
+    // LRU crawler *must* be stopped.
+    free(cdata);
+    if (settings.verbose > 2)
+        fprintf(stderr, "LRU maintainer thread stopping\n");
+
+    return NULL;
+}
+
+int stop_lru_maintainer_thread(void) {
+    int ret;
+    pthread_mutex_lock(&lru_maintainer_lock);
+    /* LRU thread is a sleep loop, will die on its own */
+    do_run_lru_maintainer_thread = 0;
+    pthread_mutex_unlock(&lru_maintainer_lock);
+    if ((ret = pthread_join(lru_maintainer_tid, NULL)) != 0) {
+        fprintf(stderr, "Failed to stop LRU maintainer thread: %s\n", strerror(ret));
+        return -1;
+    }
+    settings.lru_maintainer_thread = false;
+    return 0;
+}
+
+int start_lru_maintainer_thread(void *arg) {
+    int ret;
+    pthread_mutex_lock(&lru_maintainer_lock);
+    do_run_lru_maintainer_thread = 1;
+    settings.lru_maintainer_thread = true;
+    if ((ret = pthread_create(&lru_maintainer_tid, NULL,
+        lru_maintainer_thread, arg)) != 0) {
+        fprintf(stderr, "Can't create LRU maintainer thread: %s\n",
+            strerror(ret));
+        pthread_mutex_unlock(&lru_maintainer_lock);
+        return -1;
+    }
+    pthread_mutex_unlock(&lru_maintainer_lock);
+
+    return 0;
+}
+
+/* If we hold this lock, crawler can't wake up or move */
+void lru_maintainer_pause(void) {
+    pthread_mutex_lock(&lru_maintainer_lock);
+}
+
+void lru_maintainer_resume(void) {
+    pthread_mutex_unlock(&lru_maintainer_lock);
+}
+
+int init_lru_maintainer(void) {
+    lru_maintainer_initialized = 1;
+    return 0;
+}
+
+/* Tail linkers and crawler for the LRU crawler. */
+void do_item_linktail_q(item *it) { /* item is the new tail */
+    item **head, **tail;
+    assert(it->it_flags == 1);
+    assert(it->nbytes == 0);
+
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+    //assert(*tail != 0);
+    assert(it != *tail);
+    assert((*head && *tail) || (*head == 0 && *tail == 0));
+    it->prev = *tail;
+    it->next = 0;
+    if (it->prev) {
+        assert(it->prev->next == 0);
+        it->prev->next = it;
+    }
+    *tail = it;
+    if (*head == 0) *head = it;
+    return;
+}
+
+void do_item_unlinktail_q(item *it) {
+    item **head, **tail;
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+
+    if (*head == it) {
+        assert(it->prev == 0);
+        *head = it->next;
+    }
+    if (*tail == it) {
+        assert(it->next == 0);
+        *tail = it->prev;
+    }
+    assert(it->next != it);
+    assert(it->prev != it);
+
+    if (it->next) it->next->prev = it->prev;
+    if (it->prev) it->prev->next = it->next;
+    return;
+}
+
+/* This is too convoluted, but it's a difficult shuffle. Try to rewrite it
+ * more clearly. */
+item *do_item_crawl_q(item *it) {
+    item **head, **tail;
+    assert(it->it_flags == 1);
+    assert(it->nbytes == 0);
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+
+    /* We've hit the head, pop off */
+    if (it->prev == 0) {
+        assert(*head == it);
+        if (it->next) {
+            *head = it->next;
+            assert(it->next->prev == it);
+            it->next->prev = 0;
+        }
+        return NULL; /* Done */
+    }
+
+    /* Swing ourselves in front of the next item */
+    /* NB: If there is a prev, we can't be the head */
+    assert(it->prev != it);
+    if (it->prev) {
+        if (*head == it->prev) {
+            /* Prev was the head, now we're the head */
+            *head = it;
+        }
+        if (*tail == it) {
+            /* We are the tail, now they are the tail */
+            *tail = it->prev;
+        }
+        assert(it->next != it);
+        if (it->next) {
+            assert(it->prev->next == it);
+            it->prev->next = it->next;
+            it->next->prev = it->prev;
+        } else {
+            /* Tail. Move this above? */
+            it->prev->next = 0;
+        }
+        /* prev->prev's next is it->prev */
+        it->next = it->prev;
+        it->prev = it->next->prev;
+        it->next->prev = it;
+        /* New it->prev now, if we're not at the head. */
+        if (it->prev) {
+            it->prev->next = it;
+        }
+    }
+    assert(it->next != it);
+    assert(it->prev != it);
+
+    return it->next; /* success */
+}
diff --git a/cachelib/external/memcached/items.h b/cachelib/external/memcached/items.h
new file mode 100644
index 0000000000..34a0867ca0
--- /dev/null
+++ b/cachelib/external/memcached/items.h
@@ -0,0 +1,100 @@
+#define HOT_LRU 0
+#define WARM_LRU 64
+#define COLD_LRU 128
+#define TEMP_LRU 192
+
+#define CLEAR_LRU(id) (id & ~(3<<6))
+#define GET_LRU(id) (id & (3<<6))
+#include <stdbool.h>
+
+/* See items.c */
+uint64_t get_cas_id(void);
+void set_cas_id(uint64_t new_cas);
+
+/*@null@*/
+item *do_item_alloc(char *key, const size_t nkey, const unsigned int flags, const rel_time_t exptime, const int nbytes);
+item_chunk *do_item_alloc_chunk(item_chunk *ch, const size_t bytes_remain);
+item *do_item_alloc_pull(const size_t ntotal, const unsigned int id);
+void item_free(item *it);
+bool item_size_ok(const size_t nkey, const int flags, const int nbytes);
+
+int  do_item_link(item *it, const uint32_t hv);     /** may fail if transgresses limits */
+void do_item_unlink(item *it, const uint32_t hv);
+void do_item_unlink_nolock(item *it, const uint32_t hv);
+void do_item_remove(item *it);
+void do_item_update(item *it);   /** update LRU time to current and reposition */
+void do_item_update_nolock(item *it);
+int  do_item_replace(item *it, item *new_it, const uint32_t hv);
+void do_item_link_fixup(item *it);
+
+int item_is_flushed(item *it);
+unsigned int do_get_lru_size(uint32_t id);
+
+void do_item_linktail_q(item *it);
+void do_item_unlinktail_q(item *it);
+item *do_item_crawl_q(item *it);
+
+void *item_lru_bump_buf_create(void);
+
+#define LRU_PULL_EVICT 1
+#define LRU_PULL_CRAWL_BLOCKS 2
+#define LRU_PULL_RETURN_ITEM 4 /* fill info struct if available */
+
+struct lru_pull_tail_return {
+    item *it;
+    uint32_t hv;
+};
+
+int lru_pull_tail(const int orig_id, const int cur_lru,
+        const uint64_t total_bytes, const uint8_t flags, const rel_time_t max_age,
+        struct lru_pull_tail_return *ret_it);
+
+/*@null@*/
+char *item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes);
+void item_stats(ADD_STAT add_stats, void *c);
+void do_item_stats_add_crawl(const int i, const uint64_t reclaimed,
+        const uint64_t unfetched, const uint64_t checked);
+void item_stats_totals(ADD_STAT add_stats, void *c);
+/*@null@*/
+void item_stats_sizes(ADD_STAT add_stats, void *c);
+void item_stats_sizes_init(void);
+void item_stats_sizes_enable(ADD_STAT add_stats, void *c);
+void item_stats_sizes_disable(ADD_STAT add_stats, void *c);
+void item_stats_sizes_add(item *it);
+void item_stats_sizes_remove(item *it);
+bool item_stats_sizes_status(void);
+
+/* stats getter for slab automover */
+typedef struct {
+    int64_t evicted;
+    int64_t outofmemory;
+    uint32_t age;
+} item_stats_automove;
+void fill_item_stats_automove(item_stats_automove *am);
+
+item *do_item_get(const char *key, const size_t nkey, const uint32_t hv, conn *c, const bool do_update);
+item *do_item_touch(const char *key, const size_t nkey, uint32_t exptime, const uint32_t hv, conn *c);
+void do_item_bump(conn *c, item *it, const uint32_t hv);
+void item_stats_reset(void);
+extern pthread_mutex_t lru_locks[POWER_LARGEST];
+
+int start_lru_maintainer_thread(void *arg);
+int stop_lru_maintainer_thread(void);
+int init_lru_maintainer(void);
+void lru_maintainer_pause(void);
+void lru_maintainer_resume(void);
+
+void *lru_bump_buf_create(void);
+
+#ifdef EXTSTORE
+#define STORAGE_delete(e, it) \
+    do { \
+        if (it->it_flags & ITEM_HDR) { \
+            item_hdr *hdr = (item_hdr *)ITEM_data(it); \
+            extstore_delete(e, hdr->page_id, hdr->page_version, \
+                    1, ITEM_ntotal(it)); \
+        } \
+    } while (0)
+#else
+#define STORAGE_delete(...)
+#endif
diff --git a/cachelib/external/memcached/itoa_ljust.c b/cachelib/external/memcached/itoa_ljust.c
new file mode 100644
index 0000000000..7921f7fd00
--- /dev/null
+++ b/cachelib/external/memcached/itoa_ljust.c
@@ -0,0 +1,149 @@
+//=== itoa_ljust.cpp - Fast integer to ascii conversion           --*- C++ -*-//
+//
+// Substantially simplified (and slightly faster) version
+// based on the following functions in Google's protocol buffers:
+//
+//    FastInt32ToBufferLeft()
+//    FastUInt32ToBufferLeft()
+//    FastInt64ToBufferLeft()
+//    FastUInt64ToBufferLeft()
+//
+// Differences:
+//    1) Greatly simplified
+//    2) Avoids GOTO statements - uses "switch" instead and relies on
+//       compiler constant folding and propagation for high performance
+//    3) Avoids unary minus of signed types - undefined behavior if value
+//       is INT_MIN in platforms using two's complement representation
+//    4) Uses memcpy to store 2 digits at a time - lets the compiler
+//       generate a 2-byte load/store in platforms that support
+//       unaligned access, this is faster (and less code) than explicitly
+//       loading and storing each byte
+//
+// Copyright (c) 2016 Arturo Martin-de-Nicolas
+// arturomdn@gmail.com
+// https://github.com/amdn/itoa_ljust/
+//
+// Released under the BSD 3-Clause License, see Google's original copyright
+// and license below.
+//===----------------------------------------------------------------------===//
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//===----------------------------------------------------------------------===//
+
+#include "itoa_ljust.h"
+#include <string.h>
+
+static const char lut[201] =
+    "0001020304050607080910111213141516171819"
+    "2021222324252627282930313233343536373839"
+    "4041424344454647484950515253545556575859"
+    "6061626364656667686970717273747576777879"
+    "8081828384858687888990919293949596979899";
+
+#define dd(u) ((const uint16_t)(lut[u]))
+
+static inline char* out2(const int d, char* p) {
+    memcpy(p, &((uint16_t *)lut)[d], 2);
+    return p + 2;
+}
+
+static inline char* out1(const char in, char* p) {
+    memcpy(p, &in, 1);
+    return p + 1;
+}
+
+static inline int digits( uint32_t u, unsigned k, int* d, char** p, int n ) {
+    if (u < k*10) {
+        *d = u / k;
+        *p = out1('0'+*d, *p);
+        --n;
+    }
+    return n;
+}
+
+static inline char* itoa(uint32_t u, char* p, int d, int n) {
+    switch(n) {
+    case 10: d  = u / 100000000; p = out2( d, p );
+    case  9: u -= d * 100000000;
+    case  8: d  = u /   1000000; p = out2( d, p );
+    case  7: u -= d *   1000000;
+    case  6: d  = u /     10000; p = out2( d, p );
+    case  5: u -= d *     10000;
+    case  4: d  = u /       100; p = out2( d, p );
+    case  3: u -= d *       100;
+    case  2: d  = u /         1; p = out2( d, p );
+    case  1: ;
+    }
+    *p = '\0';
+    return p;
+}
+
+char* itoa_u32(uint32_t u, char* p) {
+    int d = 0,n;
+         if (u >=100000000) n = digits(u, 100000000, &d, &p, 10);
+    else if (u <       100) n = digits(u,         1, &d, &p,  2);
+    else if (u <     10000) n = digits(u,       100, &d, &p,  4);
+    else if (u <   1000000) n = digits(u,     10000, &d, &p,  6);
+    else                    n = digits(u,   1000000, &d, &p,  8);
+    return itoa( u, p, d, n );
+}
+
+char* itoa_32(int32_t i, char* p) {
+    uint32_t u = i;
+    if (i < 0) {
+        *p++ = '-';
+        u = -u;
+    }
+    return itoa_u32(u, p);
+}
+
+char* itoa_u64(uint64_t u, char* p) {
+    int d;
+
+    uint32_t lower = (uint32_t)u;
+    if (lower == u) return itoa_u32(lower, p);
+
+    uint64_t upper = u / 1000000000;
+    p = itoa_u64(upper, p);
+    lower = u - (upper * 1000000000);
+    d = lower / 100000000;
+    p = out1('0'+d,p);
+    return itoa( lower, p, d, 9 );
+}
+
+char* itoa_64(int64_t i, char* p) {
+    uint64_t u = i;
+    if (i < 0) {
+        *p++ = '-';
+        u = -u;
+    }
+    return itoa_u64(u, p);
+}
diff --git a/cachelib/external/memcached/itoa_ljust.h b/cachelib/external/memcached/itoa_ljust.h
new file mode 100644
index 0000000000..18fc94ded6
--- /dev/null
+++ b/cachelib/external/memcached/itoa_ljust.h
@@ -0,0 +1,28 @@
+#ifndef ITOA_LJUST_H
+#define ITOA_LJUST_H
+
+//=== itoa_ljust.h - Fast integer to ascii conversion
+//
+// Fast and simple integer to ASCII conversion:
+//
+//   - 32 and 64-bit integers
+//   - signed and unsigned
+//   - user supplied buffer must be large enough for all decimal digits
+//     in value plus minus sign if negative
+//   - left-justified
+//   - NUL terminated
+//   - return value is pointer to NUL terminator
+//
+// Copyright (c) 2016 Arturo Martin-de-Nicolas
+// arturomdn@gmail.com
+// https://github.com/amdn/itoa_ljust/
+//===----------------------------------------------------------------------===//
+
+#include <stdint.h>
+
+char* itoa_u32(uint32_t u, char* buffer);
+char* itoa_32( int32_t i, char* buffer);
+char* itoa_u64(uint64_t u, char* buffer);
+char* itoa_64( int64_t i, char* buffer);
+
+#endif // ITOA_LJUST_H
diff --git a/cachelib/external/memcached/jenkins_hash.c b/cachelib/external/memcached/jenkins_hash.c
new file mode 100644
index 0000000000..3631dd01ea
--- /dev/null
+++ b/cachelib/external/memcached/jenkins_hash.c
@@ -0,0 +1,431 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Hash table
+ *
+ * The hash function used here is by Bob Jenkins, 1996:
+ *    <http://burtleburtle.net/bob/hash/doobs.html>
+ *       "By Bob Jenkins, 1996.  bob_jenkins@burtleburtle.net.
+ *       You may use this code any way you wish, private, educational,
+ *       or commercial.  It's free."
+ *
+ */
+#include "memcached.h"
+#include "jenkins_hash.h"
+
+/*
+ * Since the hash function does bit manipulation, it needs to know
+ * whether it's big or little-endian. ENDIAN_LITTLE and ENDIAN_BIG
+ * are set in the configure script.
+ */
+#if ENDIAN_BIG == 1
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 1
+#else
+# if ENDIAN_LITTLE == 1
+#  define HASH_LITTLE_ENDIAN 1
+#  define HASH_BIG_ENDIAN 0
+# else
+#  define HASH_LITTLE_ENDIAN 0
+#  define HASH_BIG_ENDIAN 0
+# endif
+#endif
+
+#define rot(x,k) (((x)<<(k)) ^ ((x)>>(32-(k))))
+
+/*
+-------------------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+
+This is reversible, so any information in (a,b,c) before mix() is
+still in (a,b,c) after mix().
+
+If four pairs of (a,b,c) inputs are run through mix(), or through
+mix() in reverse, there are at least 32 bits of the output that
+are sometimes the same for one pair and different for another pair.
+This was tested for:
+* pairs that differed by one bit, by two bits, in any combination
+  of top bits of (a,b,c), or in any combination of bottom bits of
+  (a,b,c).
+* "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+  the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+  is commonly produced by subtraction) look like a single 1-bit
+  difference.
+* the base values were pseudorandom, all zero but one bit set, or
+  all zero plus a counter that starts at zero.
+
+Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
+satisfy this are
+    4  6  8 16 19  4
+    9 15  3 18 27 15
+   14  9  3  7 17  3
+Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+for "differ" defined as + with a one-bit base and a two-bit delta.  I
+used http://burtleburtle.net/bob/hash/avalanche.html to choose
+the operations, constants, and arrangements of the variables.
+
+This does not achieve avalanche.  There are input bits of (a,b,c)
+that fail to affect some output bits of (a,b,c), especially of a.  The
+most thoroughly mixed value is c, but it doesn't really even achieve
+avalanche in c.
+
+This allows some parallelism.  Read-after-writes are good at doubling
+the number of bits affected, so the goal of mixing pulls in the opposite
+direction as the goal of parallelism.  I did what I could.  Rotates
+seem to cost as much as shifts on every machine I could lay my hands
+on, and rotates are much kinder to the top and bottom bits, so I used
+rotates.
+-------------------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+  a -= c;  a ^= rot(c, 4);  c += b; \
+  b -= a;  b ^= rot(a, 6);  a += c; \
+  c -= b;  c ^= rot(b, 8);  b += a; \
+  a -= c;  a ^= rot(c,16);  c += b; \
+  b -= a;  b ^= rot(a,19);  a += c; \
+  c -= b;  c ^= rot(b, 4);  b += a; \
+}
+
+/*
+-------------------------------------------------------------------------------
+final -- final mixing of 3 32-bit values (a,b,c) into c
+
+Pairs of (a,b,c) values differing in only a few bits will usually
+produce values of c that look totally different.  This was tested for
+* pairs that differed by one bit, by two bits, in any combination
+  of top bits of (a,b,c), or in any combination of bottom bits of
+  (a,b,c).
+* "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+  the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+  is commonly produced by subtraction) look like a single 1-bit
+  difference.
+* the base values were pseudorandom, all zero but one bit set, or
+  all zero plus a counter that starts at zero.
+
+These constants passed:
+ 14 11 25 16 4 14 24
+ 12 14 25 16 4 14 24
+and these came close:
+  4  8 15 26 3 22 24
+ 10  8 15 26 3 22 24
+ 11  8 15 26 3 22 24
+-------------------------------------------------------------------------------
+*/
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c,4);  \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+#if HASH_LITTLE_ENDIAN == 1
+uint32_t jenkins_hash(
+  const void *key,       /* the key to hash */
+  size_t      length)    /* length of the key */
+{
+  uint32_t a,b,c;                                          /* internal state */
+  union { const void *ptr; size_t i; } u;     /* needed for Mac Powerbook G4 */
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)length) + 0;
+
+  u.ptr = key;
+  if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+    const uint32_t *k = key;                           /* read 32-bit chunks */
+#ifdef VALGRIND
+    const uint8_t  *k8;
+#endif /* ifdef VALGRIND */
+
+    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      b += k[1];
+      c += k[2];
+      mix(a,b,c);
+      length -= 12;
+      k += 3;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    /*
+     * "k[2]&0xffffff" actually reads beyond the end of the string, but
+     * then masks off the part it's not allowed to read.  Because the
+     * string is aligned, the masked-off tail is in the same word as the
+     * rest of the string.  Every machine with memory protection I've seen
+     * does it on word boundaries, so is OK with this.  But VALGRIND will
+     * still catch it and complain.  The masking trick does make the hash
+     * noticeably faster for short strings (like English words).
+     */
+#ifndef VALGRIND
+
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+    case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+    case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+    case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+    case 5 : b+=k[1]&0xff; a+=k[0]; break;
+    case 4 : a+=k[0]; break;
+    case 3 : a+=k[0]&0xffffff; break;
+    case 2 : a+=k[0]&0xffff; break;
+    case 1 : a+=k[0]&0xff; break;
+    case 0 : return c;  /* zero length strings require no mixing */
+    }
+
+#else /* make valgrind happy */
+
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=((uint32_t)k8[10])<<16;  /* fall through */
+    case 10: c+=((uint32_t)k8[9])<<8;    /* fall through */
+    case 9 : c+=k8[8];                   /* fall through */
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=((uint32_t)k8[6])<<16;   /* fall through */
+    case 6 : b+=((uint32_t)k8[5])<<8;    /* fall through */
+    case 5 : b+=k8[4];                   /* fall through */
+    case 4 : a+=k[0]; break;
+    case 3 : a+=((uint32_t)k8[2])<<16;   /* fall through */
+    case 2 : a+=((uint32_t)k8[1])<<8;    /* fall through */
+    case 1 : a+=k8[0]; break;
+    case 0 : return c;  /* zero length strings require no mixing */
+    }
+
+#endif /* !valgrind */
+
+  } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+    const uint16_t *k = key;                           /* read 16-bit chunks */
+    const uint8_t  *k8;
+
+    /*--------------- all but last block: aligned reads and different mixing */
+    while (length > 12)
+    {
+      a += k[0] + (((uint32_t)k[1])<<16);
+      b += k[2] + (((uint32_t)k[3])<<16);
+      c += k[4] + (((uint32_t)k[5])<<16);
+      mix(a,b,c);
+      length -= 12;
+      k += 6;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 11: c+=((uint32_t)k8[10])<<16;     /* @fallthrough */
+    case 10: c+=k[4];                       /* @fallthrough@ */
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 9 : c+=k8[8];                      /* @fallthrough */
+    case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 7 : b+=((uint32_t)k8[6])<<16;      /* @fallthrough */
+    case 6 : b+=k[2];
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 5 : b+=k8[4];                      /* @fallthrough */
+    case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 3 : a+=((uint32_t)k8[2])<<16;      /* @fallthrough */
+    case 2 : a+=k[0];
+             break;
+    case 1 : a+=k8[0];
+             break;
+    case 0 : return c;  /* zero length strings require no mixing */
+    }
+
+  } else {                        /* need to read the key one byte at a time */
+    const uint8_t *k = key;
+
+    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      a += ((uint32_t)k[1])<<8;
+      a += ((uint32_t)k[2])<<16;
+      a += ((uint32_t)k[3])<<24;
+      b += k[4];
+      b += ((uint32_t)k[5])<<8;
+      b += ((uint32_t)k[6])<<16;
+      b += ((uint32_t)k[7])<<24;
+      c += k[8];
+      c += ((uint32_t)k[9])<<8;
+      c += ((uint32_t)k[10])<<16;
+      c += ((uint32_t)k[11])<<24;
+      mix(a,b,c);
+      length -= 12;
+      k += 12;
+    }
+
+    /*-------------------------------- last block: affect all 32 bits of (c) */
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=((uint32_t)k[11])<<24;
+    case 11: c+=((uint32_t)k[10])<<16;
+    case 10: c+=((uint32_t)k[9])<<8;
+    case 9 : c+=k[8];
+    case 8 : b+=((uint32_t)k[7])<<24;
+    case 7 : b+=((uint32_t)k[6])<<16;
+    case 6 : b+=((uint32_t)k[5])<<8;
+    case 5 : b+=k[4];
+    case 4 : a+=((uint32_t)k[3])<<24;
+    case 3 : a+=((uint32_t)k[2])<<16;
+    case 2 : a+=((uint32_t)k[1])<<8;
+    case 1 : a+=k[0];
+             break;
+    case 0 : return c;  /* zero length strings require no mixing */
+    }
+  }
+
+  final(a,b,c);
+  return c;             /* zero length strings require no mixing */
+}
+
+#elif HASH_BIG_ENDIAN == 1
+/*
+ * hashbig():
+ * This is the same as hashword() on big-endian machines.  It is different
+ * from hashlittle() on all machines.  hashbig() takes advantage of
+ * big-endian byte ordering.
+ */
+uint32_t jenkins_hash( const void *key, size_t length)
+{
+  uint32_t a,b,c;
+  union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)length) + 0;
+
+  u.ptr = key;
+  if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
+    const uint32_t *k = key;                           /* read 32-bit chunks */
+#ifdef VALGRIND
+    const uint8_t  *k8;
+#endif /* ifdef VALGRIND */
+
+    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      b += k[1];
+      c += k[2];
+      mix(a,b,c);
+      length -= 12;
+      k += 3;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    /*
+     * "k[2]<<8" actually reads beyond the end of the string, but
+     * then shifts out the part it's not allowed to read.  Because the
+     * string is aligned, the illegal read is in the same word as the
+     * rest of the string.  Every machine with memory protection I've seen
+     * does it on word boundaries, so is OK with this.  But VALGRIND will
+     * still catch it and complain.  The masking trick does make the hash
+     * noticeably faster for short strings (like English words).
+     */
+#ifndef VALGRIND
+
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
+    case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
+    case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=k[1]&0xffffff00; a+=k[0]; break;
+    case 6 : b+=k[1]&0xffff0000; a+=k[0]; break;
+    case 5 : b+=k[1]&0xff000000; a+=k[0]; break;
+    case 4 : a+=k[0]; break;
+    case 3 : a+=k[0]&0xffffff00; break;
+    case 2 : a+=k[0]&0xffff0000; break;
+    case 1 : a+=k[0]&0xff000000; break;
+    case 0 : return c;              /* zero length strings require no mixing */
+    }
+
+#else  /* make valgrind happy */
+
+    k8 = (const uint8_t *)k;
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=((uint32_t)k8[10])<<8;  /* fall through */
+    case 10: c+=((uint32_t)k8[9])<<16;  /* fall through */
+    case 9 : c+=((uint32_t)k8[8])<<24;  /* fall through */
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=((uint32_t)k8[6])<<8;   /* fall through */
+    case 6 : b+=((uint32_t)k8[5])<<16;  /* fall through */
+    case 5 : b+=((uint32_t)k8[4])<<24;  /* fall through */
+    case 4 : a+=k[0]; break;
+    case 3 : a+=((uint32_t)k8[2])<<8;   /* fall through */
+    case 2 : a+=((uint32_t)k8[1])<<16;  /* fall through */
+    case 1 : a+=((uint32_t)k8[0])<<24; break;
+    case 0 : return c;
+    }
+
+#endif /* !VALGRIND */
+
+  } else {                        /* need to read the key one byte at a time */
+    const uint8_t *k = key;
+
+    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += ((uint32_t)k[0])<<24;
+      a += ((uint32_t)k[1])<<16;
+      a += ((uint32_t)k[2])<<8;
+      a += ((uint32_t)k[3]);
+      b += ((uint32_t)k[4])<<24;
+      b += ((uint32_t)k[5])<<16;
+      b += ((uint32_t)k[6])<<8;
+      b += ((uint32_t)k[7]);
+      c += ((uint32_t)k[8])<<24;
+      c += ((uint32_t)k[9])<<16;
+      c += ((uint32_t)k[10])<<8;
+      c += ((uint32_t)k[11]);
+      mix(a,b,c);
+      length -= 12;
+      k += 12;
+    }
+
+    /*-------------------------------- last block: affect all 32 bits of (c) */
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=k[11];
+    case 11: c+=((uint32_t)k[10])<<8;
+    case 10: c+=((uint32_t)k[9])<<16;
+    case 9 : c+=((uint32_t)k[8])<<24;
+    case 8 : b+=k[7];
+    case 7 : b+=((uint32_t)k[6])<<8;
+    case 6 : b+=((uint32_t)k[5])<<16;
+    case 5 : b+=((uint32_t)k[4])<<24;
+    case 4 : a+=k[3];
+    case 3 : a+=((uint32_t)k[2])<<8;
+    case 2 : a+=((uint32_t)k[1])<<16;
+    case 1 : a+=((uint32_t)k[0])<<24;
+             break;
+    case 0 : return c;
+    }
+  }
+
+  final(a,b,c);
+  return c;
+}
+#else /* HASH_XXX_ENDIAN == 1 */
+#error Must define HASH_BIG_ENDIAN or HASH_LITTLE_ENDIAN
+#endif /* HASH_XXX_ENDIAN == 1 */
diff --git a/cachelib/external/memcached/jenkins_hash.h b/cachelib/external/memcached/jenkins_hash.h
new file mode 100644
index 0000000000..b44fba1eef
--- /dev/null
+++ b/cachelib/external/memcached/jenkins_hash.h
@@ -0,0 +1,15 @@
+#ifndef JENKINS_HASH_H
+#define    JENKINS_HASH_H
+
+#ifdef    __cplusplus
+extern "C" {
+#endif
+
+uint32_t jenkins_hash(const void *key, size_t length);
+
+#ifdef    __cplusplus
+}
+#endif
+
+#endif    /* JENKINS_HASH_H */
+
diff --git a/cachelib/external/memcached/linux_priv.c b/cachelib/external/memcached/linux_priv.c
new file mode 100644
index 0000000000..401e02a4fe
--- /dev/null
+++ b/cachelib/external/memcached/linux_priv.c
@@ -0,0 +1,205 @@
+#include "config.h"
+#include <seccomp.h>
+#include <termios.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <string.h>
+#include "memcached.h"
+
+static char *kill_msg;
+// Make sure to preserve the ??? position in the string, or correct the offsets
+// in the syssig handler.
+#define KILL_MSG_STR "Seccomp policy failure. Caught syscall ???. This is " \
+    "either an exploit attempt, or your system is not supported yet.\n"
+
+static void handle_syssig(int signum, siginfo_t *si, void *thread_context) {
+#if defined(si_syscall)
+    int syscall_no = si->si_syscall;
+#else
+    // If system has no support for si_syscal, the information may not be
+    // precise.
+    int syscall_no = si->si_value.sival_int;
+#endif
+
+    // Replace the characters in the kill message with the syscall number. We
+    // can't safely printf (even write is not really valid, but we're crashing
+    // anyway).
+
+    kill_msg[39] = (syscall_no / 100) % 10 + '0';
+    kill_msg[40] = (syscall_no / 10) % 10 + '0';
+    kill_msg[41] = syscall_no % 10 + '0';
+    if (write(2, kill_msg, strlen(kill_msg)) == -1) {
+        // An error occurred, but we can't do anything about it here. This check
+        // is mostly to avoid the "ignoring return value of 'write'" error on
+        // distributions with broken gcc (no "ignore via cast to void" support).
+    }
+
+    // We can't use the nice exit() version because it causes at_exit handlers
+    // to be looked up and run. We can't take any locks while handling the
+    // signal, so _exit() is the only thing to do safely.
+    _exit(EXIT_FAILURE);
+}
+
+static const struct sigaction act = {
+    .sa_sigaction = handle_syssig,
+    .sa_flags = SA_SIGINFO,
+};
+
+void setup_privilege_violations_handler(void) {
+    kill_msg = malloc(strlen(KILL_MSG_STR)+1);
+    strcpy(kill_msg, KILL_MSG_STR);
+
+    sigaction(SIGSYS, &act, NULL);
+}
+
+// If anything crosses the policy, kill the process.
+#define DENY_ACTION SCMP_ACT_TRAP
+
+void drop_privileges(void) {
+    scmp_filter_ctx ctx = seccomp_init(DENY_ACTION);
+    if (ctx == NULL) {
+        return;
+    }
+
+    int rc = 0;
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sigreturn), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_wait), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_pwait), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept4), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(shmctl), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1, SCMP_A1(SCMP_CMP_EQ, TIOCGWINSZ));
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1, SCMP_A1(SCMP_CMP_EQ, TCGETS));
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clock_gettime), 0);
+#endif
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(gettimeofday), 0);
+
+#ifdef MEMCACHED_DEBUG
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(readv), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lseek), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);
+
+    if (settings.relaxed_privileges) {
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mkdir), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(access), 0);
+    }
+#endif
+
+    if (rc != 0) {
+        goto fail;
+    }
+
+    rc = seccomp_load(ctx);
+    if (rc < 0) {
+        goto fail;
+    }
+
+    seccomp_release(ctx);
+    return;
+
+fail:
+    seccomp_release(ctx);
+    fprintf(stderr, "Failed to set a seccomp profile on the main thread\n");
+    exit(EXIT_FAILURE);
+}
+
+void drop_worker_privileges(void) {
+    scmp_filter_ctx ctx = seccomp_init(DENY_ACTION);
+    if (ctx == NULL) {
+        return;
+    }
+
+    int rc = 0;
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sigreturn), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_wait), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_pwait), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(poll), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(readv), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpeername), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendmsg), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getrusage), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mremap), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvfrom), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1, SCMP_A1(SCMP_CMP_EQ, TIOCGWINSZ));
+
+    // for spawning the LRU crawler
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(set_robust_list), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(madvise), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
+
+    // stat
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockname), 0);
+    rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);
+
+    if (settings.shutdown_command) {
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(tgkill), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(tkill), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(gettid), 0);
+    }
+
+    if (settings.relaxed_privileges) {
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mkdir), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(access), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lseek), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 0);
+    } else {
+        // stdout
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, SCMP_A0(SCMP_CMP_EQ, 1));
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 1, SCMP_A0(SCMP_CMP_EQ, 1));
+        // stderr
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, SCMP_A0(SCMP_CMP_EQ, 2));
+        rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 1, SCMP_A0(SCMP_CMP_EQ, 2));
+    }
+
+    if (rc != 0) {
+        goto fail;
+    }
+
+    rc = seccomp_load(ctx);
+    if (rc < 0) {
+        goto fail;
+    }
+
+    seccomp_release(ctx);
+    return;
+
+fail:
+    seccomp_release(ctx);
+    fprintf(stderr, "Failed to set a seccomp profile on a worker thread\n");
+    exit(EXIT_FAILURE);
+}
diff --git a/cachelib/external/memcached/logger.c b/cachelib/external/memcached/logger.c
new file mode 100644
index 0000000000..a8c186704c
--- /dev/null
+++ b/cachelib/external/memcached/logger.c
@@ -0,0 +1,847 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <poll.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+#if defined(__sun)
+#include <atomic.h>
+#endif
+
+#include "config.h"
+#include "memcached.h"
+#include "bipbuffer.h"
+
+#ifdef LOGGER_DEBUG
+#define L_DEBUG(...) \
+    do { \
+        fprintf(stderr, __VA_ARGS__); \
+    } while (0)
+#else
+#define L_DEBUG(...)
+#endif
+
+
+/* TODO: put this in a struct and ditch the global vars. */
+static logger *logger_stack_head = NULL;
+static logger *logger_stack_tail = NULL;
+static unsigned int logger_count = 0;
+static volatile int do_run_logger_thread = 1;
+static pthread_t logger_tid;
+pthread_mutex_t logger_stack_lock = PTHREAD_MUTEX_INITIALIZER;
+
+pthread_key_t logger_key;
+
+#if !defined(HAVE_GCC_64ATOMICS) && !defined(__sun)
+pthread_mutex_t logger_atomics_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+#define WATCHER_LIMIT 20
+logger_watcher *watchers[20];
+struct pollfd watchers_pollfds[20];
+int watcher_count = 0;
+
+/* Should this go somewhere else? */
+static const entry_details default_entries[] = {
+    [LOGGER_ASCII_CMD] = {LOGGER_TEXT_ENTRY, 512, LOG_RAWCMDS, "<%d %s"},
+    [LOGGER_EVICTION] = {LOGGER_EVICTION_ENTRY, 512, LOG_EVICTIONS, NULL},
+    [LOGGER_ITEM_GET] = {LOGGER_ITEM_GET_ENTRY, 512, LOG_FETCHERS, NULL},
+    [LOGGER_ITEM_STORE] = {LOGGER_ITEM_STORE_ENTRY, 512, LOG_MUTATIONS, NULL},
+    [LOGGER_CRAWLER_STATUS] = {LOGGER_TEXT_ENTRY, 512, LOG_SYSEVENTS,
+        "type=lru_crawler crawler=%d lru=%s low_mark=%llu next_reclaims=%llu since_run=%u next_run=%d elapsed=%u examined=%llu reclaimed=%llu"
+    },
+    [LOGGER_SLAB_MOVE] = {LOGGER_TEXT_ENTRY, 512, LOG_SYSEVENTS,
+        "type=slab_move src=%d dst=%d"
+    },
+#ifdef EXTSTORE
+    [LOGGER_EXTSTORE_WRITE] = {LOGGER_EXT_WRITE_ENTRY, 512, LOG_EVICTIONS, NULL},
+    [LOGGER_COMPACT_START] = {LOGGER_TEXT_ENTRY, 512, LOG_SYSEVENTS,
+        "type=compact_start id=%lu version=%llu"
+    },
+    [LOGGER_COMPACT_ABORT] = {LOGGER_TEXT_ENTRY, 512, LOG_SYSEVENTS,
+        "type=compact_abort id=%lu"
+    },
+    [LOGGER_COMPACT_READ_START] = {LOGGER_TEXT_ENTRY, 512, LOG_SYSEVENTS,
+        "type=compact_read_start id=%lu offset=%llu"
+    },
+    [LOGGER_COMPACT_READ_END] = {LOGGER_TEXT_ENTRY, 512, LOG_SYSEVENTS,
+        "type=compact_read_end id=%lu offset=%llu rescues=%lu lost=%lu skipped=%lu"
+    },
+    [LOGGER_COMPACT_END] = {LOGGER_TEXT_ENTRY, 512, LOG_SYSEVENTS,
+        "type=compact_end id=%lu"
+    },
+    [LOGGER_COMPACT_FRAGINFO] = {LOGGER_TEXT_ENTRY, 512, LOG_SYSEVENTS,
+        "type=compact_fraginfo ratio=%.2f bytes=%lu"
+    },
+#endif
+};
+
+#define WATCHER_ALL -1
+static int logger_thread_poll_watchers(int force_poll, int watcher);
+
+/*************************
+ * Util functions shared between bg thread and workers
+ *************************/
+
+/* Logger GID's can be used by watchers to put logs back into strict order
+ */
+static uint64_t logger_gid = 0;
+uint64_t logger_get_gid(void) {
+#ifdef HAVE_GCC_64ATOMICS
+    return __sync_add_and_fetch(&logger_gid, 1);
+#elif defined(__sun)
+    return atomic_inc_64_nv(&logger_gid);
+#else
+    mutex_lock(&logger_atomics_mutex);
+    uint64_t res = ++logger_gid;
+    mutex_unlock(&logger_atomics_mutex);
+    return res;
+#endif
+}
+
+void logger_set_gid(uint64_t gid) {
+#ifdef HAVE_GCC_64ATOMICS
+    __sync_add_and_fetch(&logger_gid, gid);
+#elif defined(__sun)
+    atomic_add_64(&logger_gid);
+#else
+    mutex_lock(&logger_atomics_mutex);
+    logger_gid = gid;
+    mutex_unlock(&logger_atomics_mutex);
+#endif
+}
+
+/* TODO: genericize lists. would be nice to import queue.h if the impact is
+ * studied... otherwise can just write a local one.
+ */
+/* Add to the list of threads with a logger object */
+static void logger_link_q(logger *l) {
+    pthread_mutex_lock(&logger_stack_lock);
+    assert(l != logger_stack_head);
+
+    l->prev = 0;
+    l->next = logger_stack_head;
+    if (l->next) l->next->prev = l;
+    logger_stack_head = l;
+    if (logger_stack_tail == 0) logger_stack_tail = l;
+    logger_count++;
+    pthread_mutex_unlock(&logger_stack_lock);
+    return;
+}
+
+/* Remove from the list of threads with a logger object */
+/*static void logger_unlink_q(logger *l) {
+    pthread_mutex_lock(&logger_stack_lock);
+    if (logger_stack_head == l) {
+        assert(l->prev == 0);
+        logger_stack_head = l->next;
+    }
+    if (logger_stack_tail == l) {
+        assert(l->next == 0);
+        logger_stack_tail = l->prev;
+    }
+    assert(l->next != l);
+    assert(l->prev != l);
+
+    if (l->next) l->next->prev = l->prev;
+    if (l->prev) l->prev->next = l->next;
+    logger_count--;
+    pthread_mutex_unlock(&logger_stack_lock);
+    return;
+}*/
+
+/* Called with logger stack locked.
+ * Iterates over every watcher collecting enabled flags.
+ */
+static void logger_set_flags(void) {
+    logger *l = NULL;
+    int x = 0;
+    uint16_t f = 0; /* logger eflags */
+
+    for (x = 0; x < WATCHER_LIMIT; x++) {
+        logger_watcher *w = watchers[x];
+        if (w == NULL)
+            continue;
+
+        f |= w->eflags;
+    }
+    for (l = logger_stack_head; l != NULL; l=l->next) {
+        pthread_mutex_lock(&l->mutex);
+        l->eflags = f;
+        pthread_mutex_unlock(&l->mutex);
+    }
+    return;
+}
+
+/*************************
+ * Logger background thread functions. Aggregates per-worker buffers and
+ * writes to any watchers.
+ *************************/
+
+#define LOGGER_PARSE_SCRATCH 4096
+
+static int _logger_thread_parse_ise(logentry *e, char *scratch) {
+    int total;
+    const char *cmd = "na";
+    char keybuf[KEY_MAX_URI_ENCODED_LENGTH];
+    struct logentry_item_store *le = (struct logentry_item_store *) e->data;
+    const char * const status_map[] = {
+        "not_stored", "stored", "exists", "not_found", "too_large", "no_memory" };
+    const char * const cmd_map[] = {
+        "null", "add", "set", "replace", "append", "prepend", "cas" };
+
+    if (le->cmd <= 6)
+        cmd = cmd_map[le->cmd];
+
+    uriencode(le->key, keybuf, le->nkey, KEY_MAX_URI_ENCODED_LENGTH);
+    total = snprintf(scratch, LOGGER_PARSE_SCRATCH,
+            "ts=%d.%d gid=%llu type=item_store key=%s status=%s cmd=%s ttl=%u clsid=%u cfd=%d\n",
+            (int)e->tv.tv_sec, (int)e->tv.tv_usec, (unsigned long long) e->gid,
+            keybuf, status_map[le->status], cmd, le->ttl, le->clsid, le->sfd);
+    return total;
+}
+
+static int _logger_thread_parse_ige(logentry *e, char *scratch) {
+    int total;
+    struct logentry_item_get *le = (struct logentry_item_get *) e->data;
+    char keybuf[KEY_MAX_URI_ENCODED_LENGTH];
+    const char * const was_found_map[] = {
+        "not_found", "found", "flushed", "expired" };
+
+    uriencode(le->key, keybuf, le->nkey, KEY_MAX_URI_ENCODED_LENGTH);
+    total = snprintf(scratch, LOGGER_PARSE_SCRATCH,
+            "ts=%d.%d gid=%llu type=item_get key=%s status=%s clsid=%u cfd=%d\n",
+            (int)e->tv.tv_sec, (int)e->tv.tv_usec, (unsigned long long) e->gid,
+            keybuf, was_found_map[le->was_found], le->clsid, le->sfd);
+    return total;
+}
+
+static int _logger_thread_parse_ee(logentry *e, char *scratch) {
+    int total;
+    char keybuf[KEY_MAX_URI_ENCODED_LENGTH];
+    struct logentry_eviction *le = (struct logentry_eviction *) e->data;
+    uriencode(le->key, keybuf, le->nkey, KEY_MAX_URI_ENCODED_LENGTH);
+    total = snprintf(scratch, LOGGER_PARSE_SCRATCH,
+            "ts=%d.%d gid=%llu type=eviction key=%s fetch=%s ttl=%lld la=%d clsid=%u\n",
+            (int)e->tv.tv_sec, (int)e->tv.tv_usec, (unsigned long long) e->gid,
+            keybuf, (le->it_flags & ITEM_FETCHED) ? "yes" : "no",
+            (long long int)le->exptime, le->latime, le->clsid);
+
+    return total;
+}
+#ifdef EXTSTORE
+static int _logger_thread_parse_extw(logentry *e, char *scratch) {
+    int total;
+    char keybuf[KEY_MAX_URI_ENCODED_LENGTH];
+    struct logentry_ext_write *le = (struct logentry_ext_write *) e->data;
+    uriencode(le->key, keybuf, le->nkey, KEY_MAX_URI_ENCODED_LENGTH);
+    total = snprintf(scratch, LOGGER_PARSE_SCRATCH,
+            "ts=%d.%d gid=%llu type=extwrite key=%s fetch=%s ttl=%lld la=%d clsid=%u bucket=%u\n",
+            (int)e->tv.tv_sec, (int)e->tv.tv_usec, (unsigned long long) e->gid,
+            keybuf, (le->it_flags & ITEM_FETCHED) ? "yes" : "no",
+            (long long int)le->exptime, le->latime, le->clsid, le->bucket);
+
+    return total;
+}
+#endif
+/* Completes rendering of log line. */
+static enum logger_parse_entry_ret logger_thread_parse_entry(logentry *e, struct logger_stats *ls,
+        char *scratch, int *scratch_len) {
+    int total = 0;
+
+    switch (e->event) {
+        case LOGGER_TEXT_ENTRY:
+            total = snprintf(scratch, LOGGER_PARSE_SCRATCH, "ts=%d.%d gid=%llu %s\n",
+                        (int)e->tv.tv_sec, (int)e->tv.tv_usec,
+                        (unsigned long long) e->gid, (char *) e->data);
+            break;
+        case LOGGER_EVICTION_ENTRY:
+            total = _logger_thread_parse_ee(e, scratch);
+            break;
+#ifdef EXTSTORE
+        case LOGGER_EXT_WRITE_ENTRY:
+            total = _logger_thread_parse_extw(e, scratch);
+            break;
+#endif
+        case LOGGER_ITEM_GET_ENTRY:
+            total = _logger_thread_parse_ige(e, scratch);
+            break;
+        case LOGGER_ITEM_STORE_ENTRY:
+            total = _logger_thread_parse_ise(e, scratch);
+            break;
+
+    }
+
+    if (total >= LOGGER_PARSE_SCRATCH || total <= 0) {
+        L_DEBUG("LOGGER: Failed to flatten log entry!\n");
+        return LOGGER_PARSE_ENTRY_FAILED;
+    } else {
+        *scratch_len = total;
+    }
+
+    return LOGGER_PARSE_ENTRY_OK;
+}
+
+/* Writes flattened entry to available watchers */
+static void logger_thread_write_entry(logentry *e, struct logger_stats *ls,
+        char *scratch, int scratch_len) {
+    int x, total;
+    /* Write the line into available watchers with matching flags */
+    for (x = 0; x < WATCHER_LIMIT; x++) {
+        logger_watcher *w = watchers[x];
+        char *skip_scr = NULL;
+        if (w == NULL || (e->eflags & w->eflags) == 0)
+            continue;
+
+         /* Avoid poll()'ing constantly when buffer is full by resetting a
+         * flag periodically.
+         */
+        while (!w->failed_flush &&
+                (skip_scr = (char *) bipbuf_request(w->buf, scratch_len + 128)) == NULL) {
+            if (logger_thread_poll_watchers(0, x) <= 0) {
+                L_DEBUG("LOGGER: Watcher had no free space for line of size (%d)\n", scratch_len + 128);
+                w->failed_flush = true;
+            }
+        }
+
+        if (w->failed_flush) {
+            L_DEBUG("LOGGER: Fast skipped for watcher [%d] due to failed_flush\n", w->sfd);
+            w->skipped++;
+            ls->watcher_skipped++;
+            continue;
+        }
+
+        if (w->skipped > 0) {
+            total = snprintf(skip_scr, 128, "skipped=%llu\n", (unsigned long long) w->skipped);
+            if (total >= 128 || total <= 0) {
+                L_DEBUG("LOGGER: Failed to flatten skipped message into watcher [%d]\n", w->sfd);
+                w->skipped++;
+                ls->watcher_skipped++;
+                continue;
+            }
+            bipbuf_push(w->buf, total);
+            w->skipped = 0;
+        }
+        /* Can't fail because bipbuf_request succeeded. */
+        bipbuf_offer(w->buf, (unsigned char *) scratch, scratch_len);
+        ls->watcher_sent++;
+    }
+}
+
+/* Called with logger stack locked.
+ * Releases every chunk associated with a watcher and closes the connection.
+ * We can't presently send a connection back to the worker for further
+ * processing.
+ */
+static void logger_thread_close_watcher(logger_watcher *w) {
+    L_DEBUG("LOGGER: Closing dead watcher\n");
+    watchers[w->id] = NULL;
+    sidethread_conn_close(w->c);
+    watcher_count--;
+    bipbuf_free(w->buf);
+    free(w);
+    logger_set_flags();
+}
+
+/* Reads a particular worker thread's available bipbuf bytes. Parses each log
+ * entry into the watcher buffers.
+ */
+static int logger_thread_read(logger *l, struct logger_stats *ls) {
+    unsigned int size;
+    unsigned int pos = 0;
+    unsigned char *data;
+    char scratch[LOGGER_PARSE_SCRATCH];
+    logentry *e;
+    pthread_mutex_lock(&l->mutex);
+    data = bipbuf_peek_all(l->buf, &size);
+    pthread_mutex_unlock(&l->mutex);
+
+    if (data == NULL) {
+        return 0;
+    }
+    L_DEBUG("LOGGER: Got %d bytes from bipbuffer\n", size);
+
+    /* parse buffer */
+    while (pos < size && watcher_count > 0) {
+        enum logger_parse_entry_ret ret;
+        int scratch_len = 0;
+        e = (logentry *) (data + pos);
+        ret = logger_thread_parse_entry(e, ls, scratch, &scratch_len);
+        if (ret != LOGGER_PARSE_ENTRY_OK) {
+            /* TODO: stats counter */
+            fprintf(stderr, "LOGGER: Failed to parse log entry\n");
+        } else {
+            logger_thread_write_entry(e, ls, scratch, scratch_len);
+        }
+        pos += sizeof(logentry) + e->size + e->pad;
+    }
+    assert(pos <= size);
+
+    pthread_mutex_lock(&l->mutex);
+    data = bipbuf_poll(l->buf, size);
+    ls->worker_written += l->written;
+    ls->worker_dropped += l->dropped;
+    l->written = 0;
+    l->dropped = 0;
+    pthread_mutex_unlock(&l->mutex);
+    if (data == NULL) {
+        fprintf(stderr, "LOGGER: unexpectedly couldn't advance buf pointer\n");
+        assert(0);
+    }
+    return size; /* maybe the count of objects iterated? */
+}
+
+/* Since the event loop code isn't reusable without a refactor, and we have a
+ * limited number of potential watchers, we run our own poll loop.
+ * This calls poll() unnecessarily during write flushes, should be possible to
+ * micro-optimize later.
+ *
+ * This flushes buffers attached to watchers, iterating through the bytes set
+ * to each worker. Also checks for readability in case client connection was
+ * closed.
+ *
+ * Allows a specific watcher to be flushed (if buf full)
+ */
+static int logger_thread_poll_watchers(int force_poll, int watcher) {
+    int x;
+    int nfd = 0;
+    unsigned char *data;
+    unsigned int data_size = 0;
+    int flushed = 0;
+
+    for (x = 0; x < WATCHER_LIMIT; x++) {
+        logger_watcher *w = watchers[x];
+        if (w == NULL || (watcher != WATCHER_ALL && x != watcher))
+            continue;
+
+        data = bipbuf_peek_all(w->buf, &data_size);
+        if (data != NULL) {
+            watchers_pollfds[nfd].fd = w->sfd;
+            watchers_pollfds[nfd].events = POLLOUT;
+            nfd++;
+        } else if (force_poll) {
+            watchers_pollfds[nfd].fd = w->sfd;
+            watchers_pollfds[nfd].events = POLLIN;
+            nfd++;
+        }
+        /* This gets set after a call to poll, and should be used to gate on
+         * calling poll again.
+         */
+        w->failed_flush = false;
+    }
+
+    if (nfd == 0)
+        return 0;
+
+    //L_DEBUG("LOGGER: calling poll() [data_size: %d]\n", data_size);
+    int ret = poll(watchers_pollfds, nfd, 0);
+
+    if (ret < 0) {
+        perror("something failed with logger thread watcher fd polling");
+        return -1;
+    }
+
+    nfd = 0;
+    for (x = 0; x < WATCHER_LIMIT; x++) {
+        logger_watcher *w = watchers[x];
+        if (w == NULL)
+            continue;
+
+        data_size = 0;
+        /* Early detection of a disconnect. Otherwise we have to wait until
+         * the next write
+         */
+        if (watchers_pollfds[nfd].revents & POLLIN) {
+            char buf[1];
+            int res = ((conn*)w->c)->read(w->c, buf, 1);
+            if (res == 0 || (res == -1 && (errno != EAGAIN && errno != EWOULDBLOCK))) {
+                L_DEBUG("LOGGER: watcher closed remotely\n");
+                logger_thread_close_watcher(w);
+                nfd++;
+                continue;
+            }
+        }
+        if ((data = bipbuf_peek_all(w->buf, &data_size)) != NULL) {
+            if (watchers_pollfds[nfd].revents & (POLLHUP|POLLERR)) {
+                L_DEBUG("LOGGER: watcher closed during poll() call\n");
+                logger_thread_close_watcher(w);
+            } else if (watchers_pollfds[nfd].revents & POLLOUT) {
+                int total = 0;
+
+                /* We can write a bit. */
+                switch (w->t) {
+                    case LOGGER_WATCHER_STDERR:
+                        total = fwrite(data, 1, data_size, stderr);
+                        break;
+                    case LOGGER_WATCHER_CLIENT:
+                        total = ((conn*)w->c)->write(w->c, data, data_size);
+                        break;
+                }
+
+                L_DEBUG("LOGGER: poll() wrote %d to %d (data_size: %d) (bipbuf_used: %d)\n", total, w->sfd,
+                        data_size, bipbuf_used(w->buf));
+                if (total == -1) {
+                    if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                        logger_thread_close_watcher(w);
+                    }
+                    L_DEBUG("LOGGER: watcher hit EAGAIN\n");
+                } else if (total == 0) {
+                    logger_thread_close_watcher(w);
+                } else {
+                    bipbuf_poll(w->buf, total);
+                    flushed += total;
+                }
+            }
+        }
+        nfd++;
+    }
+    return flushed;
+}
+
+static void logger_thread_sum_stats(struct logger_stats *ls) {
+    STATS_LOCK();
+    stats.log_worker_dropped  += ls->worker_dropped;
+    stats.log_worker_written  += ls->worker_written;
+    stats.log_watcher_skipped += ls->watcher_skipped;
+    stats.log_watcher_sent    += ls->watcher_sent;
+    STATS_UNLOCK();
+}
+
+#define MAX_LOGGER_SLEEP 1000000
+#define MIN_LOGGER_SLEEP 1000
+
+/* Primary logger thread routine */
+static void *logger_thread(void *arg) {
+    useconds_t to_sleep = MIN_LOGGER_SLEEP;
+    L_DEBUG("LOGGER: Starting logger thread\n");
+    // TODO: If we ever have item references in the logger code, will need to
+    // ensure everything is dequeued before stopping the thread.
+    while (do_run_logger_thread) {
+        int found_logs = 0;
+        logger *l;
+        struct logger_stats ls;
+        memset(&ls, 0, sizeof(struct logger_stats));
+
+        /* only sleep if we're *above* the minimum */
+        if (to_sleep > MIN_LOGGER_SLEEP)
+            usleep(to_sleep);
+
+        /* Call function to iterate each logger. */
+        pthread_mutex_lock(&logger_stack_lock);
+        for (l = logger_stack_head; l != NULL; l=l->next) {
+            /* lock logger, call function to manipulate it */
+            found_logs += logger_thread_read(l, &ls);
+        }
+
+        logger_thread_poll_watchers(1, WATCHER_ALL);
+        pthread_mutex_unlock(&logger_stack_lock);
+
+        /* TODO: abstract into a function and share with lru_crawler */
+        if (!found_logs) {
+            if (to_sleep < MAX_LOGGER_SLEEP)
+                to_sleep += to_sleep / 8;
+            if (to_sleep > MAX_LOGGER_SLEEP)
+                to_sleep = MAX_LOGGER_SLEEP;
+        } else {
+            to_sleep /= 2;
+            if (to_sleep < MIN_LOGGER_SLEEP)
+                to_sleep = MIN_LOGGER_SLEEP;
+        }
+        logger_thread_sum_stats(&ls);
+    }
+
+    return NULL;
+}
+
+static int start_logger_thread(void) {
+    int ret;
+    do_run_logger_thread = 1;
+    if ((ret = pthread_create(&logger_tid, NULL,
+                              logger_thread, NULL)) != 0) {
+        fprintf(stderr, "Can't start logger thread: %s\n", strerror(ret));
+        return -1;
+    }
+    return 0;
+}
+
+static int stop_logger_thread(void) {
+    do_run_logger_thread = 0;
+    pthread_join(logger_tid, NULL);
+    return 0;
+}
+
+/*************************
+ * Public functions for submitting logs and starting loggers from workers.
+ *************************/
+
+/* Global logger thread start/init */
+void logger_init(void) {
+    /* TODO: auto destructor when threads exit */
+    /* TODO: error handling */
+
+    /* init stack for iterating loggers */
+    logger_stack_head = 0;
+    logger_stack_tail = 0;
+    pthread_key_create(&logger_key, NULL);
+
+    if (start_logger_thread() != 0) {
+        abort();
+    }
+
+    /* This is what adding a STDERR watcher looks like. should replace old
+     * "verbose" settings. */
+    //logger_add_watcher(NULL, 0);
+    return;
+}
+
+void logger_stop(void) {
+    stop_logger_thread();
+}
+
+/* called *from* the thread using a logger.
+ * initializes the per-thread bipbuf, links it into the list of loggers
+ */
+logger *logger_create(void) {
+    L_DEBUG("LOGGER: Creating and linking new logger instance\n");
+    logger *l = calloc(1, sizeof(logger));
+    if (l == NULL) {
+        return NULL;
+    }
+
+    l->buf = bipbuf_new(settings.logger_buf_size);
+    if (l->buf == NULL) {
+        free(l);
+        return NULL;
+    }
+
+    l->entry_map = default_entries;
+
+    pthread_mutex_init(&l->mutex, NULL);
+    pthread_setspecific(logger_key, l);
+
+    /* add to list of loggers */
+    logger_link_q(l);
+    return l;
+}
+
+/* helpers for logger_log */
+
+static void _logger_log_evictions(logentry *e, item *it) {
+    struct logentry_eviction *le = (struct logentry_eviction *) e->data;
+    le->exptime = (it->exptime > 0) ? (long long int)(it->exptime - current_time) : (long long int) -1;
+    le->latime = current_time - it->time;
+    le->it_flags = it->it_flags;
+    le->nkey = it->nkey;
+    le->clsid = ITEM_clsid(it);
+    memcpy(le->key, ITEM_key(it), it->nkey);
+    e->size = sizeof(struct logentry_eviction) + le->nkey;
+}
+#ifdef EXTSTORE
+/* TODO: When more logging endpoints are done and the extstore API has matured
+ * more, this could be merged with above and print different types of
+ * expulsion events.
+ */
+static void _logger_log_ext_write(logentry *e, item *it, uint8_t bucket) {
+    struct logentry_ext_write *le = (struct logentry_ext_write *) e->data;
+    le->exptime = (it->exptime > 0) ? (long long int)(it->exptime - current_time) : (long long int) -1;
+    le->latime = current_time - it->time;
+    le->it_flags = it->it_flags;
+    le->nkey = it->nkey;
+    le->clsid = ITEM_clsid(it);
+    le->bucket = bucket;
+    memcpy(le->key, ITEM_key(it), it->nkey);
+    e->size = sizeof(struct logentry_ext_write) + le->nkey;
+}
+#endif
+/* 0 == nf, 1 == found. 2 == flushed. 3 == expired.
+ * might be useful to store/print the flags an item has?
+ * could also collapse this and above code into an "item status" struct. wait
+ * for more endpoints to be written before making it generic, though.
+ */
+static void _logger_log_item_get(logentry *e, const int was_found, const char *key,
+        const int nkey, const uint8_t clsid, const int sfd) {
+    struct logentry_item_get *le = (struct logentry_item_get *) e->data;
+    le->was_found = was_found;
+    le->nkey = nkey;
+    le->clsid = clsid;
+    memcpy(le->key, key, nkey);
+    le->sfd = sfd;
+    e->size = sizeof(struct logentry_item_get) + nkey;
+}
+
+static void _logger_log_item_store(logentry *e, const enum store_item_type status,
+        const int comm, char *key, const int nkey, rel_time_t ttl, const uint8_t clsid, int sfd) {
+    struct logentry_item_store *le = (struct logentry_item_store *) e->data;
+    le->status = status;
+    le->cmd = comm;
+    le->nkey = nkey;
+    le->clsid = clsid;
+    if (ttl != 0) {
+        le->ttl = ttl - current_time;
+    } else {
+        le->ttl = 0;
+    }
+    memcpy(le->key, key, nkey);
+    le->sfd = sfd;
+    e->size = sizeof(struct logentry_item_store) + nkey;
+}
+
+/* Public function for logging an entry.
+ * Tries to encapsulate as much of the formatting as possible to simplify the
+ * caller's code.
+ */
+enum logger_ret_type logger_log(logger *l, const enum log_entry_type event, const void *entry, ...) {
+    bipbuf_t *buf = l->buf;
+    bool nospace = false;
+    va_list ap;
+    int total = 0;
+    logentry *e;
+
+    const entry_details *d = &l->entry_map[event];
+    int reqlen = d->reqlen;
+
+    pthread_mutex_lock(&l->mutex);
+    /* Request a maximum length of data to write to */
+    e = (logentry *) bipbuf_request(buf, (sizeof(logentry) + reqlen));
+    if (e == NULL) {
+        pthread_mutex_unlock(&l->mutex);
+        l->dropped++;
+        return LOGGER_RET_NOSPACE;
+    }
+    e->event = d->subtype;
+    e->pad = 0;
+    e->gid = logger_get_gid();
+    /* TODO: Could pass this down as an argument now that we're using
+     * LOGGER_LOG() macro.
+     */
+    e->eflags = d->eflags;
+    /* Noting time isn't optional. A feature may be added to avoid rendering
+     * time and/or gid to a logger.
+     */
+    gettimeofday(&e->tv, NULL);
+
+    switch (d->subtype) {
+        case LOGGER_TEXT_ENTRY:
+            va_start(ap, entry);
+            total = vsnprintf((char *) e->data, reqlen, d->format, ap);
+            va_end(ap);
+            if (total >= reqlen || total <= 0) {
+                fprintf(stderr, "LOGGER: Failed to vsnprintf a text entry: (total) %d\n", total);
+                break;
+            }
+            e->size = total + 1; /* null byte */
+
+            break;
+        case LOGGER_EVICTION_ENTRY:
+            _logger_log_evictions(e, (item *)entry);
+            break;
+#ifdef EXTSTORE
+        case LOGGER_EXT_WRITE_ENTRY:
+            va_start(ap, entry);
+            int ew_bucket = va_arg(ap, int);
+            va_end(ap);
+            _logger_log_ext_write(e, (item *)entry, ew_bucket);
+            break;
+#endif
+        case LOGGER_ITEM_GET_ENTRY:
+            va_start(ap, entry);
+            int was_found = va_arg(ap, int);
+            char *key = va_arg(ap, char *);
+            size_t nkey = va_arg(ap, size_t);
+            uint8_t gclsid = va_arg(ap, int);
+            int gsfd = va_arg(ap, int);
+            _logger_log_item_get(e, was_found, key, nkey, gclsid, gsfd);
+            va_end(ap);
+            break;
+        case LOGGER_ITEM_STORE_ENTRY:
+            va_start(ap, entry);
+            enum store_item_type status = va_arg(ap, enum store_item_type);
+            int comm = va_arg(ap, int);
+            char *skey = va_arg(ap, char *);
+            size_t snkey = va_arg(ap, size_t);
+            rel_time_t sttl = va_arg(ap, rel_time_t);
+            uint8_t sclsid = va_arg(ap, int);
+            int ssfd = va_arg(ap, int);
+            _logger_log_item_store(e, status, comm, skey, snkey, sttl, sclsid, ssfd);
+            va_end(ap);
+            break;
+    }
+
+#ifdef NEED_ALIGN
+    /* Need to ensure *next* request is aligned. */
+    if (sizeof(logentry) + e->size % 8 != 0) {
+        e->pad = 8 - (sizeof(logentry) + e->size % 8);
+    }
+#endif
+
+    /* Push pointer forward by the actual amount required */
+    if (bipbuf_push(buf, (sizeof(logentry) + e->size + e->pad)) == 0) {
+        fprintf(stderr, "LOGGER: Failed to bipbuf push a text entry\n");
+        pthread_mutex_unlock(&l->mutex);
+        return LOGGER_RET_ERR;
+    }
+    l->written++;
+    L_DEBUG("LOGGER: Requested %d bytes, wrote %lu bytes\n", reqlen,
+            (sizeof(logentry) + e->size));
+
+    pthread_mutex_unlock(&l->mutex);
+
+    if (nospace) {
+        return LOGGER_RET_NOSPACE;
+    } else {
+        return LOGGER_RET_OK;
+    }
+}
+
+/* Passes a client connection socket from a primary worker thread to the
+ * logger thread. Caller *must* event_del() the client before handing it over.
+ * Presently there's no way to hand the client back to the worker thread.
+ */
+enum logger_add_watcher_ret logger_add_watcher(void *c, const int sfd, uint16_t f) {
+    int x;
+    logger_watcher *w = NULL;
+    pthread_mutex_lock(&logger_stack_lock);
+    if (watcher_count >= WATCHER_LIMIT) {
+        pthread_mutex_unlock(&logger_stack_lock);
+        return LOGGER_ADD_WATCHER_TOO_MANY;
+    }
+
+    for (x = 0; x < WATCHER_LIMIT-1; x++) {
+        if (watchers[x] == NULL)
+            break;
+    }
+
+    w = calloc(1, sizeof(logger_watcher));
+    if (w == NULL) {
+        pthread_mutex_unlock(&logger_stack_lock);
+        return LOGGER_ADD_WATCHER_FAILED;
+    }
+    w->c = c;
+    w->sfd = sfd;
+    if (sfd == 0 && c == NULL) {
+        w->t = LOGGER_WATCHER_STDERR;
+    } else {
+        w->t = LOGGER_WATCHER_CLIENT;
+    }
+    w->id = x;
+    w->eflags = f;
+    w->buf = bipbuf_new(settings.logger_watcher_buf_size);
+    if (w->buf == NULL) {
+        free(w);
+        pthread_mutex_unlock(&logger_stack_lock);
+        return LOGGER_ADD_WATCHER_FAILED;
+    }
+    bipbuf_offer(w->buf, (unsigned char *) "OK\r\n", 4);
+
+    watchers[x] = w;
+    watcher_count++;
+    /* Update what flags the global logs will watch */
+    logger_set_flags();
+
+    pthread_mutex_unlock(&logger_stack_lock);
+    return LOGGER_ADD_WATCHER_OK;
+}
diff --git a/cachelib/external/memcached/logger.h b/cachelib/external/memcached/logger.h
new file mode 100644
index 0000000000..3cc4594f90
--- /dev/null
+++ b/cachelib/external/memcached/logger.h
@@ -0,0 +1,196 @@
+/* logging functions */
+#ifndef LOGGER_H
+#define LOGGER_H
+
+#include "bipbuffer.h"
+
+/* TODO: starttime tunable */
+#define LOGGER_BUF_SIZE 1024 * 64
+#define LOGGER_WATCHER_BUF_SIZE 1024 * 256
+#define LOGGER_ENTRY_MAX_SIZE 2048
+#define GET_LOGGER() ((logger *) pthread_getspecific(logger_key));
+
+/* Inlined from memcached.h - should go into sub header */
+typedef unsigned int rel_time_t;
+
+enum log_entry_type {
+    LOGGER_ASCII_CMD = 0,
+    LOGGER_EVICTION,
+    LOGGER_ITEM_GET,
+    LOGGER_ITEM_STORE,
+    LOGGER_CRAWLER_STATUS,
+    LOGGER_SLAB_MOVE,
+#ifdef EXTSTORE
+    LOGGER_EXTSTORE_WRITE,
+    LOGGER_COMPACT_START,
+    LOGGER_COMPACT_ABORT,
+    LOGGER_COMPACT_READ_START,
+    LOGGER_COMPACT_READ_END,
+    LOGGER_COMPACT_END,
+    LOGGER_COMPACT_FRAGINFO,
+#endif
+};
+
+enum log_entry_subtype {
+    LOGGER_TEXT_ENTRY = 0,
+    LOGGER_EVICTION_ENTRY,
+    LOGGER_ITEM_GET_ENTRY,
+    LOGGER_ITEM_STORE_ENTRY,
+#ifdef EXTSTORE
+    LOGGER_EXT_WRITE_ENTRY,
+#endif
+};
+
+enum logger_ret_type {
+    LOGGER_RET_OK = 0,
+    LOGGER_RET_NOSPACE,
+    LOGGER_RET_ERR
+};
+
+enum logger_parse_entry_ret {
+    LOGGER_PARSE_ENTRY_OK = 0,
+    LOGGER_PARSE_ENTRY_FULLBUF,
+    LOGGER_PARSE_ENTRY_FAILED
+};
+
+typedef const struct {
+    enum log_entry_subtype subtype;
+    int reqlen;
+    uint16_t eflags;
+    char *format;
+} entry_details;
+
+/* log entry intermediary structures */
+struct logentry_eviction {
+    long long int exptime;
+    uint32_t latime;
+    uint16_t it_flags;
+    uint8_t nkey;
+    uint8_t clsid;
+    char key[];
+};
+#ifdef EXTSTORE
+struct logentry_ext_write {
+    long long int exptime;
+    uint32_t latime;
+    uint16_t it_flags;
+    uint8_t nkey;
+    uint8_t clsid;
+    uint8_t bucket;
+    char key[];
+};
+#endif
+struct logentry_item_get {
+    uint8_t was_found;
+    uint8_t nkey;
+    uint8_t clsid;
+    int sfd;
+    char key[];
+};
+
+struct logentry_item_store {
+    int status;
+    int cmd;
+    rel_time_t ttl;
+    uint8_t nkey;
+    uint8_t clsid;
+    int sfd;
+    char key[];
+};
+
+/* end intermediary structures */
+
+/* WARNING: cuddled items aren't compatible with warm restart. more code
+ * necessary to ensure log streams are all flushed/processed before stopping
+ */
+typedef struct _logentry {
+    enum log_entry_subtype event;
+    uint8_t pad;
+    uint16_t eflags;
+    uint64_t gid;
+    struct timeval tv; /* not monotonic! */
+    int size;
+    union {
+        char end;
+    } data[];
+} logentry;
+
+#define LOG_SYSEVENTS  (1<<1) /* threads start/stop/working */
+#define LOG_FETCHERS   (1<<2) /* get/gets/etc */
+#define LOG_MUTATIONS  (1<<3) /* set/append/incr/etc */
+#define LOG_SYSERRORS  (1<<4) /* malloc/etc errors */
+#define LOG_CONNEVENTS (1<<5) /* new client, closed, etc */
+#define LOG_EVICTIONS  (1<<6) /* details of evicted items */
+#define LOG_STRICT     (1<<7) /* block worker instead of drop */
+#define LOG_RAWCMDS    (1<<9) /* raw ascii commands */
+
+typedef struct _logger {
+    struct _logger *prev;
+    struct _logger *next;
+    pthread_mutex_t mutex; /* guard for this + *buf */
+    uint64_t written; /* entries written to the buffer */
+    uint64_t dropped; /* entries dropped */
+    uint64_t blocked; /* times blocked instead of dropped */
+    uint16_t fetcher_ratio; /* log one out of every N fetches */
+    uint16_t mutation_ratio; /* log one out of every N mutations */
+    uint16_t eflags; /* flags this logger should log */
+    bipbuf_t *buf;
+    const entry_details *entry_map;
+} logger;
+
+enum logger_watcher_type {
+    LOGGER_WATCHER_STDERR = 0,
+    LOGGER_WATCHER_CLIENT = 1
+};
+
+typedef struct  {
+    void *c; /* original connection structure. still with source thread attached */
+    int sfd; /* client fd */
+    int id; /* id number for watcher list */
+    uint64_t skipped; /* lines skipped since last successful print */
+    bool failed_flush; /* recently failed to write out (EAGAIN), wait before retry */
+    enum logger_watcher_type t; /* stderr, client, syslog, etc */
+    uint16_t eflags; /* flags we are interested in */
+    bipbuf_t *buf; /* per-watcher output buffer */
+} logger_watcher;
+
+
+struct logger_stats {
+    uint64_t worker_dropped;
+    uint64_t worker_written;
+    uint64_t watcher_skipped;
+    uint64_t watcher_sent;
+};
+
+extern pthread_key_t logger_key;
+
+/* public functions */
+
+void logger_init(void);
+void logger_stop(void);
+logger *logger_create(void);
+
+#define LOGGER_LOG(l, flag, type, ...) \
+    do { \
+        logger *myl = l; \
+        if (l == NULL) \
+            myl = GET_LOGGER(); \
+        if (myl->eflags & flag) \
+            logger_log(myl, type, __VA_ARGS__); \
+    } while (0)
+
+enum logger_ret_type logger_log(logger *l, const enum log_entry_type event, const void *entry, ...);
+
+enum logger_add_watcher_ret {
+    LOGGER_ADD_WATCHER_TOO_MANY = 0,
+    LOGGER_ADD_WATCHER_OK,
+    LOGGER_ADD_WATCHER_FAILED
+};
+
+enum logger_add_watcher_ret logger_add_watcher(void *c, const int sfd, uint16_t f);
+
+/* functions used by restart system */
+uint64_t logger_get_gid(void);
+void logger_set_gid(uint64_t gid);
+
+#endif
diff --git a/cachelib/external/memcached/memcached.c b/cachelib/external/memcached/memcached.c
new file mode 100644
index 0000000000..dd8d81aa94
--- /dev/null
+++ b/cachelib/external/memcached/memcached.c
@@ -0,0 +1,10677 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ *  memcached - memory caching daemon
+ *
+ *       https://www.memcached.org/
+ *
+ *  Copyright 2003 Danga Interactive, Inc.  All rights reserved.
+ *
+ *  Use and distribution licensed under the BSD license.  See
+ *  the LICENSE file for full text.
+ *
+ *  Authors:
+ *      Anatoly Vorobey <mellon@pobox.com>
+ *      Brad Fitzpatrick <brad@danga.com>
+ */
+#include "config.h"
+#include "memcached.h"
+#ifdef EXTSTORE
+#include "storage.h"
+#endif
+#include "authfile.h"
+#include "restart.h"
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <signal.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/uio.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+/* some POSIX systems need the following definition
+ * to get mlockall flags out of sys/mman.h.  */
+#ifndef _P1003_1B_VISIBLE
+#define _P1003_1B_VISIBLE
+#endif
+#include <pwd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <sysexits.h>
+#include <stddef.h>
+
+#ifdef HAVE_GETOPT_LONG
+#include <getopt.h>
+#endif
+
+#ifdef TLS
+#include "tls.h"
+#endif
+
+#if defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#endif
+
+/*
+ * forward declarations
+ */
+static void drive_machine(conn *c);
+static int new_socket(struct addrinfo *ai);
+static ssize_t tcp_read(conn *arg, void *buf, size_t count);
+static ssize_t tcp_sendmsg(conn *arg, struct msghdr *msg, int flags);
+static ssize_t tcp_write(conn *arg, void *buf, size_t count);
+static void reset_cmd_handler(conn *c);
+
+enum try_read_result {
+    READ_DATA_RECEIVED,
+    READ_NO_DATA_RECEIVED,
+    READ_ERROR,            /** an error occurred (on the socket) (or client closed connection) */
+    READ_MEMORY_ERROR      /** failed to allocate more memory */
+};
+
+static int try_read_command_negotiate(conn *c);
+static int try_read_command_udp(conn *c);
+static int try_read_command_binary(conn *c);
+static int try_read_command_ascii(conn *c);
+static int try_read_command_asciiauth(conn *c);
+
+static enum try_read_result try_read_network(conn *c);
+static enum try_read_result try_read_udp(conn *c);
+
+static void conn_set_state(conn *c, enum conn_states state);
+static int start_conn_timeout_thread();
+
+static mc_resp* resp_finish(conn *c, mc_resp *resp);
+
+/* stats */
+static void stats_init(void);
+static void server_stats(ADD_STAT add_stats, conn *c);
+static void process_stat_settings(ADD_STAT add_stats, void *c);
+static void conn_to_str(const conn *c, char *addr, char *svr_addr);
+
+/** Return a datum for stats in binary protocol */
+static bool get_stats(const char *stat_type, int nkey, ADD_STAT add_stats, void *c);
+
+/* defaults */
+static void settings_init(size_t size_in_mb);
+
+/* event handling, network IO */
+static void event_handler(const int fd, const short which, void *arg);
+static void conn_close(conn *c);
+static void conn_init(void);
+static bool update_event(conn *c, const int new_flags);
+static void complete_nread(conn *c);
+void process_command(conn *c, char *command);
+static void write_and_free(conn *c, char *buf, int bytes);
+static void write_bin_error(conn *c, protocol_binary_response_status err,
+                            const char *errstr, int swallow);
+static void write_bin_miss_response(conn *c, char *key, size_t nkey);
+
+#ifdef EXTSTORE
+static void _get_extstore_cb(void *e, obj_io *io, int ret);
+static inline int _get_extstore(conn *c, item *it, mc_resp *resp);
+#endif
+static void conn_free(conn *c);
+
+/** binprot handlers **/
+static void process_bin_flush(conn *c, char *extbuf);
+static void process_bin_append_prepend(conn *c);
+static void process_bin_update(conn *c, char *extbuf);
+static void process_bin_get_or_touch(conn *c, char *extbuf);
+static void process_bin_delete(conn *c);
+static void complete_incr_bin(conn *c, char *extbuf);
+static void process_bin_stat(conn *c);
+static void process_bin_sasl_auth(conn *c);
+
+/** exported globals **/
+struct stats stats;
+struct stats_state stats_state;
+struct settings settings;
+time_t process_started;     /* when the process was started */
+conn **conns;
+
+struct slab_rebalance slab_rebal;
+volatile int slab_rebalance_signal;
+#ifdef EXTSTORE
+/* hoping this is temporary; I'd prefer to cut globals, but will complete this
+ * battle another day.
+ */
+void *ext_storage = NULL;
+#endif
+struct _mc_meta_data {
+    void *mmap_base;
+    uint64_t old_base;
+    char *slab_config; // string containing either factor or custom slab list.
+    int64_t time_delta;
+    uint64_t process_started;
+    uint32_t current_time;
+};
+/** file scope variables **/
+static conn *listen_conn = NULL;
+static int max_fds;
+struct event_base *main_base = NULL;
+static struct _mc_meta_data *meta;
+static char *memory_file = NULL;
+
+
+
+enum transmit_result {
+    TRANSMIT_COMPLETE,   /** All done writing. */
+    TRANSMIT_INCOMPLETE, /** More data remaining to write. */
+    TRANSMIT_SOFT_ERROR, /** Can't write any more right now. */
+    TRANSMIT_HARD_ERROR  /** Can't write (c->state is set to conn_closing) */
+};
+
+/* Default methods to read from/ write to a socket */
+ssize_t tcp_read(conn *c, void *buf, size_t count) {
+    assert (c != NULL);
+    return read(c->sfd, buf, count);
+}
+
+ssize_t tcp_sendmsg(conn *c, struct msghdr *msg, int flags) {
+    assert (c != NULL);
+    return sendmsg(c->sfd, msg, flags);
+}
+
+ssize_t tcp_write(conn *c, void *buf, size_t count) {
+    assert (c != NULL);
+    return write(c->sfd, buf, count);
+}
+
+static enum transmit_result transmit(conn *c);
+
+/* This reduces the latency without adding lots of extra wiring to be able to
+ * notify the listener thread of when to listen again.
+ * Also, the clock timer could be broken out into its own thread and we
+ * can block the listener via a condition.
+ */
+static volatile bool allow_new_conns = true;
+static bool stop_main_loop = false;
+static struct event maxconnsevent;
+static void maxconns_handler(const int fd, const short which, void *arg) {
+    struct timeval t = {.tv_sec = 0, .tv_usec = 10000};
+
+    if (fd == -42 || allow_new_conns == false) {
+        /* reschedule in 10ms if we need to keep polling */
+        evtimer_set(&maxconnsevent, maxconns_handler, 0);
+        event_base_set(main_base, &maxconnsevent);
+        evtimer_add(&maxconnsevent, &t);
+    } else {
+        evtimer_del(&maxconnsevent);
+        accept_new_conns(true);
+    }
+}
+
+#define REALTIME_MAXDELTA 60*60*24*30
+
+/* Negative exptimes can underflow and end up immortal. realtime() will
+   immediately expire values that are greater than REALTIME_MAXDELTA, but less
+   than process_started, so lets aim for that. */
+#define EXPTIME_TO_POSITIVE_TIME(exptime) (exptime < 0) ? \
+        REALTIME_MAXDELTA + 1 : exptime
+
+
+/*
+ * given time value that's either unix time or delta from current unix time, return
+ * unix time. Use the fact that delta can't exceed one month (and real time value can't
+ * be that low).
+ */
+static rel_time_t realtime(const time_t exptime) {
+    /* no. of seconds in 30 days - largest possible delta exptime */
+
+    if (exptime == 0) return 0; /* 0 means never expire */
+
+    if (exptime > REALTIME_MAXDELTA) {
+        /* if item expiration is at/before the server started, give it an
+           expiration time of 1 second after the server started.
+           (because 0 means don't expire).  without this, we'd
+           underflow and wrap around to some large value way in the
+           future, effectively making items expiring in the past
+           really expiring never */
+        if (exptime <= process_started)
+            return (rel_time_t)1;
+        return (rel_time_t)(exptime - process_started);
+    } else {
+        return (rel_time_t)(exptime + current_time);
+    }
+}
+
+static void stats_init(void) {
+    memset(&stats, 0, sizeof(struct stats));
+    memset(&stats_state, 0, sizeof(struct stats_state));
+    stats_state.accepting_conns = true; /* assuming we start in this state. */
+
+    /* make the time we started always be 2 seconds before we really
+       did, so time(0) - time.started is never zero.  if so, things
+       like 'settings.oldest_live' which act as booleans as well as
+       values are now false in boolean context... */
+    process_started = time(0) - ITEM_UPDATE_INTERVAL - 2;
+    stats_prefix_init(settings.prefix_delimiter);
+}
+
+static void stats_reset(void) {
+    STATS_LOCK();
+    memset(&stats, 0, sizeof(struct stats));
+    stats_prefix_clear();
+    STATS_UNLOCK();
+    threadlocal_stats_reset();
+    item_stats_reset();
+}
+
+static void settings_init(size_t size_in_mb) {
+    settings.use_cas = true;
+    settings.access = 0700;
+    settings.port = 11211;
+    settings.udpport = 0;
+#ifdef TLS
+    settings.ssl_enabled = false;
+    settings.ssl_ctx = NULL;
+    settings.ssl_chain_cert = NULL;
+    settings.ssl_key = NULL;
+    settings.ssl_verify_mode = SSL_VERIFY_NONE;
+    settings.ssl_keyformat = SSL_FILETYPE_PEM;
+    settings.ssl_ciphers = NULL;
+    settings.ssl_ca_cert = NULL;
+    settings.ssl_last_cert_refresh_time = current_time;
+    settings.ssl_wbuf_size = 16 * 1024; // default is 16KB (SSL max frame size is 17KB)
+    settings.ssl_session_cache = false;
+#endif
+    /* By default this string should be NULL for getaddrinfo() */
+    settings.inter = NULL;
+    settings.maxbytes = size_in_mb * 1024 * 1024; /* default is 64MB */
+    settings.maxconns = 1024;         /* to limit connections-related memory to about 5MB */
+    settings.verbose = 0;
+    settings.oldest_live = 0;
+    settings.oldest_cas = 0;          /* supplements accuracy of oldest_live */
+    settings.evict_to_free = 1;       /* push old items out of cache when memory runs out */
+    settings.socketpath = NULL;       /* by default, not using a unix socket */
+    settings.auth_file = NULL;        /* by default, not using ASCII authentication tokens */
+    settings.factor = 1.25;
+    settings.chunk_size = 48;         /* space for a modest key and value */
+    settings.num_threads = 4;         /* N workers */
+    settings.num_threads_per_udp = 0;
+    settings.prefix_delimiter = ':';
+    settings.detail_enabled = 0;
+    settings.reqs_per_event = 20;
+    settings.backlog = 1024;
+    settings.binding_protocol = negotiating_prot;
+    settings.item_size_max = 1024 * 1024; /* The famous 1MB upper limit. */
+    settings.slab_page_size = 1024 * 1024; /* chunks are split from 1MB pages. */
+    settings.slab_chunk_size_max = settings.slab_page_size ;/// 2;
+    settings.sasl = false;
+    settings.maxconns_fast = true;
+    settings.lru_crawler = false;
+    settings.lru_crawler_sleep = 100;
+    settings.lru_crawler_tocrawl = 0;
+    settings.lru_maintainer_thread = false;
+    settings.lru_segmented = true;
+    settings.hot_lru_pct = 20;
+    settings.warm_lru_pct = 40;
+    settings.hot_max_factor = 0.2;
+    settings.warm_max_factor = 2.0;
+    settings.temp_lru = false;
+    settings.temporary_ttl = 61;
+    settings.idle_timeout = 0; /* disabled */
+    settings.hashpower_init = 0;
+    settings.slab_reassign = true;
+    settings.slab_automove = 1;
+    settings.slab_automove_ratio = 0.8;
+    settings.slab_automove_window = 30;
+    settings.shutdown_command = false;
+    settings.tail_repair_time = TAIL_REPAIR_TIME_DEFAULT;
+    settings.flush_enabled = true;
+    settings.dump_enabled = true;
+    settings.crawls_persleep = 1000;
+    settings.logger_watcher_buf_size = LOGGER_WATCHER_BUF_SIZE;
+    settings.logger_buf_size = LOGGER_BUF_SIZE;
+    settings.drop_privileges = false;
+    settings.watch_enabled = true;
+    settings.resp_obj_mem_limit = 0;
+    settings.read_buf_mem_limit = 0;
+#ifdef MEMCACHED_DEBUG
+    settings.relaxed_privileges = false;
+#endif
+}
+
+extern pthread_mutex_t conn_lock;
+
+/* Connection timeout thread bits */
+static pthread_t conn_timeout_tid;
+static int do_run_conn_timeout_thread;
+
+#define CONNS_PER_SLICE 100
+#define TIMEOUT_MSG_SIZE (1 + sizeof(int))
+static void *conn_timeout_thread(void *arg) {
+    int i;
+    conn *c;
+    char buf[TIMEOUT_MSG_SIZE];
+    rel_time_t oldest_last_cmd;
+    int sleep_time;
+    int sleep_slice = max_fds / CONNS_PER_SLICE;
+    if (sleep_slice == 0)
+        sleep_slice = CONNS_PER_SLICE;
+
+    useconds_t timeslice = 1000000 / sleep_slice;
+
+    while(do_run_conn_timeout_thread) {
+        if (settings.verbose > 2)
+            fprintf(stderr, "idle timeout thread at top of connection list\n");
+
+        oldest_last_cmd = current_time;
+
+        for (i = 0; i < max_fds; i++) {
+            if ((i % CONNS_PER_SLICE) == 0) {
+                if (settings.verbose > 2)
+                    fprintf(stderr, "idle timeout thread sleeping for %ulus\n",
+                        (unsigned int)timeslice);
+                usleep(timeslice);
+            }
+
+            if (!conns[i])
+                continue;
+
+            c = conns[i];
+
+            if (!IS_TCP(c->transport))
+                continue;
+
+            if (c->state != conn_new_cmd && c->state != conn_read)
+                continue;
+
+            if ((current_time - c->last_cmd_time) > settings.idle_timeout) {
+                buf[0] = 't';
+                memcpy(&buf[1], &i, sizeof(int));
+                if (write(c->thread->notify_send_fd, buf, TIMEOUT_MSG_SIZE)
+                    != TIMEOUT_MSG_SIZE)
+                    perror("Failed to write timeout to notify pipe");
+            } else {
+                if (c->last_cmd_time < oldest_last_cmd)
+                    oldest_last_cmd = c->last_cmd_time;
+            }
+        }
+
+        /* This is the soonest we could have another connection time out */
+        sleep_time = settings.idle_timeout - (current_time - oldest_last_cmd) + 1;
+        if (sleep_time <= 0)
+            sleep_time = 1;
+
+        if (settings.verbose > 2)
+            fprintf(stderr,
+                    "idle timeout thread finished pass, sleeping for %ds\n",
+                    sleep_time);
+        usleep((useconds_t) sleep_time * 1000000);
+    }
+
+    return NULL;
+}
+
+static int start_conn_timeout_thread() {
+    int ret;
+
+    if (settings.idle_timeout == 0)
+        return -1;
+
+    do_run_conn_timeout_thread = 1;
+    if ((ret = pthread_create(&conn_timeout_tid, NULL,
+        conn_timeout_thread, NULL)) != 0) {
+        fprintf(stderr, "Can't create idle connection timeout thread: %s\n",
+            strerror(ret));
+        return -1;
+    }
+
+    return 0;
+}
+
+int stop_conn_timeout_thread(void) {
+    if (!do_run_conn_timeout_thread)
+        return -1;
+    do_run_conn_timeout_thread = 0;
+    pthread_join(conn_timeout_tid, NULL);
+    return 0;
+}
+
+/*
+ * read buffer cache helper functions
+ */
+static void rbuf_release(conn *c) {
+    if (c->rbuf != NULL && c->rbytes == 0 && !IS_UDP(c->transport)) {
+        if (c->rbuf_malloced) {
+            free(c->rbuf);
+            c->rbuf_malloced = false;
+        } else {
+            do_cache_free(c->thread->rbuf_cache, c->rbuf);
+        }
+        c->rsize = 0;
+        c->rbuf = NULL;
+        c->rcurr = NULL;
+    }
+}
+
+static bool rbuf_alloc(conn *c) {
+    if (c->rbuf == NULL) {
+        c->rbuf = do_cache_alloc(c->thread->rbuf_cache);
+        if (!c->rbuf) {
+            THR_STATS_LOCK(c);
+            c->thread->stats.read_buf_oom++;
+            THR_STATS_UNLOCK(c);
+            return false;
+        }
+        c->rsize = READ_BUFFER_SIZE;
+        c->rcurr = c->rbuf;
+    }
+    return true;
+}
+
+// Just for handling huge ASCII multigets.
+// The previous system was essentially the same; realloc'ing until big enough,
+// then realloc'ing back down after the request finished.
+static bool rbuf_switch_to_malloc(conn *c) {
+    // Might as well start with x2 and work from there.
+    size_t size = c->rsize * 2;
+    char *tmp = malloc(size);
+    if (!tmp)
+        return false;
+
+    do_cache_free(c->thread->rbuf_cache, c->rbuf);
+    memcpy(tmp, c->rcurr, c->rbytes);
+
+    c->rcurr = c->rbuf = tmp;
+    c->rsize = size;
+    c->rbuf_malloced = true;
+    return true;
+}
+
+/*
+ * Initializes the connections array. We don't actually allocate connection
+ * structures until they're needed, so as to avoid wasting memory when the
+ * maximum connection count is much higher than the actual number of
+ * connections.
+ *
+ * This does end up wasting a few pointers' worth of memory for FDs that are
+ * used for things other than connections, but that's worth it in exchange for
+ * being able to directly index the conns array by FD.
+ */
+static void conn_init(void) {
+    /* We're unlikely to see an FD much higher than maxconns. */
+    int next_fd = dup(1);
+    if (next_fd < 0) {
+        perror("Failed to duplicate file descriptor\n");
+        exit(1);
+    }
+    int headroom = 10;      /* account for extra unexpected open FDs */
+    struct rlimit rl;
+
+    max_fds = settings.maxconns + headroom + next_fd;
+
+    /* But if possible, get the actual highest FD we can possibly ever see. */
+    if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
+        max_fds = rl.rlim_max;
+    } else {
+        fprintf(stderr, "Failed to query maximum file descriptor; "
+                        "falling back to maxconns\n");
+    }
+
+    close(next_fd);
+
+    if ((conns = calloc(max_fds, sizeof(conn *))) == NULL) {
+        fprintf(stderr, "Failed to allocate connection structures\n");
+        /* This is unrecoverable so bail out early. */
+        exit(1);
+    }
+}
+
+static const char *prot_text(enum protocol prot) {
+    char *rv = "unknown";
+    switch(prot) {
+        case ascii_prot:
+            rv = "ascii";
+            break;
+        case binary_prot:
+            rv = "binary";
+            break;
+        case negotiating_prot:
+            rv = "auto-negotiate";
+            break;
+    }
+    return rv;
+}
+
+void conn_close_idle(conn *c) {
+    if (settings.idle_timeout > 0 &&
+        (current_time - c->last_cmd_time) > settings.idle_timeout) {
+        if (c->state != conn_new_cmd && c->state != conn_read) {
+            if (settings.verbose > 1)
+                fprintf(stderr,
+                    "fd %d wants to timeout, but isn't in read state", c->sfd);
+            return;
+        }
+
+        if (settings.verbose > 1)
+            fprintf(stderr, "Closing idle fd %d\n", c->sfd);
+
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.idle_kicks++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        conn_set_state(c, conn_closing);
+        drive_machine(c);
+    }
+}
+
+/* bring conn back from a sidethread. could have had its event base moved. */
+void conn_worker_readd(conn *c) {
+    c->ev_flags = EV_READ | EV_PERSIST;
+    event_set(&c->event, c->sfd, c->ev_flags, event_handler, (void *)c);
+    event_base_set(c->thread->base, &c->event);
+
+    // TODO: call conn_cleanup/fail/etc
+    if (event_add(&c->event, 0) == -1) {
+        perror("event_add");
+    }
+
+    // side thread wanted us to close immediately.
+    if (c->state == conn_closing) {
+        drive_machine(c);
+        return;
+    }
+    c->state = conn_new_cmd;
+
+#ifdef EXTSTORE
+    // If we had IO objects, process
+    if (c->io_wraplist) {
+        //assert(c->io_wrapleft == 0); // assert no more to process
+        conn_set_state(c, conn_mwrite);
+        drive_machine(c);
+    }
+#endif
+}
+
+conn *conn_new(const int sfd, enum conn_states init_state,
+                const int event_flags,
+                const int read_buffer_size, enum network_transport transport,
+                struct event_base *base, void *ssl) {
+    conn *c;
+
+    assert(sfd >= 0 && sfd < max_fds);
+    c = conns[sfd];
+
+    if (NULL == c) {
+        if (!(c = (conn *)calloc(1, sizeof(conn)))) {
+            STATS_LOCK();
+            stats.malloc_fails++;
+            STATS_UNLOCK();
+            fprintf(stderr, "Failed to allocate connection object\n");
+            return NULL;
+        }
+        MEMCACHED_CONN_CREATE(c);
+        c->read = NULL;
+        c->sendmsg = NULL;
+        c->write = NULL;
+        c->rbuf = NULL;
+
+        c->rsize = read_buffer_size;
+
+        // UDP connections use a persistent static buffer.
+        if (c->rsize) {
+            c->rbuf = (char *)malloc((size_t)c->rsize);
+        }
+
+        if (c->rsize && c->rbuf == NULL) {
+            conn_free(c);
+            STATS_LOCK();
+            stats.malloc_fails++;
+            STATS_UNLOCK();
+            fprintf(stderr, "Failed to allocate buffers for connection\n");
+            return NULL;
+        }
+
+        STATS_LOCK();
+        stats_state.conn_structs++;
+        STATS_UNLOCK();
+
+        c->sfd = sfd;
+        conns[sfd] = c;
+    }
+
+    c->transport = transport;
+    c->protocol = settings.binding_protocol;
+
+    /* unix socket mode doesn't need this, so zeroed out.  but why
+     * is this done for every command?  presumably for UDP
+     * mode.  */
+    if (!settings.socketpath) {
+        c->request_addr_size = sizeof(c->request_addr);
+    } else {
+        c->request_addr_size = 0;
+    }
+
+    if (transport == tcp_transport && init_state == conn_new_cmd) {
+        if (getpeername(sfd, (struct sockaddr *) &c->request_addr,
+                        &c->request_addr_size)) {
+            perror("getpeername");
+            memset(&c->request_addr, 0, sizeof(c->request_addr));
+        }
+    }
+
+    if (settings.verbose > 1) {
+        if (init_state == conn_listening) {
+            fprintf(stderr, "<%d server listening (%s)\n", sfd,
+                prot_text(c->protocol));
+        } else if (IS_UDP(transport)) {
+            fprintf(stderr, "<%d server listening (udp)\n", sfd);
+        } else if (c->protocol == negotiating_prot) {
+            fprintf(stderr, "<%d new auto-negotiating client connection\n",
+                    sfd);
+        } else if (c->protocol == ascii_prot) {
+            fprintf(stderr, "<%d new ascii client connection.\n", sfd);
+        } else if (c->protocol == binary_prot) {
+            fprintf(stderr, "<%d new binary client connection.\n", sfd);
+        } else {
+            fprintf(stderr, "<%d new unknown (%d) client connection\n",
+                sfd, c->protocol);
+            assert(false);
+        }
+    }
+
+#ifdef TLS
+    c->ssl = NULL;
+    c->ssl_wbuf = NULL;
+    c->ssl_enabled = false;
+#endif
+    c->state = init_state;
+    c->rlbytes = 0;
+    c->cmd = -1;
+    c->rbytes = 0;
+    c->rcurr = c->rbuf;
+    c->ritem = 0;
+    c->rbuf_malloced = false;
+    c->sasl_started = false;
+    c->set_stale = false;
+    c->mset_res = false;
+    c->close_after_write = false;
+    c->last_cmd_time = current_time; /* initialize for idle kicker */
+#ifdef EXTSTORE
+    c->io_wraplist = NULL;
+    c->io_wrapleft = 0;
+#endif
+
+    c->item = 0;
+
+    c->noreply = false;
+
+#ifdef TLS
+    if (ssl) {
+        c->ssl = (SSL*)ssl;
+        c->read = ssl_read;
+        c->sendmsg = ssl_sendmsg;
+        c->write = ssl_write;
+        c->ssl_enabled = true;
+        SSL_set_info_callback(c->ssl, ssl_callback);
+    } else
+#else
+    // This must be NULL if TLS is not enabled.
+    assert(ssl == NULL);
+#endif
+    {
+        c->read = tcp_read;
+        c->sendmsg = tcp_sendmsg;
+        c->write = tcp_write;
+    }
+
+    if (IS_UDP(transport)) {
+        c->try_read_command = try_read_command_udp;
+    } else {
+        switch (c->protocol) {
+            case ascii_prot:
+                if (settings.auth_file == NULL) {
+                    c->authenticated = true;
+                    c->try_read_command = try_read_command_ascii;
+                } else {
+                    c->authenticated = false;
+                    c->try_read_command = try_read_command_asciiauth;
+                }
+                break;
+            case binary_prot:
+                // binprot handles its own authentication via SASL parsing.
+                c->authenticated = false;
+                c->try_read_command = try_read_command_binary;
+                break;
+            case negotiating_prot:
+                c->try_read_command = try_read_command_negotiate;
+                break;
+        }
+    }
+
+    event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
+    event_base_set(base, &c->event);
+    c->ev_flags = event_flags;
+
+    if (event_add(&c->event, 0) == -1) {
+        perror("event_add");
+        return NULL;
+    }
+
+    STATS_LOCK();
+    stats_state.curr_conns++;
+    stats.total_conns++;
+    STATS_UNLOCK();
+
+    MEMCACHED_CONN_ALLOCATE(c->sfd);
+
+    return c;
+}
+#ifdef EXTSTORE
+static void recache_or_free(conn *c, io_wrap *wrap) {
+    item *it;
+    it = (item *)wrap->io.buf;
+    bool do_free = true;
+    if (wrap->active) {
+        // If request never dispatched, free the read buffer but leave the
+        // item header alone.
+        do_free = false;
+        size_t ntotal = ITEM_ntotal(wrap->hdr_it);
+        slabs_free(it, ntotal, slabs_clsid(ntotal));
+        c->io_wrapleft--;
+        assert(c->io_wrapleft >= 0);
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.get_aborted_extstore++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+    } else if (wrap->miss) {
+        // If request was ultimately a miss, unlink the header.
+        do_free = false;
+        size_t ntotal = ITEM_ntotal(wrap->hdr_it);
+        item_unlink(wrap->hdr_it);
+        slabs_free(it, ntotal, slabs_clsid(ntotal));
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.miss_from_extstore++;
+        if (wrap->badcrc)
+            c->thread->stats.badcrc_from_extstore++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+    } else if (settings.ext_recache_rate) {
+        // hashvalue is cuddled during store
+        uint32_t hv = (uint32_t)it->time;
+        // opt to throw away rather than wait on a lock.
+        void *hold_lock = item_trylock(hv);
+        if (hold_lock != NULL) {
+            item *h_it = wrap->hdr_it;
+            uint8_t flags = ITEM_LINKED|ITEM_FETCHED|ITEM_ACTIVE;
+            // Item must be recently hit at least twice to recache.
+            if (((h_it->it_flags & flags) == flags) &&
+                    h_it->time > current_time - ITEM_UPDATE_INTERVAL &&
+                    c->recache_counter++ % settings.ext_recache_rate == 0) {
+                do_free = false;
+                // In case it's been updated.
+                it->exptime = h_it->exptime;
+                it->it_flags &= ~ITEM_LINKED;
+                it->refcount = 0;
+                it->h_next = NULL; // might not be necessary.
+                STORAGE_delete(c->thread->storage, h_it);
+                item_replace(h_it, it, hv);
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.recache_from_extstore++;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+            }
+        }
+        if (hold_lock)
+            item_trylock_unlock(hold_lock);
+    }
+    if (do_free)
+        slabs_free(it, ITEM_ntotal(it), ITEM_clsid(it));
+
+    wrap->io.buf = NULL; // sanity.
+    wrap->io.next = NULL;
+    wrap->next = NULL;
+    wrap->active = false;
+
+    // TODO: reuse lock and/or hv.
+    item_remove(wrap->hdr_it);
+}
+#endif
+static void conn_release_items(conn *c) {
+    assert(c != NULL);
+
+    if (c->item) {
+        item_remove(c->item);
+        c->item = 0;
+    }
+
+#ifdef EXTSTORE
+    if (c->io_wraplist) {
+        io_wrap *tmp = c->io_wraplist;
+        while (tmp) {
+            io_wrap *next = tmp->next;
+            recache_or_free(c, tmp);
+            // malloc'ed iovec list used for chunked extstore fetches.
+            if (tmp->io.iov) {
+                free(tmp->io.iov);
+                tmp->io.iov = NULL;
+            }
+            do_cache_free(c->thread->io_cache, tmp); // lockless
+            tmp = next;
+        }
+        c->io_wraplist = NULL;
+    }
+#endif
+
+    // Cull any unsent responses.
+    if (c->resp_head) {
+        mc_resp *resp = c->resp_head;
+        // r_f() handles the chain maintenance.
+        while (resp) {
+            // temporary by default. hide behind a debug flag in the future:
+            // double free detection. Transmit loops can drop out early, but
+            // here we could infinite loop.
+            if (resp->free) {
+                fprintf(stderr, "ERROR: double free detected during conn_release_items(): [%d] [%s]\n",
+                        c->sfd, c->protocol == binary_prot ? "binary" : "ascii");
+                // Since this is a critical failure, just leak the memory.
+                // If these errors are seen, an abort() can be used instead.
+                c->resp_head = NULL;
+                c->resp = NULL;
+                break;
+            }
+            resp = resp_finish(c, resp);
+        }
+    }
+}
+
+static void conn_cleanup(conn *c) {
+    assert(c != NULL);
+
+    conn_release_items(c);
+
+    if (c->sasl_conn) {
+        assert(settings.sasl);
+        sasl_dispose(&c->sasl_conn);
+        c->sasl_conn = NULL;
+    }
+
+    if (IS_UDP(c->transport)) {
+        conn_set_state(c, conn_read);
+    }
+}
+
+/*
+ * Frees a connection.
+ */
+void conn_free(conn *c) {
+    if (c) {
+        assert(c != NULL);
+        assert(c->sfd >= 0 && c->sfd < max_fds);
+
+        MEMCACHED_CONN_DESTROY(c);
+        conns[c->sfd] = NULL;
+        if (c->rbuf)
+            free(c->rbuf);
+#ifdef TLS
+        if (c->ssl_wbuf)
+            c->ssl_wbuf = NULL;
+#endif
+
+        free(c);
+    }
+}
+
+static void conn_close(conn *c) {
+    assert(c != NULL);
+
+    /* delete the event, the socket and the conn */
+    event_del(&c->event);
+
+    if (settings.verbose > 1)
+        fprintf(stderr, "<%d connection closed.\n", c->sfd);
+
+    conn_cleanup(c);
+
+    // force release of read buffer.
+    if (c->thread) {
+        c->rbytes = 0;
+        rbuf_release(c);
+    }
+
+    MEMCACHED_CONN_RELEASE(c->sfd);
+    conn_set_state(c, conn_closed);
+#ifdef TLS
+    if (c->ssl) {
+        SSL_shutdown(c->ssl);
+        SSL_free(c->ssl);
+    }
+#endif
+    close(c->sfd);
+    pthread_mutex_lock(&conn_lock);
+    allow_new_conns = true;
+    pthread_mutex_unlock(&conn_lock);
+
+    STATS_LOCK();
+    stats_state.curr_conns--;
+    STATS_UNLOCK();
+
+    return;
+}
+
+// Since some connections might be off on side threads and some are managed as
+// listeners we need to walk through them all from a central point.
+// Must be called with all worker threads hung or in the process of closing.
+void conn_close_all(void) {
+    int i;
+    for (i = 0; i < max_fds; i++) {
+        if (conns[i] && conns[i]->state != conn_closed) {
+            conn_close(conns[i]);
+        }
+    }
+}
+
+/**
+ * Convert a state name to a human readable form.
+ */
+static const char *state_text(enum conn_states state) {
+    const char* const statenames[] = { "conn_listening",
+                                       "conn_new_cmd",
+                                       "conn_waiting",
+                                       "conn_read",
+                                       "conn_parse_cmd",
+                                       "conn_write",
+                                       "conn_nread",
+                                       "conn_swallow",
+                                       "conn_closing",
+                                       "conn_mwrite",
+                                       "conn_closed",
+                                       "conn_watch" };
+    return statenames[state];
+}
+
+/*
+ * Sets a connection's current state in the state machine. Any special
+ * processing that needs to happen on certain state transitions can
+ * happen here.
+ */
+static void conn_set_state(conn *c, enum conn_states state) {
+    assert(c != NULL);
+    assert(state >= conn_listening && state < conn_max_state);
+
+    if (state != c->state) {
+        if (settings.verbose > 2) {
+            fprintf(stderr, "%d: going from %s to %s\n",
+                    c->sfd, state_text(c->state),
+                    state_text(state));
+        }
+
+        if (state == conn_write || state == conn_mwrite) {
+            MEMCACHED_PROCESS_COMMAND_END(c->sfd, c->resp->wbuf, c->resp->wbytes);
+        }
+        c->state = state;
+    }
+}
+
+/*
+ * response object helper functions
+ */
+static void resp_reset(mc_resp *resp) {
+    if (resp->item) {
+        item_remove(resp->item);
+        resp->item = NULL;
+    }
+    if (resp->write_and_free) {
+        free(resp->write_and_free);
+        resp->write_and_free = NULL;
+    }
+    resp->wbytes = 0;
+    resp->tosend = 0;
+    resp->iovcnt = 0;
+    resp->chunked_data_iov = 0;
+    resp->chunked_total = 0;
+    resp->skip = false;
+}
+
+static void resp_add_iov(mc_resp *resp, const void *buf, int len) {
+    assert(resp->iovcnt < MC_RESP_IOVCOUNT);
+    int x = resp->iovcnt;
+    resp->iov[x].iov_base = (void *)buf;
+    resp->iov[x].iov_len = len;
+    resp->iovcnt++;
+    resp->tosend += len;
+}
+
+// Notes that an IOV should be handled as a chunked item header.
+// TODO: I'm hoping this isn't a permanent abstraction while I learn what the
+// API should be.
+static void resp_add_chunked_iov(mc_resp *resp, const void *buf, int len) {
+    resp->chunked_data_iov = resp->iovcnt;
+    resp->chunked_total = len;
+    resp_add_iov(resp, buf, len);
+}
+
+static bool resp_start(conn *c) {
+    mc_resp *resp = do_cache_alloc(c->thread->resp_cache);
+    if (!resp) {
+        THR_STATS_LOCK(c);
+        c->thread->stats.response_obj_oom++;
+        THR_STATS_UNLOCK(c);
+        return false;
+    }
+    // FIXME: make wbuf indirect or use offsetof to zero up until wbuf
+    memset(resp, 0, sizeof(*resp));
+    if (!c->resp_head) {
+        c->resp_head = resp;
+    }
+    if (!c->resp) {
+        c->resp = resp;
+    } else {
+        c->resp->next = resp;
+        c->resp = resp;
+    }
+    if (IS_UDP(c->transport)) {
+        // need to hold on to some data for async responses.
+        c->resp->request_id = c->request_id;
+        c->resp->request_addr = c->request_addr;
+        c->resp->request_addr_size = c->request_addr_size;
+    }
+    return true;
+}
+
+// returns next response in chain.
+static mc_resp* resp_finish(conn *c, mc_resp *resp) {
+    mc_resp *next = resp->next;
+    if (resp->item) {
+        // TODO: cache hash value in resp obj?
+        item_remove(resp->item);
+        resp->item = NULL;
+    }
+    if (resp->write_and_free) {
+        free(resp->write_and_free);
+    }
+    if (c->resp_head == resp) {
+        c->resp_head = next;
+    }
+    if (c->resp == resp) {
+        c->resp = NULL;
+    }
+    resp->free = true;
+    do_cache_free(c->thread->resp_cache, resp);
+    return next;
+}
+
+// tells if connection has a depth of response objects to process.
+static bool resp_has_stack(conn *c) {
+    return c->resp_head->next != NULL ? true : false;
+}
+
+static void out_string(conn *c, const char *str) {
+    size_t len;
+    mc_resp *resp = c->resp;
+
+    assert(c != NULL);
+    // if response was original filled with something, but we're now writing
+    // out an error or similar, have to reset the object first.
+    // TODO: since this is often redundant with allocation, how many callers
+    // are actually requiring it be reset? Can we fast test by just looking at
+    // tosend and reset if nonzero?
+    resp_reset(resp);
+
+    if (c->noreply) {
+        // TODO: just invalidate the response since nothing's been attempted
+        // to send yet?
+        resp->skip = true;
+        if (settings.verbose > 1)
+            fprintf(stderr, ">%d NOREPLY %s\n", c->sfd, str);
+        conn_set_state(c, conn_new_cmd);
+        return;
+    }
+
+    if (settings.verbose > 1)
+        fprintf(stderr, ">%d %s\n", c->sfd, str);
+
+    // Fill response object with static string.
+
+    len = strlen(str);
+    if ((len + 2) > WRITE_BUFFER_SIZE) {
+        /* ought to be always enough. just fail for simplicity */
+        str = "SERVER_ERROR output line too long";
+        len = strlen(str);
+    }
+
+    memcpy(resp->wbuf, str, len);
+    memcpy(resp->wbuf + len, "\r\n", 2);
+    resp_add_iov(resp, resp->wbuf, len + 2);
+
+    conn_set_state(c, conn_new_cmd);
+    return;
+}
+
+// For metaget-style ASCII commands. Ignores noreply, ensuring clients see
+// protocol level errors.
+static void out_errstring(conn *c, const char *str) {
+    c->noreply = false;
+    out_string(c, str);
+}
+
+/*
+ * Outputs a protocol-specific "out of memory" error. For ASCII clients,
+ * this is equivalent to out_string().
+ */
+static void out_of_memory(conn *c, char *ascii_error) {
+    const static char error_prefix[] = "SERVER_ERROR ";
+    const static int error_prefix_len = sizeof(error_prefix) - 1;
+
+    if (c->protocol == binary_prot) {
+        /* Strip off the generic error prefix; it's irrelevant in binary */
+        if (!strncmp(ascii_error, error_prefix, error_prefix_len)) {
+            ascii_error += error_prefix_len;
+        }
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, ascii_error, 0);
+    } else {
+        out_string(c, ascii_error);
+    }
+}
+
+/*
+ * we get here after reading the value in set/add/replace commands. The command
+ * has been stored in c->cmd, and the item is ready in c->item.
+ */
+static void complete_nread_ascii(conn *c) {
+    assert(c != NULL);
+
+    item *it = c->item;
+    int comm = c->cmd;
+    enum store_item_type ret;
+    bool is_valid = false;
+
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.slab_stats[ITEM_clsid(it)].set_cmds++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    if ((it->it_flags & ITEM_CHUNKED) == 0) {
+        if (strncmp(ITEM_data(it) + it->nbytes - 2, "\r\n", 2) == 0) {
+            is_valid = true;
+        }
+    } else {
+        char buf[2];
+        /* should point to the final item chunk */
+        item_chunk *ch = (item_chunk *) c->ritem;
+        assert(ch->used != 0);
+        /* :( We need to look at the last two bytes. This could span two
+         * chunks.
+         */
+        if (ch->used > 1) {
+            buf[0] = ch->data[ch->used - 2];
+            buf[1] = ch->data[ch->used - 1];
+        } else {
+            assert(ch->prev);
+            assert(ch->used == 1);
+            buf[0] = ch->prev->data[ch->prev->used - 1];
+            buf[1] = ch->data[ch->used - 1];
+        }
+        if (strncmp(buf, "\r\n", 2) == 0) {
+            is_valid = true;
+        } else {
+            assert(1 == 0);
+        }
+    }
+
+    if (!is_valid) {
+        // metaset mode always returns errors.
+        if (c->mset_res) {
+            c->noreply = false;
+        }
+        out_string(c, "CLIENT_ERROR bad data chunk");
+    } else {
+      ret = store_item(it, comm, c);
+
+#ifdef ENABLE_DTRACE
+      uint64_t cas = ITEM_get_cas(it);
+      switch (c->cmd) {
+      case NREAD_ADD:
+          MEMCACHED_COMMAND_ADD(c->sfd, ITEM_key(it), it->nkey,
+                                (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_REPLACE:
+          MEMCACHED_COMMAND_REPLACE(c->sfd, ITEM_key(it), it->nkey,
+                                    (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_APPEND:
+          MEMCACHED_COMMAND_APPEND(c->sfd, ITEM_key(it), it->nkey,
+                                   (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_PREPEND:
+          MEMCACHED_COMMAND_PREPEND(c->sfd, ITEM_key(it), it->nkey,
+                                    (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_SET:
+          MEMCACHED_COMMAND_SET(c->sfd, ITEM_key(it), it->nkey,
+                                (ret == 1) ? it->nbytes : -1, cas);
+          break;
+      case NREAD_CAS:
+          MEMCACHED_COMMAND_CAS(c->sfd, ITEM_key(it), it->nkey, it->nbytes,
+                                cas);
+          break;
+      }
+#endif
+
+      if (c->mset_res) {
+          // Replace the status code in the response.
+          // Rest was prepared during mset parsing.
+          mc_resp *resp = c->resp;
+          conn_set_state(c, conn_new_cmd);
+          switch (ret) {
+          case STORED:
+              memcpy(resp->wbuf, "OK ", 3);
+              // Only place noreply is used for meta cmds is a nominal response.
+              if (c->noreply) {
+                  resp->skip = true;
+              }
+              break;
+          case EXISTS:
+              memcpy(resp->wbuf, "EX ", 3);
+              break;
+          case NOT_FOUND:
+              memcpy(resp->wbuf, "NF ", 3);
+              break;
+          case NOT_STORED:
+              memcpy(resp->wbuf, "NS ", 3);
+              break;
+          default:
+              c->noreply = false;
+              out_string(c, "SERVER_ERROR Unhandled storage type.");
+          }
+      } else {
+          switch (ret) {
+          case STORED:
+              out_string(c, "STORED");
+              break;
+          case EXISTS:
+              out_string(c, "EXISTS");
+              break;
+          case NOT_FOUND:
+              out_string(c, "NOT_FOUND");
+              break;
+          case NOT_STORED:
+              out_string(c, "NOT_STORED");
+              break;
+          default:
+              out_string(c, "SERVER_ERROR Unhandled storage type.");
+          }
+      }
+
+    }
+
+    c->set_stale = false; /* force flag to be off just in case */
+    c->mset_res = false;
+    item_remove(c->item);       /* release the c->item reference */
+    c->item = 0;
+}
+
+/**
+ * get a pointer to the key in this request
+ */
+static char* binary_get_key(conn *c) {
+    return c->rcurr - (c->binary_header.request.keylen);
+}
+
+static void add_bin_header(conn *c, uint16_t err, uint8_t hdr_len, uint16_t key_len, uint32_t body_len) {
+    protocol_binary_response_header* header;
+    mc_resp *resp = c->resp;
+
+    assert(c);
+
+    resp_reset(resp);
+
+    header = (protocol_binary_response_header *)resp->wbuf;
+
+    header->response.magic = (uint8_t)PROTOCOL_BINARY_RES;
+    header->response.opcode = c->binary_header.request.opcode;
+    header->response.keylen = (uint16_t)htons(key_len);
+
+    header->response.extlen = (uint8_t)hdr_len;
+    header->response.datatype = (uint8_t)PROTOCOL_BINARY_RAW_BYTES;
+    header->response.status = (uint16_t)htons(err);
+
+    header->response.bodylen = htonl(body_len);
+    header->response.opaque = c->opaque;
+    header->response.cas = htonll(c->cas);
+
+    if (settings.verbose > 1) {
+        int ii;
+        fprintf(stderr, ">%d Writing bin response:", c->sfd);
+        for (ii = 0; ii < sizeof(header->bytes); ++ii) {
+            if (ii % 4 == 0) {
+                fprintf(stderr, "\n>%d  ", c->sfd);
+            }
+            fprintf(stderr, " 0x%02x", header->bytes[ii]);
+        }
+        fprintf(stderr, "\n");
+    }
+
+    resp->wbytes = sizeof(header->response);
+    resp_add_iov(resp, resp->wbuf, resp->wbytes);
+}
+
+/**
+ * Writes a binary error response. If errstr is supplied, it is used as the
+ * error text; otherwise a generic description of the error status code is
+ * included.
+ */
+static void write_bin_error(conn *c, protocol_binary_response_status err,
+                            const char *errstr, int swallow) {
+    size_t len;
+
+    if (!errstr) {
+        switch (err) {
+        case PROTOCOL_BINARY_RESPONSE_ENOMEM:
+            errstr = "Out of memory";
+            break;
+        case PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND:
+            errstr = "Unknown command";
+            break;
+        case PROTOCOL_BINARY_RESPONSE_KEY_ENOENT:
+            errstr = "Not found";
+            break;
+        case PROTOCOL_BINARY_RESPONSE_EINVAL:
+            errstr = "Invalid arguments";
+            break;
+        case PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS:
+            errstr = "Data exists for key.";
+            break;
+        case PROTOCOL_BINARY_RESPONSE_E2BIG:
+            errstr = "Too large.";
+            break;
+        case PROTOCOL_BINARY_RESPONSE_DELTA_BADVAL:
+            errstr = "Non-numeric server-side value for incr or decr";
+            break;
+        case PROTOCOL_BINARY_RESPONSE_NOT_STORED:
+            errstr = "Not stored.";
+            break;
+        case PROTOCOL_BINARY_RESPONSE_AUTH_ERROR:
+            errstr = "Auth failure.";
+            break;
+        default:
+            assert(false);
+            errstr = "UNHANDLED ERROR";
+            fprintf(stderr, ">%d UNHANDLED ERROR: %d\n", c->sfd, err);
+        }
+    }
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, ">%d Writing an error: %s\n", c->sfd, errstr);
+    }
+
+    len = strlen(errstr);
+    add_bin_header(c, err, 0, 0, len);
+    if (len > 0) {
+        resp_add_iov(c->resp, errstr, len);
+    }
+    if (swallow > 0) {
+        c->sbytes = swallow;
+        conn_set_state(c, conn_swallow);
+    } else {
+        conn_set_state(c, conn_mwrite);
+    }
+}
+
+/* Form and send a response to a command over the binary protocol */
+static void write_bin_response(conn *c, void *d, int hlen, int keylen, int dlen) {
+    if (!c->noreply || c->cmd == PROTOCOL_BINARY_CMD_GET ||
+        c->cmd == PROTOCOL_BINARY_CMD_GETK) {
+        add_bin_header(c, 0, hlen, keylen, dlen);
+        mc_resp *resp = c->resp;
+        if (dlen > 0) {
+            resp_add_iov(resp, d, dlen);
+        }
+    }
+
+    conn_set_state(c, conn_new_cmd);
+}
+
+static void complete_incr_bin(conn *c, char *extbuf) {
+    item *it;
+    char *key;
+    size_t nkey;
+    /* Weird magic in add_delta forces me to pad here */
+    char tmpbuf[INCR_MAX_STORAGE_LEN];
+    uint64_t cas = 0;
+
+    protocol_binary_response_incr* rsp = (protocol_binary_response_incr*)c->resp->wbuf;
+    protocol_binary_request_incr* req = (void *)extbuf;
+
+    assert(c != NULL);
+    //assert(c->wsize >= sizeof(*rsp));
+
+    /* fix byteorder in the request */
+    req->message.body.delta = ntohll(req->message.body.delta);
+    req->message.body.initial = ntohll(req->message.body.initial);
+    req->message.body.expiration = ntohl(req->message.body.expiration);
+    key = binary_get_key(c);
+    nkey = c->binary_header.request.keylen;
+
+    if (settings.verbose > 1) {
+        int i;
+        fprintf(stderr, "incr ");
+
+        for (i = 0; i < nkey; i++) {
+            fprintf(stderr, "%c", key[i]);
+        }
+        fprintf(stderr, " %lld, %llu, %d\n",
+                (long long)req->message.body.delta,
+                (long long)req->message.body.initial,
+                req->message.body.expiration);
+    }
+
+    if (c->binary_header.request.cas != 0) {
+        cas = c->binary_header.request.cas;
+    }
+    switch(add_delta(c, key, nkey, c->cmd == PROTOCOL_BINARY_CMD_INCREMENT,
+                     req->message.body.delta, tmpbuf,
+                     &cas)) {
+    case OK:
+        rsp->message.body.value = htonll(strtoull(tmpbuf, NULL, 10));
+        if (cas) {
+            c->cas = cas;
+        }
+        write_bin_response(c, &rsp->message.body, 0, 0,
+                           sizeof(rsp->message.body.value));
+        break;
+    case NON_NUMERIC:
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_DELTA_BADVAL, NULL, 0);
+        break;
+    case EOM:
+        out_of_memory(c, "SERVER_ERROR Out of memory incrementing value");
+        break;
+    case DELTA_ITEM_NOT_FOUND:
+        if (req->message.body.expiration != 0xffffffff) {
+            /* Save some room for the response */
+            rsp->message.body.value = htonll(req->message.body.initial);
+
+            snprintf(tmpbuf, INCR_MAX_STORAGE_LEN, "%llu",
+                (unsigned long long)req->message.body.initial);
+            int res = strlen(tmpbuf);
+            it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),
+                            res + 2);
+
+            if (it != NULL) {
+                memcpy(ITEM_data(it), tmpbuf, res);
+                memcpy(ITEM_data(it) + res, "\r\n", 2);
+
+                if (store_item(it, NREAD_ADD, c)) {
+                    c->cas = ITEM_get_cas(it);
+                    write_bin_response(c, &rsp->message.body, 0, 0, sizeof(rsp->message.body.value));
+                } else {
+                    write_bin_error(c, PROTOCOL_BINARY_RESPONSE_NOT_STORED,
+                                    NULL, 0);
+                }
+                item_remove(it);         /* release our reference */
+            } else {
+                out_of_memory(c,
+                        "SERVER_ERROR Out of memory allocating new item");
+            }
+        } else {
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            if (c->cmd == PROTOCOL_BINARY_CMD_INCREMENT) {
+                c->thread->stats.incr_misses++;
+            } else {
+                c->thread->stats.decr_misses++;
+            }
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, NULL, 0);
+        }
+        break;
+    case DELTA_ITEM_CAS_MISMATCH:
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, NULL, 0);
+        break;
+    }
+}
+
+static void complete_update_bin(conn *c) {
+    protocol_binary_response_status eno = PROTOCOL_BINARY_RESPONSE_EINVAL;
+    enum store_item_type ret = NOT_STORED;
+    assert(c != NULL);
+
+    item *it = c->item;
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.slab_stats[ITEM_clsid(it)].set_cmds++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    /* We don't actually receive the trailing two characters in the bin
+     * protocol, so we're going to just set them here */
+    if ((it->it_flags & ITEM_CHUNKED) == 0) {
+        *(ITEM_data(it) + it->nbytes - 2) = '\r';
+        *(ITEM_data(it) + it->nbytes - 1) = '\n';
+    } else {
+        assert(c->ritem);
+        item_chunk *ch = (item_chunk *) c->ritem;
+        if (ch->size == ch->used)
+            ch = ch->next;
+        assert(ch->size - ch->used >= 2);
+        ch->data[ch->used] = '\r';
+        ch->data[ch->used + 1] = '\n';
+        ch->used += 2;
+    }
+
+    ret = store_item(it, c->cmd, c);
+
+#ifdef ENABLE_DTRACE
+    uint64_t cas = ITEM_get_cas(it);
+    switch (c->cmd) {
+    case NREAD_ADD:
+        MEMCACHED_COMMAND_ADD(c->sfd, ITEM_key(it), it->nkey,
+                              (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    case NREAD_REPLACE:
+        MEMCACHED_COMMAND_REPLACE(c->sfd, ITEM_key(it), it->nkey,
+                                  (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    case NREAD_APPEND:
+        MEMCACHED_COMMAND_APPEND(c->sfd, ITEM_key(it), it->nkey,
+                                 (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    case NREAD_PREPEND:
+        MEMCACHED_COMMAND_PREPEND(c->sfd, ITEM_key(it), it->nkey,
+                                 (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    case NREAD_SET:
+        MEMCACHED_COMMAND_SET(c->sfd, ITEM_key(it), it->nkey,
+                              (ret == STORED) ? it->nbytes : -1, cas);
+        break;
+    }
+#endif
+
+    switch (ret) {
+    case STORED:
+        /* Stored */
+        write_bin_response(c, NULL, 0, 0, 0);
+        break;
+    case EXISTS:
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, NULL, 0);
+        break;
+    case NOT_FOUND:
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, NULL, 0);
+        break;
+    case NOT_STORED:
+    case TOO_LARGE:
+    case NO_MEMORY:
+        if (c->cmd == NREAD_ADD) {
+            eno = PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS;
+        } else if(c->cmd == NREAD_REPLACE) {
+            eno = PROTOCOL_BINARY_RESPONSE_KEY_ENOENT;
+        } else {
+            eno = PROTOCOL_BINARY_RESPONSE_NOT_STORED;
+        }
+        write_bin_error(c, eno, NULL, 0);
+    }
+
+    item_remove(c->item);       /* release the c->item reference */
+    c->item = 0;
+}
+
+static void complete_update_cachebench(conn *c, size_t vlen) {
+    protocol_binary_response_status eno = PROTOCOL_BINARY_RESPONSE_EINVAL;
+    enum store_item_type ret = NOT_STORED;
+    assert(c != NULL);
+    item *it = c->item;
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.slab_stats[ITEM_clsid(it)].set_cmds++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    /* We don't actually receive the trailing two characters in the bin
+     * protocol, so we're going to just set them here */
+    if ((it->it_flags & ITEM_CHUNKED) == 0) {
+        *(ITEM_data(it) + it->nbytes - 2) = '\r';
+        *(ITEM_data(it) + it->nbytes - 1) = '\n';
+    } else {
+        assert(c->ritem);
+        //item_chunk *ch = (item_chunk *) c->ritem;
+        //if (ch->size == ch->used)
+        //    ch = ch->next;
+        //assert(ch->size - ch->used >= 2);
+        //ch->data[ch->used] = '\r';
+        c->ritem[vlen-2] = '\r';
+//        ch->data[ch->used + 1] = '\n';
+        c->ritem[vlen-1] = '\n';
+        //ch->used += 2;
+    }
+
+    ret = store_item(it, c->cmd, c);
+
+//#ifdef ENABLE_DTRACE
+//    uint64_t cas = ITEM_get_cas(it);
+//    switch (c->cmd) {
+//    case NREAD_ADD:
+//        MEMCACHED_COMMAND_ADD(c->sfd, ITEM_key(it), it->nkey,
+//                              (ret == STORED) ? it->nbytes : -1, cas);
+//        break;
+//    case NREAD_REPLACE:
+//        MEMCACHED_COMMAND_REPLACE(c->sfd, ITEM_key(it), it->nkey,
+//                                  (ret == STORED) ? it->nbytes : -1, cas);
+//        break;
+//    case NREAD_APPEND:
+//        MEMCACHED_COMMAND_APPEND(c->sfd, ITEM_key(it), it->nkey,
+//                                 (ret == STORED) ? it->nbytes : -1, cas);
+//        break;
+//    case NREAD_PREPEND:
+//        MEMCACHED_COMMAND_PREPEND(c->sfd, ITEM_key(it), it->nkey,
+//                                 (ret == STORED) ? it->nbytes : -1, cas);
+//        break;
+//    case NREAD_SET:
+//        MEMCACHED_COMMAND_SET(c->sfd, ITEM_key(it), it->nkey,
+//                              (ret == STORED) ? it->nbytes : -1, cas);
+//        break;
+//    }
+//#endif
+    if(settings.verbose > 2){
+        switch (ret) {
+        case STORED:
+            /* Stored */
+            printf("stored!\n");
+            break;
+        case EXISTS:
+            printf("exists!\n");
+            break;
+        case NOT_FOUND:
+            printf("not found!\n");
+            break;
+        case NOT_STORED:
+        case TOO_LARGE:
+        case NO_MEMORY:
+            if (c->cmd == NREAD_ADD) {
+                eno = PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS;
+                printf("err exists!\n");
+
+            } else if(c->cmd == NREAD_REPLACE) {
+                eno = PROTOCOL_BINARY_RESPONSE_KEY_ENOENT;
+                 printf("err not found!\n");
+            } else {
+                eno = PROTOCOL_BINARY_RESPONSE_NOT_STORED;
+                printf("err not stored!!\n");
+            }
+        }
+    }
+    item_remove(c->item);       /* release the c->item reference */
+    c->item = 0;
+    reset_cmd_handler(c);
+}
+
+static void write_bin_miss_response(conn *c, char *key, size_t nkey) {
+    if (nkey) {
+        add_bin_header(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT,
+                0, nkey, nkey);
+        char *ofs = c->resp->wbuf + sizeof(protocol_binary_response_header);
+        memcpy(ofs, key, nkey);
+        resp_add_iov(c->resp, ofs, nkey);
+        conn_set_state(c, conn_new_cmd);
+    } else {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT,
+                        NULL, 0);
+    }
+}
+int process_cachebench_get_or_touch(char * key, int nkey, conn *c, int cmd) {
+    item *it;
+    c->cmd = cmd;
+    mc_resp resp;
+    c->resp = &resp;
+
+    int should_touch = (c->cmd == PROTOCOL_BINARY_CMD_TOUCH ||
+                        c->cmd == PROTOCOL_BINARY_CMD_GAT ||
+                        c->cmd == PROTOCOL_BINARY_CMD_GATK);
+    int should_return_key = (c->cmd == PROTOCOL_BINARY_CMD_GETK ||
+                             c->cmd == PROTOCOL_BINARY_CMD_GATK);
+    int should_return_value = (c->cmd != PROTOCOL_BINARY_CMD_TOUCH);
+    bool failed = false;
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "<%d %s ", c->sfd, should_touch ? "TOUCH" : "GET");
+        if (fwrite(key, 1, nkey, stderr)) {}
+        fputc('\n', stderr);
+    }
+
+    it = item_get(key, nkey, c, DO_UPDATE);
+    uint32_t hv;
+
+    int retval = it == NULL ? EXIT_FAILURE : EXIT_SUCCESS;
+    if (it) {
+        /* the length has two unnecessary bytes ("\r\n") */
+        //uint16_t keylen = 0;
+        //uint32_t bodylen = sizeof(rsp->message.body) + (it->nbytes - 2);
+
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        if (should_touch) {
+            c->thread->stats.touch_cmds++;
+            c->thread->stats.slab_stats[ITEM_clsid(it)].touch_hits++;
+        } else {
+            c->thread->stats.get_cmds++;
+            c->thread->stats.lru_hits[it->slabs_clsid]++;
+        }
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        if (should_touch) {
+            MEMCACHED_COMMAND_TOUCH(c->sfd, ITEM_key(it), it->nkey,
+                                    it->nbytes, ITEM_get_cas(it));
+        } else {
+            MEMCACHED_COMMAND_GET(c->sfd, ITEM_key(it), it->nkey,
+                                  it->nbytes, ITEM_get_cas(it));
+        }
+
+        //if (c->cmd == PROTOCOL_BINARY_CMD_TOUCH) {
+        //    bodylen -= it->nbytes - 2;
+        //} else if (should_return_key) {
+        //    bodylen += nkey;
+        //    keylen = nkey;
+        //}
+
+        //add_bin_header(c, 0, sizeof(rsp->message.body), keylen, bodylen);
+        //rsp->message.header.response.cas = htonll(ITEM_get_cas(it));
+
+        // add the flags
+        //FLAGS_CONV(it, rsp->message.body.flags);
+        //rsp->message.body.flags = htonl(rsp->message.body.flags);
+        //resp_add_iov(c->resp, &rsp->message.body, sizeof(rsp->message.body));
+
+        //if (should_return_key) {
+        //    resp_add_iov(c->resp, ITEM_key(it), nkey);
+        //}
+
+        if (should_return_value) {
+            /* Add the data minus the CRLF */
+#ifdef EXTSTORE
+            if (it->it_flags & ITEM_HDR) {
+                if (_get_extstore(c, it, c->resp) != 0) {
+                    pthread_mutex_lock(&c->thread->stats.mutex);
+                    c->thread->stats.get_oom_extstore++;
+                    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+                    failed = true;
+                }
+            }// else if ((it->it_flags & ITEM_CHUNKED) == 0) {
+               // resp_add_iov(c->resp, ITEM_data(it), it->nbytes - 2);
+            //} else {
+                // Allow transmit handler to find the item and expand iov's
+            //    resp_add_chunked_iov(c->resp, it, it->nbytes - 2);
+            //}
+#else
+            //if ((it->it_flags & ITEM_CHUNKED) == 0) {
+            //    resp_add_iov(c->resp, ITEM_data(it), it->nbytes - 2);
+            //} else {
+            //    resp_add_chunked_iov(c->resp, it, it->nbytes - 2);
+            //}
+#endif
+        }
+
+        if (!failed) {
+            conn_set_state(c, conn_new_cmd);
+            /* Remember this command so we can garbage collect it later */
+#ifdef EXTSTORE
+            if ((it->it_flags & ITEM_HDR) != 0 && should_return_value) {
+                // Only have extstore clean if header and returning value.
+                c->resp->item = NULL;
+            } else {
+                c->resp->item = it;
+            }
+#else
+            c->resp->item = it;
+#endif
+        } else {
+            item_remove(it);
+        }
+    } else {
+        failed = true;
+    }
+
+    if (failed) {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        if (should_touch) {
+            c->thread->stats.touch_cmds++;
+            c->thread->stats.touch_misses++;
+        } else {
+            c->thread->stats.get_cmds++;
+            c->thread->stats.get_misses++;
+        }
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        if (should_touch) {
+            MEMCACHED_COMMAND_TOUCH(c->sfd, key, nkey, -1, 0);
+        } else {
+            MEMCACHED_COMMAND_GET(c->sfd, key, nkey, -1, 0);
+        }
+
+        conn_set_state(c, conn_new_cmd);
+        
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_get(key, nkey, NULL != it);
+    }
+    conn_release_items(c);
+    reset_cmd_handler(c);
+
+    if(it != NULL){
+        hv = hash(ITEM_key(it), it->nkey);
+        item_lock(hv);
+        refcount_decr(it);
+        item_unlock(hv);
+    }
+    return retval;
+}
+
+static void process_bin_get_or_touch(conn *c, char *extbuf) {
+    item *it;
+
+    protocol_binary_response_get* rsp = (protocol_binary_response_get*)c->resp->wbuf;
+    char* key = binary_get_key(c);
+    size_t nkey = c->binary_header.request.keylen;
+    int should_touch = (c->cmd == PROTOCOL_BINARY_CMD_TOUCH ||
+                        c->cmd == PROTOCOL_BINARY_CMD_GAT ||
+                        c->cmd == PROTOCOL_BINARY_CMD_GATK);
+    int should_return_key = (c->cmd == PROTOCOL_BINARY_CMD_GETK ||
+                             c->cmd == PROTOCOL_BINARY_CMD_GATK);
+    int should_return_value = (c->cmd != PROTOCOL_BINARY_CMD_TOUCH);
+    bool failed = false;
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "<%d %s ", c->sfd, should_touch ? "TOUCH" : "GET");
+        if (fwrite(key, 1, nkey, stderr)) {}
+        fputc('\n', stderr);
+    }
+
+    if (should_touch) {
+        protocol_binary_request_touch *t = (void *)extbuf;
+        time_t exptime = ntohl(t->message.body.expiration);
+
+        it = item_touch(key, nkey, realtime(exptime), c);
+    } else {
+        it = item_get(key, nkey, c, DO_UPDATE);
+    }
+
+    if (it) {
+        /* the length has two unnecessary bytes ("\r\n") */
+        uint16_t keylen = 0;
+        uint32_t bodylen = sizeof(rsp->message.body) + (it->nbytes - 2);
+
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        if (should_touch) {
+            c->thread->stats.touch_cmds++;
+            c->thread->stats.slab_stats[ITEM_clsid(it)].touch_hits++;
+        } else {
+            c->thread->stats.get_cmds++;
+            c->thread->stats.lru_hits[it->slabs_clsid]++;
+        }
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        if (should_touch) {
+            MEMCACHED_COMMAND_TOUCH(c->sfd, ITEM_key(it), it->nkey,
+                                    it->nbytes, ITEM_get_cas(it));
+        } else {
+            MEMCACHED_COMMAND_GET(c->sfd, ITEM_key(it), it->nkey,
+                                  it->nbytes, ITEM_get_cas(it));
+        }
+
+        if (c->cmd == PROTOCOL_BINARY_CMD_TOUCH) {
+            bodylen -= it->nbytes - 2;
+        } else if (should_return_key) {
+            bodylen += nkey;
+            keylen = nkey;
+        }
+
+        add_bin_header(c, 0, sizeof(rsp->message.body), keylen, bodylen);
+        rsp->message.header.response.cas = htonll(ITEM_get_cas(it));
+
+        // add the flags
+        FLAGS_CONV(it, rsp->message.body.flags);
+        rsp->message.body.flags = htonl(rsp->message.body.flags);
+        resp_add_iov(c->resp, &rsp->message.body, sizeof(rsp->message.body));
+
+        if (should_return_key) {
+            resp_add_iov(c->resp, ITEM_key(it), nkey);
+        }
+
+        if (should_return_value) {
+            /* Add the data minus the CRLF */
+#ifdef EXTSTORE
+            if (it->it_flags & ITEM_HDR) {
+                if (_get_extstore(c, it, c->resp) != 0) {
+                    pthread_mutex_lock(&c->thread->stats.mutex);
+                    c->thread->stats.get_oom_extstore++;
+                    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+                    failed = true;
+                }
+            } else if ((it->it_flags & ITEM_CHUNKED) == 0) {
+                resp_add_iov(c->resp, ITEM_data(it), it->nbytes - 2);
+            } else {
+                // Allow transmit handler to find the item and expand iov's
+                resp_add_chunked_iov(c->resp, it, it->nbytes - 2);
+            }
+#else
+            if ((it->it_flags & ITEM_CHUNKED) == 0) {
+                resp_add_iov(c->resp, ITEM_data(it), it->nbytes - 2);
+            } else {
+                resp_add_chunked_iov(c->resp, it, it->nbytes - 2);
+            }
+#endif
+        }
+
+        if (!failed) {
+            conn_set_state(c, conn_new_cmd);
+            /* Remember this command so we can garbage collect it later */
+#ifdef EXTSTORE
+            if ((it->it_flags & ITEM_HDR) != 0 && should_return_value) {
+                // Only have extstore clean if header and returning value.
+                c->resp->item = NULL;
+            } else {
+                c->resp->item = it;
+            }
+#else
+            c->resp->item = it;
+#endif
+        } else {
+            item_remove(it);
+        }
+    } else {
+        failed = true;
+    }
+
+    if (failed) {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        if (should_touch) {
+            c->thread->stats.touch_cmds++;
+            c->thread->stats.touch_misses++;
+        } else {
+            c->thread->stats.get_cmds++;
+            c->thread->stats.get_misses++;
+        }
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        if (should_touch) {
+            MEMCACHED_COMMAND_TOUCH(c->sfd, key, nkey, -1, 0);
+        } else {
+            MEMCACHED_COMMAND_GET(c->sfd, key, nkey, -1, 0);
+        }
+
+        if (c->noreply) {
+            conn_set_state(c, conn_new_cmd);
+        } else {
+            if (should_return_key) {
+                write_bin_miss_response(c, key, nkey);
+            } else {
+                write_bin_miss_response(c, NULL, 0);
+            }
+        }
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_get(key, nkey, NULL != it);
+    }
+}
+
+static void append_bin_stats(const char *key, const uint16_t klen,
+                             const char *val, const uint32_t vlen,
+                             conn *c) {
+    char *buf = c->stats.buffer + c->stats.offset;
+    uint32_t bodylen = klen + vlen;
+    protocol_binary_response_header header = {
+        .response.magic = (uint8_t)PROTOCOL_BINARY_RES,
+        .response.opcode = PROTOCOL_BINARY_CMD_STAT,
+        .response.keylen = (uint16_t)htons(klen),
+        .response.datatype = (uint8_t)PROTOCOL_BINARY_RAW_BYTES,
+        .response.bodylen = htonl(bodylen),
+        .response.opaque = c->opaque
+    };
+
+    memcpy(buf, header.bytes, sizeof(header.response));
+    buf += sizeof(header.response);
+
+    if (klen > 0) {
+        memcpy(buf, key, klen);
+        buf += klen;
+
+        if (vlen > 0) {
+            memcpy(buf, val, vlen);
+        }
+    }
+
+    c->stats.offset += sizeof(header.response) + bodylen;
+}
+
+static void append_ascii_stats(const char *key, const uint16_t klen,
+                               const char *val, const uint32_t vlen,
+                               conn *c) {
+    char *pos = c->stats.buffer + c->stats.offset;
+    uint32_t nbytes = 0;
+    int remaining = c->stats.size - c->stats.offset;
+    int room = remaining - 1;
+
+    if (klen == 0 && vlen == 0) {
+        nbytes = snprintf(pos, room, "END\r\n");
+    } else if (vlen == 0) {
+        nbytes = snprintf(pos, room, "STAT %s\r\n", key);
+    } else {
+        nbytes = snprintf(pos, room, "STAT %s %s\r\n", key, val);
+    }
+
+    c->stats.offset += nbytes;
+}
+
+static bool grow_stats_buf(conn *c, size_t needed) {
+    size_t nsize = c->stats.size;
+    size_t available = nsize - c->stats.offset;
+    bool rv = true;
+
+    /* Special case: No buffer -- need to allocate fresh */
+    if (c->stats.buffer == NULL) {
+        nsize = 1024;
+        available = c->stats.size = c->stats.offset = 0;
+    }
+
+    while (needed > available) {
+        assert(nsize > 0);
+        nsize = nsize << 1;
+        available = nsize - c->stats.offset;
+    }
+
+    if (nsize != c->stats.size) {
+        char *ptr = realloc(c->stats.buffer, nsize);
+        if (ptr) {
+            c->stats.buffer = ptr;
+            c->stats.size = nsize;
+        } else {
+            STATS_LOCK();
+            stats.malloc_fails++;
+            STATS_UNLOCK();
+            rv = false;
+        }
+    }
+
+    return rv;
+}
+
+static void append_stats(const char *key, const uint16_t klen,
+                  const char *val, const uint32_t vlen,
+                  const void *cookie)
+{
+    /* value without a key is invalid */
+    if (klen == 0 && vlen > 0) {
+        return;
+    }
+
+    conn *c = (conn*)cookie;
+
+    if (c->protocol == binary_prot) {
+        size_t needed = vlen + klen + sizeof(protocol_binary_response_header);
+        if (!grow_stats_buf(c, needed)) {
+            return;
+        }
+        append_bin_stats(key, klen, val, vlen, c);
+    } else {
+        size_t needed = vlen + klen + 10; // 10 == "STAT = \r\n"
+        if (!grow_stats_buf(c, needed)) {
+            return;
+        }
+        append_ascii_stats(key, klen, val, vlen, c);
+    }
+
+    assert(c->stats.offset <= c->stats.size);
+}
+
+static void process_bin_stat(conn *c) {
+    char *subcommand = binary_get_key(c);
+    size_t nkey = c->binary_header.request.keylen;
+
+    if (settings.verbose > 1) {
+        int ii;
+        fprintf(stderr, "<%d STATS ", c->sfd);
+        for (ii = 0; ii < nkey; ++ii) {
+            fprintf(stderr, "%c", subcommand[ii]);
+        }
+        fprintf(stderr, "\n");
+    }
+
+    if (nkey == 0) {
+        /* request all statistics */
+        server_stats(&append_stats, c);
+        (void)get_stats(NULL, 0, &append_stats, c);
+    } else if (strncmp(subcommand, "reset", 5) == 0) {
+        stats_reset();
+    } else if (strncmp(subcommand, "settings", 8) == 0) {
+        process_stat_settings(&append_stats, c);
+    } else if (strncmp(subcommand, "detail", 6) == 0) {
+        char *subcmd_pos = subcommand + 6;
+        if (strncmp(subcmd_pos, " dump", 5) == 0) {
+            int len;
+            char *dump_buf = stats_prefix_dump(&len);
+            if (dump_buf == NULL || len <= 0) {
+                out_of_memory(c, "SERVER_ERROR Out of memory generating stats");
+                if (dump_buf != NULL)
+                    free(dump_buf);
+                return;
+            } else {
+                append_stats("detailed", strlen("detailed"), dump_buf, len, c);
+                free(dump_buf);
+            }
+        } else if (strncmp(subcmd_pos, " on", 3) == 0) {
+            settings.detail_enabled = 1;
+        } else if (strncmp(subcmd_pos, " off", 4) == 0) {
+            settings.detail_enabled = 0;
+        } else {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, NULL, 0);
+            return;
+        }
+    } else {
+        if (get_stats(subcommand, nkey, &append_stats, c)) {
+            if (c->stats.buffer == NULL) {
+                out_of_memory(c, "SERVER_ERROR Out of memory generating stats");
+            } else {
+                write_and_free(c, c->stats.buffer, c->stats.offset);
+                c->stats.buffer = NULL;
+            }
+        } else {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, NULL, 0);
+        }
+
+        return;
+    }
+
+    /* Append termination package and start the transfer */
+    append_stats(NULL, 0, NULL, 0, c);
+    if (c->stats.buffer == NULL) {
+        out_of_memory(c, "SERVER_ERROR Out of memory preparing to send stats");
+    } else {
+        write_and_free(c, c->stats.buffer, c->stats.offset);
+        c->stats.buffer = NULL;
+    }
+}
+
+/* Just write an error message and disconnect the client */
+static void handle_binary_protocol_error(conn *c) {
+    write_bin_error(c, PROTOCOL_BINARY_RESPONSE_EINVAL, NULL, 0);
+    if (settings.verbose) {
+        fprintf(stderr, "Protocol error (opcode %02x), close connection %d\n",
+                c->binary_header.request.opcode, c->sfd);
+    }
+    c->close_after_write = true;
+}
+
+static void init_sasl_conn(conn *c) {
+    assert(c);
+    /* should something else be returned? */
+    if (!settings.sasl)
+        return;
+
+    c->authenticated = false;
+
+    if (!c->sasl_conn) {
+        int result=sasl_server_new("memcached",
+                                   NULL,
+                                   my_sasl_hostname[0] ? my_sasl_hostname : NULL,
+                                   NULL, NULL,
+                                   NULL, 0, &c->sasl_conn);
+        if (result != SASL_OK) {
+            if (settings.verbose) {
+                fprintf(stderr, "Failed to initialize SASL conn.\n");
+            }
+            c->sasl_conn = NULL;
+        }
+    }
+}
+
+static void bin_list_sasl_mechs(conn *c) {
+    // Guard against a disabled SASL.
+    if (!settings.sasl) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, NULL,
+                        c->binary_header.request.bodylen
+                        - c->binary_header.request.keylen);
+        return;
+    }
+
+    init_sasl_conn(c);
+    const char *result_string = NULL;
+    unsigned int string_length = 0;
+    int result=sasl_listmech(c->sasl_conn, NULL,
+                             "",   /* What to prepend the string with */
+                             " ",  /* What to separate mechanisms with */
+                             "",   /* What to append to the string */
+                             &result_string, &string_length,
+                             NULL);
+    if (result != SASL_OK) {
+        /* Perhaps there's a better error for this... */
+        if (settings.verbose) {
+            fprintf(stderr, "Failed to list SASL mechanisms.\n");
+        }
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, NULL, 0);
+        return;
+    }
+    write_bin_response(c, (char*)result_string, 0, 0, string_length);
+}
+
+static void process_bin_sasl_auth(conn *c) {
+    // Guard for handling disabled SASL on the server.
+    if (!settings.sasl) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, NULL,
+                        c->binary_header.request.bodylen
+                        - c->binary_header.request.keylen);
+        return;
+    }
+
+    assert(c->binary_header.request.extlen == 0);
+
+    int nkey = c->binary_header.request.keylen;
+    int vlen = c->binary_header.request.bodylen - nkey;
+
+    if (nkey > MAX_SASL_MECH_LEN) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_EINVAL, NULL, vlen);
+        conn_set_state(c, conn_swallow);
+        return;
+    }
+
+    char *key = binary_get_key(c);
+    assert(key);
+
+    item *it = item_alloc(key, nkey, 0, 0, vlen+2);
+
+    /* Can't use a chunked item for SASL authentication. */
+    if (it == 0 || (it->it_flags & ITEM_CHUNKED)) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, NULL, vlen);
+        conn_set_state(c, conn_swallow);
+        if (it) {
+            do_item_remove(it);
+        }
+        return;
+    }
+
+    c->item = it;
+    c->ritem = ITEM_data(it);
+    c->rlbytes = vlen;
+    conn_set_state(c, conn_nread);
+    c->substate = bin_reading_sasl_auth_data;
+}
+
+static void process_bin_complete_sasl_auth(conn *c) {
+    assert(settings.sasl);
+    const char *out = NULL;
+    unsigned int outlen = 0;
+
+    assert(c->item);
+    init_sasl_conn(c);
+
+    int nkey = c->binary_header.request.keylen;
+    int vlen = c->binary_header.request.bodylen - nkey;
+
+    if (nkey > ((item*) c->item)->nkey) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_EINVAL, NULL, vlen);
+        conn_set_state(c, conn_swallow);
+        return;
+    }
+
+    char mech[nkey+1];
+    memcpy(mech, ITEM_key((item*)c->item), nkey);
+    mech[nkey] = 0x00;
+
+    if (settings.verbose)
+        fprintf(stderr, "mech:  ``%s'' with %d bytes of data\n", mech, vlen);
+
+    const char *challenge = vlen == 0 ? NULL : ITEM_data((item*) c->item);
+
+    if (vlen > ((item*) c->item)->nbytes) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_EINVAL, NULL, vlen);
+        conn_set_state(c, conn_swallow);
+        return;
+    }
+
+    int result=-1;
+
+    switch (c->cmd) {
+    case PROTOCOL_BINARY_CMD_SASL_AUTH:
+        result = sasl_server_start(c->sasl_conn, mech,
+                                   challenge, vlen,
+                                   &out, &outlen);
+        c->sasl_started = (result == SASL_OK || result == SASL_CONTINUE);
+        break;
+    case PROTOCOL_BINARY_CMD_SASL_STEP:
+        if (!c->sasl_started) {
+            if (settings.verbose) {
+                fprintf(stderr, "%d: SASL_STEP called but sasl_server_start "
+                        "not called for this connection!\n", c->sfd);
+            }
+            break;
+        }
+        result = sasl_server_step(c->sasl_conn,
+                                  challenge, vlen,
+                                  &out, &outlen);
+        break;
+    default:
+        assert(false); /* CMD should be one of the above */
+        /* This code is pretty much impossible, but makes the compiler
+           happier */
+        if (settings.verbose) {
+            fprintf(stderr, "Unhandled command %d with challenge %s\n",
+                    c->cmd, challenge);
+        }
+        break;
+    }
+
+    if (settings.verbose) {
+        fprintf(stderr, "sasl result code:  %d\n", result);
+    }
+
+    switch(result) {
+    case SASL_OK:
+        c->authenticated = true;
+        write_bin_response(c, "Authenticated", 0, 0, strlen("Authenticated"));
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.auth_cmds++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+        break;
+    case SASL_CONTINUE:
+        add_bin_header(c, PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE, 0, 0, outlen);
+        if (outlen > 0) {
+            resp_add_iov(c->resp, out, outlen);
+        }
+        // Immediately flush our write.
+        conn_set_state(c, conn_mwrite);
+        break;
+    default:
+        if (settings.verbose)
+            fprintf(stderr, "Unknown sasl response:  %d\n", result);
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, NULL, 0);
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.auth_cmds++;
+        c->thread->stats.auth_errors++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+    }
+}
+
+static bool authenticated(conn *c) {
+    assert(settings.sasl);
+    bool rv = false;
+
+    switch (c->cmd) {
+    case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS: /* FALLTHROUGH */
+    case PROTOCOL_BINARY_CMD_SASL_AUTH:       /* FALLTHROUGH */
+    case PROTOCOL_BINARY_CMD_SASL_STEP:       /* FALLTHROUGH */
+    case PROTOCOL_BINARY_CMD_VERSION:         /* FALLTHROUGH */
+        rv = true;
+        break;
+    default:
+        rv = c->authenticated;
+    }
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "authenticated() in cmd 0x%02x is %s\n",
+                c->cmd, rv ? "true" : "false");
+    }
+
+    return rv;
+}
+
+static void dispatch_bin_command(conn *c, char *extbuf) {
+    int protocol_error = 0;
+
+    uint8_t extlen = c->binary_header.request.extlen;
+    uint16_t keylen = c->binary_header.request.keylen;
+    uint32_t bodylen = c->binary_header.request.bodylen;
+
+    if (keylen > bodylen || keylen + extlen > bodylen) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, NULL, 0);
+        c->close_after_write = true;
+        return;
+    }
+
+    if (settings.sasl && !authenticated(c)) {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, NULL, 0);
+        c->close_after_write = true;
+        return;
+    }
+
+    MEMCACHED_PROCESS_COMMAND_START(c->sfd, c->rcurr, c->rbytes);
+    c->noreply = true;
+
+    /* binprot supports 16bit keys, but internals are still 8bit */
+    if (keylen > KEY_MAX_LENGTH) {
+        handle_binary_protocol_error(c);
+        return;
+    }
+
+    switch (c->cmd) {
+    case PROTOCOL_BINARY_CMD_SETQ:
+        c->cmd = PROTOCOL_BINARY_CMD_SET;
+        break;
+    case PROTOCOL_BINARY_CMD_ADDQ:
+        c->cmd = PROTOCOL_BINARY_CMD_ADD;
+        break;
+    case PROTOCOL_BINARY_CMD_REPLACEQ:
+        c->cmd = PROTOCOL_BINARY_CMD_REPLACE;
+        break;
+    case PROTOCOL_BINARY_CMD_DELETEQ:
+        c->cmd = PROTOCOL_BINARY_CMD_DELETE;
+        break;
+    case PROTOCOL_BINARY_CMD_INCREMENTQ:
+        c->cmd = PROTOCOL_BINARY_CMD_INCREMENT;
+        break;
+    case PROTOCOL_BINARY_CMD_DECREMENTQ:
+        c->cmd = PROTOCOL_BINARY_CMD_DECREMENT;
+        break;
+    case PROTOCOL_BINARY_CMD_QUITQ:
+        c->cmd = PROTOCOL_BINARY_CMD_QUIT;
+        break;
+    case PROTOCOL_BINARY_CMD_FLUSHQ:
+        c->cmd = PROTOCOL_BINARY_CMD_FLUSH;
+        break;
+    case PROTOCOL_BINARY_CMD_APPENDQ:
+        c->cmd = PROTOCOL_BINARY_CMD_APPEND;
+        break;
+    case PROTOCOL_BINARY_CMD_PREPENDQ:
+        c->cmd = PROTOCOL_BINARY_CMD_PREPEND;
+        break;
+    case PROTOCOL_BINARY_CMD_GETQ:
+        c->cmd = PROTOCOL_BINARY_CMD_GET;
+        break;
+    case PROTOCOL_BINARY_CMD_GETKQ:
+        c->cmd = PROTOCOL_BINARY_CMD_GETK;
+        break;
+    case PROTOCOL_BINARY_CMD_GATQ:
+        c->cmd = PROTOCOL_BINARY_CMD_GAT;
+        break;
+    case PROTOCOL_BINARY_CMD_GATKQ:
+        c->cmd = PROTOCOL_BINARY_CMD_GATK;
+        break;
+    default:
+        c->noreply = false;
+    }
+
+    switch (c->cmd) {
+        case PROTOCOL_BINARY_CMD_VERSION:
+            if (extlen == 0 && keylen == 0 && bodylen == 0) {
+                write_bin_response(c, VERSION, 0, 0, strlen(VERSION));
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_FLUSH:
+            if (keylen == 0 && bodylen == extlen && (extlen == 0 || extlen == 4)) {
+                process_bin_flush(c, extbuf);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_NOOP:
+            if (extlen == 0 && keylen == 0 && bodylen == 0) {
+                write_bin_response(c, NULL, 0, 0, 0);
+                // NOOP forces pipeline flush.
+                conn_set_state(c, conn_mwrite);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_SET: /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_ADD: /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_REPLACE:
+            if (extlen == 8 && keylen != 0 && bodylen >= (keylen + 8)) {
+                process_bin_update(c, extbuf);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_GETQ:  /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_GET:   /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_GETKQ: /* FALLTHROUGH */
+        case PROTOCOL_BINARY_CMD_GETK:
+            if (extlen == 0 && bodylen == keylen && keylen > 0) {
+                process_bin_get_or_touch(c, extbuf);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_DELETE:
+            if (keylen > 0 && extlen == 0 && bodylen == keylen) {
+                process_bin_delete(c);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_INCREMENT:
+        case PROTOCOL_BINARY_CMD_DECREMENT:
+            if (keylen > 0 && extlen == 20 && bodylen == (keylen + extlen)) {
+                complete_incr_bin(c, extbuf);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_APPEND:
+        case PROTOCOL_BINARY_CMD_PREPEND:
+            if (keylen > 0 && extlen == 0) {
+                process_bin_append_prepend(c);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_STAT:
+            if (extlen == 0) {
+                process_bin_stat(c);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_QUIT:
+            if (keylen == 0 && extlen == 0 && bodylen == 0) {
+                write_bin_response(c, NULL, 0, 0, 0);
+                conn_set_state(c, conn_mwrite);
+                c->close_after_write = true;
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS:
+            if (extlen == 0 && keylen == 0 && bodylen == 0) {
+                bin_list_sasl_mechs(c);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_SASL_AUTH:
+        case PROTOCOL_BINARY_CMD_SASL_STEP:
+            if (extlen == 0 && keylen != 0) {
+                process_bin_sasl_auth(c);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        case PROTOCOL_BINARY_CMD_TOUCH:
+        case PROTOCOL_BINARY_CMD_GAT:
+        case PROTOCOL_BINARY_CMD_GATQ:
+        case PROTOCOL_BINARY_CMD_GATK:
+        case PROTOCOL_BINARY_CMD_GATKQ:
+            if (extlen == 4 && keylen != 0) {
+                process_bin_get_or_touch(c, extbuf);
+            } else {
+                protocol_error = 1;
+            }
+            break;
+        default:
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, NULL,
+                            bodylen);
+    }
+
+    if (protocol_error)
+        handle_binary_protocol_error(c);
+}
+
+static void process_bin_update(conn *c, char *extbuf) {
+    char *key;
+    int nkey;
+    int vlen;
+    item *it;
+    protocol_binary_request_set* req = (void *)extbuf;
+
+    assert(c != NULL);
+
+    key = binary_get_key(c);
+    nkey = c->binary_header.request.keylen;
+
+    /* fix byteorder in the request */
+    req->message.body.flags = ntohl(req->message.body.flags);
+    req->message.body.expiration = ntohl(req->message.body.expiration);
+
+    vlen = c->binary_header.request.bodylen - (nkey + c->binary_header.request.extlen);
+
+    if (settings.verbose > 1) {
+        int ii;
+        if (c->cmd == PROTOCOL_BINARY_CMD_ADD) {
+            fprintf(stderr, "<%d ADD ", c->sfd);
+        } else if (c->cmd == PROTOCOL_BINARY_CMD_SET) {
+            fprintf(stderr, "<%d SET ", c->sfd);
+        } else {
+            fprintf(stderr, "<%d REPLACE ", c->sfd);
+        }
+        for (ii = 0; ii < nkey; ++ii) {
+            fprintf(stderr, "%c", key[ii]);
+        }
+
+        fprintf(stderr, " Value len is %d", vlen);
+        fprintf(stderr, "\n");
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_set(key, nkey);
+    }
+
+    it = item_alloc(key, nkey, req->message.body.flags,
+            realtime(req->message.body.expiration), vlen+2);
+
+    if (it == 0) {
+        enum store_item_type status;
+        if (! item_size_ok(nkey, req->message.body.flags, vlen + 2)) {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_E2BIG, NULL, vlen);
+            status = TOO_LARGE;
+        } else {
+            out_of_memory(c, "SERVER_ERROR Out of memory allocating item");
+            /* This error generating method eats the swallow value. Add here. */
+            c->sbytes = vlen;
+            status = NO_MEMORY;
+        }
+        /* FIXME: losing c->cmd since it's translated below. refactor? */
+        LOGGER_LOG(c->thread->l, LOG_MUTATIONS, LOGGER_ITEM_STORE,
+                NULL, status, 0, key, nkey, req->message.body.expiration,
+                ITEM_clsid(it), c->sfd);
+
+        /* Avoid stale data persisting in cache because we failed alloc.
+         * Unacceptable for SET. Anywhere else too? */
+        if (c->cmd == PROTOCOL_BINARY_CMD_SET) {
+            it = item_get(key, nkey, c, DONT_UPDATE);
+            if (it) {
+                item_unlink(it);
+                STORAGE_delete(c->thread->storage, it);
+                item_remove(it);
+            }
+        }
+
+        /* swallow the data line */
+        conn_set_state(c, conn_swallow);
+        return;
+    }
+
+    ITEM_set_cas(it, c->binary_header.request.cas);
+
+    switch (c->cmd) {
+        case PROTOCOL_BINARY_CMD_ADD:
+            c->cmd = NREAD_ADD;
+            break;
+        case PROTOCOL_BINARY_CMD_SET:
+            c->cmd = NREAD_SET;
+            break;
+        case PROTOCOL_BINARY_CMD_REPLACE:
+            c->cmd = NREAD_REPLACE;
+            break;
+        default:
+            assert(0);
+    }
+
+    if (ITEM_get_cas(it) != 0) {
+        c->cmd = NREAD_CAS;
+    }
+
+    c->item = it;
+#ifdef NEED_ALIGN
+    if (it->it_flags & ITEM_CHUNKED) {
+        c->ritem = ITEM_schunk(it);
+    } else {
+        c->ritem = ITEM_data(it);
+    }
+#else
+    c->ritem = ITEM_data(it);
+#endif
+    c->rlbytes = vlen;
+    conn_set_state(c, conn_nread);
+    c->substate = bin_read_set_value;
+}
+
+item * process_cachebench_set(char * key, int nkey, char * val, int vlen, conn *c, int cmd) {
+    //protocol_binary_request_set* req = (void *)extbuf;
+    c->protocol = binary_prot;
+    item *it;
+    assert(c != NULL);
+
+    /* fix byteorder in the request */
+    if (settings.verbose > 1) {
+        int ii;
+        if (cmd == PROTOCOL_BINARY_CMD_ADD) {
+            fprintf(stderr, "<%d ADD ", c->sfd);
+        } else if (cmd == PROTOCOL_BINARY_CMD_SET) {
+            fprintf(stderr, "<%d SET ", c->sfd);
+        } else {
+            fprintf(stderr, "<%d REPLACE ", c->sfd);
+        }
+        for (ii = 0; ii < nkey; ++ii) {
+            fprintf(stderr, "%c", key[ii]);
+        }
+
+        fprintf(stderr, " Value len is %d", vlen);
+        fprintf(stderr, "\n");
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_set(key, nkey);
+    }
+
+    it = item_alloc(key, nkey, 0,
+            0, vlen+2);
+
+    if (it == 0) {
+        enum store_item_type status;
+        if (! item_size_ok(nkey, 0, vlen + 2)) {
+            printf("alloc TOO_LARGE\n");
+            //write_bin_error(c, PROTOCOL_BINARY_RESPONSE_E2BIG, NULL, vlen);
+            status = TOO_LARGE;
+        } else {
+            printf("alloc NO_MEMORY\n");
+            //out_of_memory(c, "SERVER_ERROR Out of memory allocating item");
+            /* This error generating method eats the swallow value. Add here. */
+            //c->sbytes = vlen;
+            status = NO_MEMORY;
+        }
+        /* FIXME: losing c->cmd since it's translated below. refactor? */
+        //LOGGER_LOG(c->thread->l, LOG_MUTATIONS, LOGGER_ITEM_STORE,
+          //      NULL, status, 0, key, nkey, req->message.body.expiration,
+            //    ITEM_clsid(it), c->sfd);
+
+        /* Avoid stale data persisting in cache because we failed alloc.
+         * Unacceptable for SET. Anywhere else too? */
+        if (c->cmd == PROTOCOL_BINARY_CMD_SET) {
+            it = item_get(key, nkey, c, DONT_UPDATE);
+            if (it) {
+                item_unlink(it);
+                STORAGE_delete(c->thread->storage, it);
+                item_remove(it);
+            }
+        }
+
+        /* swallow the data line */
+        conn_set_state(c, conn_swallow);
+        return it;
+    }
+
+    ITEM_set_cas(it, c->binary_header.request.cas);
+
+    switch (cmd) {
+        case PROTOCOL_BINARY_CMD_ADD:
+            c->cmd = NREAD_ADD;
+            break;
+        case PROTOCOL_BINARY_CMD_SET:
+            c->cmd = NREAD_SET;
+            break;
+        case PROTOCOL_BINARY_CMD_REPLACE:
+            c->cmd = NREAD_REPLACE;
+            break;
+        default:
+            assert(0);
+    }
+
+    if (ITEM_get_cas(it) != 0) {
+        c->cmd = NREAD_CAS;
+    }
+    c->item = it;
+#ifdef NEED_ALIGN
+    if (it->it_flags & ITEM_CHUNKED) {
+        c->ritem = ITEM_schunk(it);
+    } else {
+        c->ritem = ITEM_data(it);
+    }
+#else
+    c->ritem = ITEM_data(it);
+#endif
+    c->rlbytes = vlen;
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.bytes_read += vlen;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+    c->substate = bin_read_set_value;
+    complete_update_cachebench(c, vlen);
+    return it;
+}
+
+static void process_bin_append_prepend(conn *c) {
+    char *key;
+    int nkey;
+    int vlen;
+    item *it;
+
+    assert(c != NULL);
+
+    key = binary_get_key(c);
+    nkey = c->binary_header.request.keylen;
+    vlen = c->binary_header.request.bodylen - nkey;
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "Value len is %d\n", vlen);
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_set(key, nkey);
+    }
+
+    it = item_alloc(key, nkey, 0, 0, vlen+2);
+
+    if (it == 0) {
+        if (! item_size_ok(nkey, 0, vlen + 2)) {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_E2BIG, NULL, vlen);
+        } else {
+            out_of_memory(c, "SERVER_ERROR Out of memory allocating item");
+            /* OOM calls eat the swallow value. Add here. */
+            c->sbytes = vlen;
+        }
+        /* swallow the data line */
+        conn_set_state(c, conn_swallow);
+        return;
+    }
+
+    ITEM_set_cas(it, c->binary_header.request.cas);
+
+    switch (c->cmd) {
+        case PROTOCOL_BINARY_CMD_APPEND:
+            c->cmd = NREAD_APPEND;
+            break;
+        case PROTOCOL_BINARY_CMD_PREPEND:
+            c->cmd = NREAD_PREPEND;
+            break;
+        default:
+            assert(0);
+    }
+
+    c->item = it;
+#ifdef NEED_ALIGN
+    if (it->it_flags & ITEM_CHUNKED) {
+        c->ritem = ITEM_schunk(it);
+    } else {
+        c->ritem = ITEM_data(it);
+    }
+#else
+    c->ritem = ITEM_data(it);
+#endif
+    c->rlbytes = vlen;
+    conn_set_state(c, conn_nread);
+    c->substate = bin_read_set_value;
+}
+
+static void process_bin_flush(conn *c, char *extbuf) {
+    time_t exptime = 0;
+    protocol_binary_request_flush* req = (void *)extbuf;
+    rel_time_t new_oldest = 0;
+
+    if (!settings.flush_enabled) {
+      // flush_all is not allowed but we log it on stats
+      write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, NULL, 0);
+      return;
+    }
+
+    if (c->binary_header.request.extlen == sizeof(req->message.body)) {
+        exptime = ntohl(req->message.body.expiration);
+    }
+
+    if (exptime > 0) {
+        new_oldest = realtime(exptime);
+    } else {
+        new_oldest = current_time;
+    }
+    if (settings.use_cas) {
+        settings.oldest_live = new_oldest - 1;
+        if (settings.oldest_live <= current_time)
+            settings.oldest_cas = get_cas_id();
+    } else {
+        settings.oldest_live = new_oldest;
+    }
+
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.flush_cmds++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    write_bin_response(c, NULL, 0, 0, 0);
+}
+
+static void process_bin_delete(conn *c) {
+    item *it;
+    uint32_t hv;
+
+    char* key = binary_get_key(c);
+    size_t nkey = c->binary_header.request.keylen;
+
+    assert(c != NULL);
+
+    if (settings.verbose > 1) {
+        int ii;
+        fprintf(stderr, "Deleting ");
+        for (ii = 0; ii < nkey; ++ii) {
+            fprintf(stderr, "%c", key[ii]);
+        }
+        fprintf(stderr, "\n");
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_delete(key, nkey);
+    }
+
+    it = item_get_locked(key, nkey, c, DONT_UPDATE, &hv);
+    if (it) {
+        uint64_t cas = c->binary_header.request.cas;
+        if (cas == 0 || cas == ITEM_get_cas(it)) {
+            MEMCACHED_COMMAND_DELETE(c->sfd, ITEM_key(it), it->nkey);
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.slab_stats[ITEM_clsid(it)].delete_hits++;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+            do_item_unlink(it, hv);
+            STORAGE_delete(c->thread->storage, it);
+            write_bin_response(c, NULL, 0, 0, 0);
+        } else {
+            write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, NULL, 0);
+        }
+        do_item_remove(it);      /* release our reference */
+    } else {
+        write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, NULL, 0);
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.delete_misses++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+    }
+    item_unlock(hv);
+}
+
+static void complete_nread_binary(conn *c) {
+    assert(c != NULL);
+    assert(c->cmd >= 0);
+
+    switch(c->substate) {
+    case bin_read_set_value:
+        complete_update_bin(c);
+        break;
+    case bin_reading_sasl_auth_data:
+        process_bin_complete_sasl_auth(c);
+        if (c->item) {
+            do_item_remove(c->item);
+            c->item = NULL;
+        }
+        break;
+    default:
+        fprintf(stderr, "Not handling substate %d\n", c->substate);
+        assert(0);
+    }
+}
+
+static void reset_cmd_handler(conn *c) {
+    c->cmd = -1;
+    c->substate = bin_no_state;
+    if (c->item != NULL) {
+        // TODO: Any other way to get here?
+        // SASL auth was mistakenly using it. Nothing else should?
+        item_remove(c->item);
+        c->item = NULL;
+    }
+    if (c->rbytes > 0) {
+        conn_set_state(c, conn_parse_cmd);
+    } else if (c->resp_head) {
+        conn_set_state(c, conn_mwrite);
+    } else {
+        conn_set_state(c, conn_waiting);
+    }
+}
+
+static void complete_nread(conn *c) {
+    assert(c != NULL);
+    assert(c->protocol == ascii_prot
+           || c->protocol == binary_prot);
+
+    if (c->protocol == ascii_prot) {
+        complete_nread_ascii(c);
+    } else if (c->protocol == binary_prot) {
+        complete_nread_binary(c);
+    }
+}
+
+/* Destination must always be chunked */
+/* This should be part of item.c */
+static int _store_item_copy_chunks(item *d_it, item *s_it, const int len) {
+    item_chunk *dch = (item_chunk *) ITEM_schunk(d_it);
+    /* Advance dch until we find free space */
+    while (dch->size == dch->used) {
+        if (dch->next) {
+            dch = dch->next;
+        } else {
+            break;
+        }
+    }
+
+    if (s_it->it_flags & ITEM_CHUNKED) {
+        int remain = len;
+        item_chunk *sch = (item_chunk *) ITEM_schunk(s_it);
+        int copied = 0;
+        /* Fills dch's to capacity, not straight copy sch in case data is
+         * being added or removed (ie append/prepend)
+         */
+        while (sch && dch && remain) {
+            assert(dch->used <= dch->size);
+            int todo = (dch->size - dch->used < sch->used - copied)
+                ? dch->size - dch->used : sch->used - copied;
+            if (remain < todo)
+                todo = remain;
+            memcpy(dch->data + dch->used, sch->data + copied, todo);
+            dch->used += todo;
+            copied += todo;
+            remain -= todo;
+            assert(dch->used <= dch->size);
+            if (dch->size == dch->used) {
+                item_chunk *tch = do_item_alloc_chunk(dch, remain);
+                if (tch) {
+                    dch = tch;
+                } else {
+                    return -1;
+                }
+            }
+            assert(copied <= sch->used);
+            if (copied == sch->used) {
+                copied = 0;
+                sch = sch->next;
+            }
+        }
+        /* assert that the destination had enough space for the source */
+        assert(remain == 0);
+    } else {
+        int done = 0;
+        /* Fill dch's via a non-chunked item. */
+        while (len > done && dch) {
+            int todo = (dch->size - dch->used < len - done)
+                ? dch->size - dch->used : len - done;
+            //assert(dch->size - dch->used != 0);
+            memcpy(dch->data + dch->used, ITEM_data(s_it) + done, todo);
+            done += todo;
+            dch->used += todo;
+            assert(dch->used <= dch->size);
+            if (dch->size == dch->used) {
+                item_chunk *tch = do_item_alloc_chunk(dch, len - done);
+                if (tch) {
+                    dch = tch;
+                } else {
+                    return -1;
+                }
+            }
+        }
+        assert(len == done);
+    }
+    return 0;
+}
+
+static int _store_item_copy_data(int comm, item *old_it, item *new_it, item *add_it) {
+    if (comm == NREAD_APPEND) {
+        if (new_it->it_flags & ITEM_CHUNKED) {
+            if (_store_item_copy_chunks(new_it, old_it, old_it->nbytes - 2) == -1 ||
+                _store_item_copy_chunks(new_it, add_it, add_it->nbytes) == -1) {
+                return -1;
+            }
+        } else {
+            memcpy(ITEM_data(new_it), ITEM_data(old_it), old_it->nbytes);
+            memcpy(ITEM_data(new_it) + old_it->nbytes - 2 /* CRLF */, ITEM_data(add_it), add_it->nbytes);
+        }
+    } else {
+        /* NREAD_PREPEND */
+        if (new_it->it_flags & ITEM_CHUNKED) {
+            if (_store_item_copy_chunks(new_it, add_it, add_it->nbytes - 2) == -1 ||
+                _store_item_copy_chunks(new_it, old_it, old_it->nbytes) == -1) {
+                return -1;
+            }
+        } else {
+            memcpy(ITEM_data(new_it), ITEM_data(add_it), add_it->nbytes);
+            memcpy(ITEM_data(new_it) + add_it->nbytes - 2 /* CRLF */, ITEM_data(old_it), old_it->nbytes);
+        }
+    }
+    return 0;
+}
+
+/*
+ * Stores an item in the cache according to the semantics of one of the set
+ * commands. Protected by the item lock.
+ *
+ * Returns the state of storage.
+ */
+enum store_item_type do_store_item(item *it, int comm, conn *c, const uint32_t hv) {
+    char *key = ITEM_key(it);
+    item *old_it = do_item_get(key, it->nkey, hv, c, DONT_UPDATE);
+    enum store_item_type stored = NOT_STORED;
+
+    enum cas_result { CAS_NONE, CAS_MATCH, CAS_BADVAL, CAS_STALE, CAS_MISS };
+
+    item *new_it = NULL;
+    uint32_t flags;
+
+    /* Do the CAS test up front so we can apply to all store modes */
+    enum cas_result cas_res = CAS_NONE;
+
+    bool do_store = false;
+    if (old_it != NULL) {
+        // Most of the CAS work requires something to compare to.
+        uint64_t it_cas = ITEM_get_cas(it);
+        uint64_t old_cas = ITEM_get_cas(old_it);
+        if (it_cas == 0) {
+            cas_res = CAS_NONE;
+        } else if (it_cas == old_cas) {
+            cas_res = CAS_MATCH;
+        } else if (c->set_stale && it_cas < old_cas) {
+            cas_res = CAS_STALE;
+        } else {
+            cas_res = CAS_BADVAL;
+        }
+
+        switch (comm) {
+            case NREAD_ADD:
+                /* add only adds a nonexistent item, but promote to head of LRU */
+                do_item_update(old_it);
+                break;
+            case NREAD_CAS:
+                if (cas_res == CAS_MATCH) {
+                    // cas validates
+                    // it and old_it may belong to different classes.
+                    // I'm updating the stats for the one that's getting pushed out
+                    pthread_mutex_lock(&c->thread->stats.mutex);
+                    c->thread->stats.slab_stats[ITEM_clsid(old_it)].cas_hits++;
+                    pthread_mutex_unlock(&c->thread->stats.mutex);
+                    do_store = true;
+                } else if (cas_res == CAS_STALE) {
+                    // if we're allowed to set a stale value, CAS must be lower than
+                    // the current item's CAS.
+                    // This replaces the value, but should preserve TTL, and stale
+                    // item marker bit + token sent if exists.
+                    it->exptime = old_it->exptime;
+                    it->it_flags |= ITEM_STALE;
+                    if (old_it->it_flags & ITEM_TOKEN_SENT) {
+                        it->it_flags |= ITEM_TOKEN_SENT;
+                    }
+
+                    pthread_mutex_lock(&c->thread->stats.mutex);
+                    c->thread->stats.slab_stats[ITEM_clsid(old_it)].cas_hits++;
+                    pthread_mutex_unlock(&c->thread->stats.mutex);
+                    do_store = true;
+                } else {
+                    // NONE or BADVAL are the same for CAS cmd
+                    pthread_mutex_lock(&c->thread->stats.mutex);
+                    c->thread->stats.slab_stats[ITEM_clsid(old_it)].cas_badval++;
+                    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+                    if (settings.verbose > 1) {
+                        fprintf(stderr, "CAS:  failure: expected %llu, got %llu\n",
+                                (unsigned long long)ITEM_get_cas(old_it),
+                                (unsigned long long)ITEM_get_cas(it));
+                    }
+                    stored = EXISTS;
+                }
+                break;
+            case NREAD_APPEND:
+            case NREAD_PREPEND:
+                if (cas_res != CAS_NONE && cas_res != CAS_MATCH) {
+                    stored = EXISTS;
+                    break;
+                }
+#ifdef EXTSTORE
+                if ((old_it->it_flags & ITEM_HDR) != 0) {
+                    /* block append/prepend from working with extstore-d items.
+                     * leave response code to NOT_STORED default */
+                    break;
+                }
+#endif
+                /* we have it and old_it here - alloc memory to hold both */
+                FLAGS_CONV(old_it, flags);
+                new_it = do_item_alloc(key, it->nkey, flags, old_it->exptime, it->nbytes + old_it->nbytes - 2 /* CRLF */);
+
+                // OOM trying to copy.
+                if (new_it == NULL)
+                    break;
+                /* copy data from it and old_it to new_it */
+                if (_store_item_copy_data(comm, old_it, new_it, it) == -1) {
+                    // failed data copy
+                    break;
+                } else {
+                    // refcount of new_it is 1 here. will end up 2 after link.
+                    // it's original ref is managed outside of this function
+                    it = new_it;
+                    do_store = true;
+                }
+                break;
+            case NREAD_REPLACE:
+            case NREAD_SET:
+                do_store = true;
+                break;
+        }
+
+        if (do_store) {
+            STORAGE_delete(c->thread->storage, old_it);
+            item_replace(old_it, it, hv);
+            stored = STORED;
+        }
+
+        do_item_remove(old_it);         /* release our reference */
+        if (new_it != NULL) {
+            // append/prepend end up with an extra reference for new_it.
+            do_item_remove(new_it);
+        }
+    } else {
+        /* No pre-existing item to replace or compare to. */
+        if (ITEM_get_cas(it) != 0) {
+            /* Asked for a CAS match but nothing to compare it to. */
+            cas_res = CAS_MISS;
+        }
+
+        switch (comm) {
+            case NREAD_ADD:
+            case NREAD_SET:
+                do_store = true;
+                break;
+            case NREAD_CAS:
+                // LRU expired
+                stored = NOT_FOUND;
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.cas_misses++;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+                break;
+            case NREAD_REPLACE:
+            case NREAD_APPEND:
+            case NREAD_PREPEND:
+                /* Requires an existing item. */
+                break;
+        }
+
+        if (do_store) {
+            do_item_link(it, hv);
+            stored = STORED;
+        }
+    }
+
+    if (stored == STORED) {
+        c->cas = ITEM_get_cas(it);
+    }
+    //LOGGER_LOG(c->thread->l, LOG_MUTATIONS, LOGGER_ITEM_STORE, NULL,
+    //        stored, comm, ITEM_key(it), it->nkey, it->exptime, ITEM_clsid(it), c->sfd);
+
+    return stored;
+}
+
+typedef struct token_s {
+    char *value;
+    size_t length;
+} token_t;
+
+#define COMMAND_TOKEN 0
+#define SUBCOMMAND_TOKEN 1
+#define KEY_TOKEN 1
+
+#define MAX_TOKENS 24
+
+#define WANT_TOKENS(ntokens, min, max) \
+    do { \
+        if ((min != -1 && ntokens < min) || (max != -1 && ntokens > max)) { \
+            out_string(c, "ERROR"); \
+            return; \
+        } \
+    } while (0)
+
+#define WANT_TOKENS_OR(ntokens, a, b) \
+    do { \
+        if (ntokens != a && ntokens != b) { \
+            out_string(c, "ERROR"); \
+            return; \
+        } \
+    } while (0)
+
+#define WANT_TOKENS_MIN(ntokens, min) \
+    do { \
+        if (ntokens < min) { \
+            out_string(c, "ERROR"); \
+            return; \
+        } \
+    } while (0)
+
+/*
+ * Tokenize the command string by replacing whitespace with '\0' and update
+ * the token array tokens with pointer to start of each token and length.
+ * Returns total number of tokens.  The last valid token is the terminal
+ * token (value points to the first unprocessed character of the string and
+ * length zero).
+ *
+ * Usage example:
+ *
+ *  while(tokenize_command(command, ncommand, tokens, max_tokens) > 0) {
+ *      for(int ix = 0; tokens[ix].length != 0; ix++) {
+ *          ...
+ *      }
+ *      ncommand = tokens[ix].value - command;
+ *      command  = tokens[ix].value;
+ *   }
+ */
+static size_t tokenize_command(char *command, token_t *tokens, const size_t max_tokens) {
+    char *s, *e;
+    size_t ntokens = 0;
+    size_t len = strlen(command);
+    unsigned int i = 0;
+
+    assert(command != NULL && tokens != NULL && max_tokens > 1);
+
+    s = e = command;
+    for (i = 0; i < len; i++) {
+        if (*e == ' ') {
+            if (s != e) {
+                tokens[ntokens].value = s;
+                tokens[ntokens].length = e - s;
+                ntokens++;
+                *e = '\0';
+                if (ntokens == max_tokens - 1) {
+                    e++;
+                    s = e; /* so we don't add an extra token */
+                    break;
+                }
+            }
+            s = e + 1;
+        }
+        e++;
+    }
+
+    if (s != e) {
+        tokens[ntokens].value = s;
+        tokens[ntokens].length = e - s;
+        ntokens++;
+    }
+
+    /*
+     * If we scanned the whole string, the terminal value pointer is null,
+     * otherwise it is the first unprocessed character.
+     */
+    tokens[ntokens].value =  *e == '\0' ? NULL : e;
+    tokens[ntokens].length = 0;
+    ntokens++;
+
+    return ntokens;
+}
+
+/* set up a connection to write a buffer then free it, used for stats */
+static void write_and_free(conn *c, char *buf, int bytes) {
+    if (buf) {
+        mc_resp *resp = c->resp;
+        resp->write_and_free = buf;
+        resp_add_iov(resp, buf, bytes);
+        conn_set_state(c, conn_new_cmd);
+    } else {
+        out_of_memory(c, "SERVER_ERROR out of memory writing stats");
+    }
+}
+
+static inline bool set_noreply_maybe(conn *c, token_t *tokens, size_t ntokens)
+{
+    int noreply_index = ntokens - 2;
+
+    /*
+      NOTE: this function is not the first place where we are going to
+      send the reply.  We could send it instead from process_command()
+      if the request line has wrong number of tokens.  However parsing
+      malformed line for "noreply" option is not reliable anyway, so
+      it can't be helped.
+    */
+    if (tokens[noreply_index].value
+        && strcmp(tokens[noreply_index].value, "noreply") == 0) {
+        c->noreply = true;
+    }
+    return c->noreply;
+}
+
+void append_stat(const char *name, ADD_STAT add_stats, conn *c,
+                 const char *fmt, ...) {
+    char val_str[STAT_VAL_LEN];
+    int vlen;
+    va_list ap;
+
+    assert(name);
+    assert(add_stats);
+    assert(c);
+    assert(fmt);
+
+    va_start(ap, fmt);
+    vlen = vsnprintf(val_str, sizeof(val_str) - 1, fmt, ap);
+    va_end(ap);
+
+    add_stats(name, strlen(name), val_str, vlen, c);
+}
+
+inline static void process_stats_detail(conn *c, const char *command) {
+    assert(c != NULL);
+
+    if (strcmp(command, "on") == 0) {
+        settings.detail_enabled = 1;
+        out_string(c, "OK");
+    }
+    else if (strcmp(command, "off") == 0) {
+        settings.detail_enabled = 0;
+        out_string(c, "OK");
+    }
+    else if (strcmp(command, "dump") == 0) {
+        int len;
+        char *stats = stats_prefix_dump(&len);
+        write_and_free(c, stats, len);
+    }
+    else {
+        out_string(c, "CLIENT_ERROR usage: stats detail on|off|dump");
+    }
+}
+
+/* return server specific stats only */
+static void server_stats(ADD_STAT add_stats, conn *c) {
+    pid_t pid = getpid();
+    rel_time_t now = current_time;
+
+    struct thread_stats thread_stats;
+    threadlocal_stats_aggregate(&thread_stats);
+    struct slab_stats slab_stats;
+    slab_stats_aggregate(&thread_stats, &slab_stats);
+#ifdef EXTSTORE
+    struct extstore_stats st;
+#endif
+#ifndef WIN32
+    struct rusage usage;
+    getrusage(RUSAGE_SELF, &usage);
+#endif /* !WIN32 */
+
+    STATS_LOCK();
+
+    APPEND_STAT("pid", "%lu", (long)pid);
+    APPEND_STAT("uptime", "%u", now - ITEM_UPDATE_INTERVAL);
+    APPEND_STAT("time", "%ld", now + (long)process_started);
+    APPEND_STAT("version", "%s", VERSION);
+    APPEND_STAT("libevent", "%s", event_get_version());
+    APPEND_STAT("pointer_size", "%d", (int)(8 * sizeof(void *)));
+
+#ifndef WIN32
+    append_stat("rusage_user", add_stats, c, "%ld.%06ld",
+                (long)usage.ru_utime.tv_sec,
+                (long)usage.ru_utime.tv_usec);
+    append_stat("rusage_system", add_stats, c, "%ld.%06ld",
+                (long)usage.ru_stime.tv_sec,
+                (long)usage.ru_stime.tv_usec);
+#endif /* !WIN32 */
+
+    APPEND_STAT("max_connections", "%d", settings.maxconns);
+    APPEND_STAT("curr_connections", "%llu", (unsigned long long)stats_state.curr_conns - 1);
+    APPEND_STAT("total_connections", "%llu", (unsigned long long)stats.total_conns);
+    if (settings.maxconns_fast) {
+        APPEND_STAT("rejected_connections", "%llu", (unsigned long long)stats.rejected_conns);
+    }
+    APPEND_STAT("connection_structures", "%u", stats_state.conn_structs);
+    APPEND_STAT("response_obj_bytes", "%llu", (unsigned long long)thread_stats.response_obj_bytes);
+    APPEND_STAT("response_obj_total", "%llu", (unsigned long long)thread_stats.response_obj_total);
+    APPEND_STAT("response_obj_free", "%llu", (unsigned long long)thread_stats.response_obj_free);
+    APPEND_STAT("response_obj_oom", "%llu", (unsigned long long)thread_stats.response_obj_oom);
+    APPEND_STAT("read_buf_bytes", "%llu", (unsigned long long)thread_stats.read_buf_bytes);
+    APPEND_STAT("read_buf_bytes_free", "%llu", (unsigned long long)thread_stats.read_buf_bytes_free);
+    APPEND_STAT("read_buf_oom", "%llu", (unsigned long long)thread_stats.read_buf_oom);
+    APPEND_STAT("reserved_fds", "%u", stats_state.reserved_fds);
+    APPEND_STAT("cmd_get", "%llu", (unsigned long long)thread_stats.get_cmds);
+    APPEND_STAT("cmd_set", "%llu", (unsigned long long)slab_stats.set_cmds);
+    APPEND_STAT("cmd_flush", "%llu", (unsigned long long)thread_stats.flush_cmds);
+    APPEND_STAT("cmd_touch", "%llu", (unsigned long long)thread_stats.touch_cmds);
+    APPEND_STAT("cmd_meta", "%llu", (unsigned long long)thread_stats.meta_cmds);
+    APPEND_STAT("get_hits", "%llu", (unsigned long long)slab_stats.get_hits);
+    APPEND_STAT("get_misses", "%llu", (unsigned long long)thread_stats.get_misses);
+    APPEND_STAT("get_expired", "%llu", (unsigned long long)thread_stats.get_expired);
+    APPEND_STAT("get_flushed", "%llu", (unsigned long long)thread_stats.get_flushed);
+#ifdef EXTSTORE
+    if (c->thread->storage) {
+        APPEND_STAT("get_extstore", "%llu", (unsigned long long)thread_stats.get_extstore);
+        APPEND_STAT("get_aborted_extstore", "%llu", (unsigned long long)thread_stats.get_aborted_extstore);
+        APPEND_STAT("get_oom_extstore", "%llu", (unsigned long long)thread_stats.get_oom_extstore);
+        APPEND_STAT("recache_from_extstore", "%llu", (unsigned long long)thread_stats.recache_from_extstore);
+        APPEND_STAT("miss_from_extstore", "%llu", (unsigned long long)thread_stats.miss_from_extstore);
+        APPEND_STAT("badcrc_from_extstore", "%llu", (unsigned long long)thread_stats.badcrc_from_extstore);
+    }
+#endif
+    APPEND_STAT("delete_misses", "%llu", (unsigned long long)thread_stats.delete_misses);
+    APPEND_STAT("delete_hits", "%llu", (unsigned long long)slab_stats.delete_hits);
+    APPEND_STAT("incr_misses", "%llu", (unsigned long long)thread_stats.incr_misses);
+    APPEND_STAT("incr_hits", "%llu", (unsigned long long)slab_stats.incr_hits);
+    APPEND_STAT("decr_misses", "%llu", (unsigned long long)thread_stats.decr_misses);
+    APPEND_STAT("decr_hits", "%llu", (unsigned long long)slab_stats.decr_hits);
+    APPEND_STAT("cas_misses", "%llu", (unsigned long long)thread_stats.cas_misses);
+    APPEND_STAT("cas_hits", "%llu", (unsigned long long)slab_stats.cas_hits);
+    APPEND_STAT("cas_badval", "%llu", (unsigned long long)slab_stats.cas_badval);
+    APPEND_STAT("touch_hits", "%llu", (unsigned long long)slab_stats.touch_hits);
+    APPEND_STAT("touch_misses", "%llu", (unsigned long long)thread_stats.touch_misses);
+    APPEND_STAT("auth_cmds", "%llu", (unsigned long long)thread_stats.auth_cmds);
+    APPEND_STAT("auth_errors", "%llu", (unsigned long long)thread_stats.auth_errors);
+    if (settings.idle_timeout) {
+        APPEND_STAT("idle_kicks", "%llu", (unsigned long long)thread_stats.idle_kicks);
+    }
+    APPEND_STAT("bytes_read", "%llu", (unsigned long long)thread_stats.bytes_read);
+    APPEND_STAT("bytes_written", "%llu", (unsigned long long)thread_stats.bytes_written);
+    APPEND_STAT("limit_maxbytes", "%llu", (unsigned long long)settings.maxbytes);
+    APPEND_STAT("accepting_conns", "%u", stats_state.accepting_conns);
+    APPEND_STAT("listen_disabled_num", "%llu", (unsigned long long)stats.listen_disabled_num);
+    APPEND_STAT("time_in_listen_disabled_us", "%llu", stats.time_in_listen_disabled_us);
+    APPEND_STAT("threads", "%d", settings.num_threads);
+    APPEND_STAT("conn_yields", "%llu", (unsigned long long)thread_stats.conn_yields);
+    APPEND_STAT("hash_power_level", "%u", stats_state.hash_power_level);
+    APPEND_STAT("hash_bytes", "%llu", (unsigned long long)stats_state.hash_bytes);
+    APPEND_STAT("hash_is_expanding", "%u", stats_state.hash_is_expanding);
+    if (settings.slab_reassign) {
+        APPEND_STAT("slab_reassign_rescues", "%llu", stats.slab_reassign_rescues);
+        APPEND_STAT("slab_reassign_chunk_rescues", "%llu", stats.slab_reassign_chunk_rescues);
+        APPEND_STAT("slab_reassign_evictions_nomem", "%llu", stats.slab_reassign_evictions_nomem);
+        APPEND_STAT("slab_reassign_inline_reclaim", "%llu", stats.slab_reassign_inline_reclaim);
+        APPEND_STAT("slab_reassign_busy_items", "%llu", stats.slab_reassign_busy_items);
+        APPEND_STAT("slab_reassign_busy_deletes", "%llu", stats.slab_reassign_busy_deletes);
+        APPEND_STAT("slab_reassign_running", "%u", stats_state.slab_reassign_running);
+        APPEND_STAT("slabs_moved", "%llu", stats.slabs_moved);
+    }
+    if (settings.lru_crawler) {
+        APPEND_STAT("lru_crawler_running", "%u", stats_state.lru_crawler_running);
+        APPEND_STAT("lru_crawler_starts", "%u", stats.lru_crawler_starts);
+    }
+    if (settings.lru_maintainer_thread) {
+        APPEND_STAT("lru_maintainer_juggles", "%llu", (unsigned long long)stats.lru_maintainer_juggles);
+    }
+    APPEND_STAT("malloc_fails", "%llu",
+                (unsigned long long)stats.malloc_fails);
+    APPEND_STAT("log_worker_dropped", "%llu", (unsigned long long)stats.log_worker_dropped);
+    APPEND_STAT("log_worker_written", "%llu", (unsigned long long)stats.log_worker_written);
+    APPEND_STAT("log_watcher_skipped", "%llu", (unsigned long long)stats.log_watcher_skipped);
+    APPEND_STAT("log_watcher_sent", "%llu", (unsigned long long)stats.log_watcher_sent);
+    STATS_UNLOCK();
+#ifdef EXTSTORE
+    if (c->thread->storage) {
+        STATS_LOCK();
+        APPEND_STAT("extstore_compact_lost", "%llu", (unsigned long long)stats.extstore_compact_lost);
+        APPEND_STAT("extstore_compact_rescues", "%llu", (unsigned long long)stats.extstore_compact_rescues);
+        APPEND_STAT("extstore_compact_skipped", "%llu", (unsigned long long)stats.extstore_compact_skipped);
+        STATS_UNLOCK();
+        extstore_get_stats(c->thread->storage, &st);
+        APPEND_STAT("extstore_page_allocs", "%llu", (unsigned long long)st.page_allocs);
+        APPEND_STAT("extstore_page_evictions", "%llu", (unsigned long long)st.page_evictions);
+        APPEND_STAT("extstore_page_reclaims", "%llu", (unsigned long long)st.page_reclaims);
+        APPEND_STAT("extstore_pages_free", "%llu", (unsigned long long)st.pages_free);
+        APPEND_STAT("extstore_pages_used", "%llu", (unsigned long long)st.pages_used);
+        APPEND_STAT("extstore_objects_evicted", "%llu", (unsigned long long)st.objects_evicted);
+        APPEND_STAT("extstore_objects_read", "%llu", (unsigned long long)st.objects_read);
+        APPEND_STAT("extstore_objects_written", "%llu", (unsigned long long)st.objects_written);
+        APPEND_STAT("extstore_objects_used", "%llu", (unsigned long long)st.objects_used);
+        APPEND_STAT("extstore_bytes_evicted", "%llu", (unsigned long long)st.bytes_evicted);
+        APPEND_STAT("extstore_bytes_written", "%llu", (unsigned long long)st.bytes_written);
+        APPEND_STAT("extstore_bytes_read", "%llu", (unsigned long long)st.bytes_read);
+        APPEND_STAT("extstore_bytes_used", "%llu", (unsigned long long)st.bytes_used);
+        APPEND_STAT("extstore_bytes_fragmented", "%llu", (unsigned long long)st.bytes_fragmented);
+        APPEND_STAT("extstore_limit_maxbytes", "%llu", (unsigned long long)(st.page_count * st.page_size));
+        APPEND_STAT("extstore_io_queue", "%llu", (unsigned long long)(st.io_queue));
+    }
+#endif
+#ifdef TLS
+    if (settings.ssl_enabled) {
+        if (settings.ssl_session_cache) {
+            APPEND_STAT("ssl_new_sessions", "%llu", (unsigned long long)stats.ssl_new_sessions);
+        }
+        APPEND_STAT("ssl_handshake_errors", "%llu", (unsigned long long)stats.ssl_handshake_errors);
+        APPEND_STAT("time_since_server_cert_refresh", "%u", now - settings.ssl_last_cert_refresh_time);
+    }
+#endif
+}
+
+static void process_stat_settings(ADD_STAT add_stats, void *c) {
+    assert(add_stats);
+    APPEND_STAT("maxbytes", "%llu", (unsigned long long)settings.maxbytes);
+    APPEND_STAT("maxconns", "%d", settings.maxconns);
+    APPEND_STAT("tcpport", "%d", settings.port);
+    APPEND_STAT("udpport", "%d", settings.udpport);
+    APPEND_STAT("inter", "%s", settings.inter ? settings.inter : "NULL");
+    APPEND_STAT("verbosity", "%d", settings.verbose);
+    APPEND_STAT("oldest", "%lu", (unsigned long)settings.oldest_live);
+    APPEND_STAT("evictions", "%s", settings.evict_to_free ? "on" : "off");
+    APPEND_STAT("domain_socket", "%s",
+                settings.socketpath ? settings.socketpath : "NULL");
+    APPEND_STAT("umask", "%o", settings.access);
+    APPEND_STAT("growth_factor", "%.2f", settings.factor);
+    APPEND_STAT("chunk_size", "%d", settings.chunk_size);
+    APPEND_STAT("num_threads", "%d", settings.num_threads);
+    APPEND_STAT("num_threads_per_udp", "%d", settings.num_threads_per_udp);
+    APPEND_STAT("stat_key_prefix", "%c", settings.prefix_delimiter);
+    APPEND_STAT("detail_enabled", "%s",
+                settings.detail_enabled ? "yes" : "no");
+    APPEND_STAT("reqs_per_event", "%d", settings.reqs_per_event);
+    APPEND_STAT("cas_enabled", "%s", settings.use_cas ? "yes" : "no");
+    APPEND_STAT("tcp_backlog", "%d", settings.backlog);
+    APPEND_STAT("binding_protocol", "%s",
+                prot_text(settings.binding_protocol));
+    APPEND_STAT("auth_enabled_sasl", "%s", settings.sasl ? "yes" : "no");
+    APPEND_STAT("auth_enabled_ascii", "%s", settings.auth_file ? settings.auth_file : "no");
+    APPEND_STAT("item_size_max", "%d", settings.item_size_max);
+    APPEND_STAT("maxconns_fast", "%s", settings.maxconns_fast ? "yes" : "no");
+    APPEND_STAT("hashpower_init", "%d", settings.hashpower_init);
+    APPEND_STAT("slab_reassign", "%s", settings.slab_reassign ? "yes" : "no");
+    APPEND_STAT("slab_automove", "%d", settings.slab_automove);
+    APPEND_STAT("slab_automove_ratio", "%.2f", settings.slab_automove_ratio);
+    APPEND_STAT("slab_automove_window", "%u", settings.slab_automove_window);
+    APPEND_STAT("slab_chunk_max", "%d", settings.slab_chunk_size_max);
+    APPEND_STAT("lru_crawler", "%s", settings.lru_crawler ? "yes" : "no");
+    APPEND_STAT("lru_crawler_sleep", "%d", settings.lru_crawler_sleep);
+    APPEND_STAT("lru_crawler_tocrawl", "%lu", (unsigned long)settings.lru_crawler_tocrawl);
+    APPEND_STAT("tail_repair_time", "%d", settings.tail_repair_time);
+    APPEND_STAT("flush_enabled", "%s", settings.flush_enabled ? "yes" : "no");
+    APPEND_STAT("dump_enabled", "%s", settings.dump_enabled ? "yes" : "no");
+    APPEND_STAT("hash_algorithm", "%s", settings.hash_algorithm);
+    APPEND_STAT("lru_maintainer_thread", "%s", settings.lru_maintainer_thread ? "yes" : "no");
+    APPEND_STAT("lru_segmented", "%s", settings.lru_segmented ? "yes" : "no");
+    APPEND_STAT("hot_lru_pct", "%d", settings.hot_lru_pct);
+    APPEND_STAT("warm_lru_pct", "%d", settings.warm_lru_pct);
+    APPEND_STAT("hot_max_factor", "%.2f", settings.hot_max_factor);
+    APPEND_STAT("warm_max_factor", "%.2f", settings.warm_max_factor);
+    APPEND_STAT("temp_lru", "%s", settings.temp_lru ? "yes" : "no");
+    APPEND_STAT("temporary_ttl", "%u", settings.temporary_ttl);
+    APPEND_STAT("idle_timeout", "%d", settings.idle_timeout);
+    APPEND_STAT("watcher_logbuf_size", "%u", settings.logger_watcher_buf_size);
+    APPEND_STAT("worker_logbuf_size", "%u", settings.logger_buf_size);
+    APPEND_STAT("resp_obj_mem_limit", "%u", settings.resp_obj_mem_limit);
+    APPEND_STAT("read_buf_mem_limit", "%u", settings.read_buf_mem_limit);
+    APPEND_STAT("track_sizes", "%s", item_stats_sizes_status() ? "yes" : "no");
+    APPEND_STAT("inline_ascii_response", "%s", "no"); // setting is dead, cannot be yes.
+#ifdef HAVE_DROP_PRIVILEGES
+    APPEND_STAT("drop_privileges", "%s", settings.drop_privileges ? "yes" : "no");
+#endif
+#ifdef EXTSTORE
+    APPEND_STAT("ext_item_size", "%u", settings.ext_item_size);
+    APPEND_STAT("ext_item_age", "%u", settings.ext_item_age);
+    APPEND_STAT("ext_low_ttl", "%u", settings.ext_low_ttl);
+    APPEND_STAT("ext_recache_rate", "%u", settings.ext_recache_rate);
+    APPEND_STAT("ext_wbuf_size", "%u", settings.ext_wbuf_size);
+    APPEND_STAT("ext_compact_under", "%u", settings.ext_compact_under);
+    APPEND_STAT("ext_drop_under", "%u", settings.ext_drop_under);
+    APPEND_STAT("ext_max_frag", "%.2f", settings.ext_max_frag);
+    APPEND_STAT("slab_automove_freeratio", "%.3f", settings.slab_automove_freeratio);
+    APPEND_STAT("ext_drop_unread", "%s", settings.ext_drop_unread ? "yes" : "no");
+#endif
+#ifdef TLS
+    APPEND_STAT("ssl_enabled", "%s", settings.ssl_enabled ? "yes" : "no");
+    APPEND_STAT("ssl_chain_cert", "%s", settings.ssl_chain_cert);
+    APPEND_STAT("ssl_key", "%s", settings.ssl_key);
+    APPEND_STAT("ssl_verify_mode", "%d", settings.ssl_verify_mode);
+    APPEND_STAT("ssl_keyformat", "%d", settings.ssl_keyformat);
+    APPEND_STAT("ssl_ciphers", "%s", settings.ssl_ciphers ? settings.ssl_ciphers : "NULL");
+    APPEND_STAT("ssl_ca_cert", "%s", settings.ssl_ca_cert ? settings.ssl_ca_cert : "NULL");
+    APPEND_STAT("ssl_wbuf_size", "%u", settings.ssl_wbuf_size);
+    APPEND_STAT("ssl_session_cache", "%s", settings.ssl_session_cache ? "yes" : "no");
+#endif
+}
+
+static int nz_strcmp(int nzlength, const char *nz, const char *z) {
+    int zlength=strlen(z);
+    return (zlength == nzlength) && (strncmp(nz, z, zlength) == 0) ? 0 : -1;
+}
+
+static bool get_stats(const char *stat_type, int nkey, ADD_STAT add_stats, void *c) {
+    bool ret = true;
+
+    if (add_stats != NULL) {
+        if (!stat_type) {
+            /* prepare general statistics for the engine */
+            STATS_LOCK();
+            APPEND_STAT("bytes", "%llu", (unsigned long long)stats_state.curr_bytes);
+            APPEND_STAT("curr_items", "%llu", (unsigned long long)stats_state.curr_items);
+            APPEND_STAT("total_items", "%llu", (unsigned long long)stats.total_items);
+            STATS_UNLOCK();
+            APPEND_STAT("slab_global_page_pool", "%u", global_page_pool_size(NULL));
+            item_stats_totals(add_stats, c);
+        } else if (nz_strcmp(nkey, stat_type, "items") == 0) {
+            item_stats(add_stats, c);
+        } else if (nz_strcmp(nkey, stat_type, "slabs") == 0) {
+            slabs_stats(add_stats, c);
+        } else if (nz_strcmp(nkey, stat_type, "sizes") == 0) {
+            item_stats_sizes(add_stats, c);
+        } else if (nz_strcmp(nkey, stat_type, "sizes_enable") == 0) {
+            item_stats_sizes_enable(add_stats, c);
+        } else if (nz_strcmp(nkey, stat_type, "sizes_disable") == 0) {
+            item_stats_sizes_disable(add_stats, c);
+        } else {
+            ret = false;
+        }
+    } else {
+        ret = false;
+    }
+
+    return ret;
+}
+
+static inline void get_conn_text(const conn *c, const int af,
+                char* addr, struct sockaddr *sock_addr) {
+    char addr_text[MAXPATHLEN];
+    addr_text[0] = '\0';
+    const char *protoname = "?";
+    unsigned short port = 0;
+    size_t pathlen = 0;
+
+    switch (af) {
+        case AF_INET:
+            (void) inet_ntop(af,
+                    &((struct sockaddr_in *)sock_addr)->sin_addr,
+                    addr_text,
+                    sizeof(addr_text) - 1);
+            port = ntohs(((struct sockaddr_in *)sock_addr)->sin_port);
+            protoname = IS_UDP(c->transport) ? "udp" : "tcp";
+            break;
+
+        case AF_INET6:
+            addr_text[0] = '[';
+            addr_text[1] = '\0';
+            if (inet_ntop(af,
+                    &((struct sockaddr_in6 *)sock_addr)->sin6_addr,
+                    addr_text + 1,
+                    sizeof(addr_text) - 2)) {
+                strcat(addr_text, "]");
+            }
+            port = ntohs(((struct sockaddr_in6 *)sock_addr)->sin6_port);
+            protoname = IS_UDP(c->transport) ? "udp6" : "tcp6";
+            break;
+
+        case AF_UNIX:
+            // this strncpy call originally could piss off an address
+            // sanitizer; we supplied the size of the dest buf as a limiter,
+            // but optimized versions of strncpy could read past the end of
+            // *src while looking for a null terminator. Since buf and
+            // sun_path here are both on the stack they could even overlap,
+            // which is "undefined". In all OSS versions of strncpy I could
+            // find this has no effect; it'll still only copy until the first null
+            // terminator is found. Thus it's possible to get the OS to
+            // examine past the end of sun_path but it's unclear to me if this
+            // can cause any actual problem.
+            //
+            // We need a safe_strncpy util function but I'll punt on figuring
+            // that out for now.
+            pathlen = sizeof(((struct sockaddr_un *)sock_addr)->sun_path);
+            if (MAXPATHLEN <= pathlen) {
+                pathlen = MAXPATHLEN - 1;
+            }
+            strncpy(addr_text,
+                    ((struct sockaddr_un *)sock_addr)->sun_path,
+                    pathlen);
+            addr_text[pathlen] = '\0';
+            protoname = "unix";
+            break;
+    }
+
+    if (strlen(addr_text) < 2) {
+        /* Most likely this is a connected UNIX-domain client which
+         * has no peer socket address, but there's no portable way
+         * to tell for sure.
+         */
+        sprintf(addr_text, "<AF %d>", af);
+    }
+
+    if (port) {
+        sprintf(addr, "%s:%s:%u", protoname, addr_text, port);
+    } else {
+        sprintf(addr, "%s:%s", protoname, addr_text);
+    }
+}
+
+static void conn_to_str(const conn *c, char *addr, char *svr_addr) {
+    if (!c) {
+        strcpy(addr, "<null>");
+    } else if (c->state == conn_closed) {
+        strcpy(addr, "<closed>");
+    } else {
+        struct sockaddr_in6 local_addr;
+        struct sockaddr *sock_addr = (void *)&c->request_addr;
+
+        /* For listen ports and idle UDP ports, show listen address */
+        if (c->state == conn_listening ||
+                (IS_UDP(c->transport) &&
+                 c->state == conn_read)) {
+            socklen_t local_addr_len = sizeof(local_addr);
+
+            if (getsockname(c->sfd,
+                        (struct sockaddr *)&local_addr,
+                        &local_addr_len) == 0) {
+                sock_addr = (struct sockaddr *)&local_addr;
+            }
+        }
+        get_conn_text(c, sock_addr->sa_family, addr, sock_addr);
+
+        if (c->state != conn_listening && !(IS_UDP(c->transport) &&
+                 c->state == conn_read)) {
+            struct sockaddr_storage svr_sock_addr;
+            socklen_t svr_addr_len = sizeof(svr_sock_addr);
+            getsockname(c->sfd, (struct sockaddr *)&svr_sock_addr, &svr_addr_len);
+            get_conn_text(c, svr_sock_addr.ss_family, svr_addr, (struct sockaddr *)&svr_sock_addr);
+        }
+    }
+}
+
+static void process_stats_conns(ADD_STAT add_stats, void *c) {
+    int i;
+    char key_str[STAT_KEY_LEN];
+    char val_str[STAT_VAL_LEN];
+    size_t extras_len = sizeof("unix:") + sizeof("65535");
+    char addr[MAXPATHLEN + extras_len];
+    char svr_addr[MAXPATHLEN + extras_len];
+    int klen = 0, vlen = 0;
+
+    assert(add_stats);
+
+    for (i = 0; i < max_fds; i++) {
+        if (conns[i]) {
+            /* This is safe to do unlocked because conns are never freed; the
+             * worst that'll happen will be a minor inconsistency in the
+             * output -- not worth the complexity of the locking that'd be
+             * required to prevent it.
+             */
+            if (IS_UDP(conns[i]->transport)) {
+                APPEND_NUM_STAT(i, "UDP", "%s", "UDP");
+            }
+            if (conns[i]->state != conn_closed) {
+                conn_to_str(conns[i], addr, svr_addr);
+
+                APPEND_NUM_STAT(i, "addr", "%s", addr);
+                if (conns[i]->state != conn_listening &&
+                    !(IS_UDP(conns[i]->transport) && conns[i]->state == conn_read)) {
+                    APPEND_NUM_STAT(i, "listen_addr", "%s", svr_addr);
+                }
+                APPEND_NUM_STAT(i, "state", "%s",
+                        state_text(conns[i]->state));
+                APPEND_NUM_STAT(i, "secs_since_last_cmd", "%d",
+                        current_time - conns[i]->last_cmd_time);
+            }
+        }
+    }
+}
+#ifdef EXTSTORE
+static void process_extstore_stats(ADD_STAT add_stats, conn *c) {
+    int i;
+    char key_str[STAT_KEY_LEN];
+    char val_str[STAT_VAL_LEN];
+    int klen = 0, vlen = 0;
+    struct extstore_stats st;
+
+    assert(add_stats);
+
+    void *storage = c->thread->storage;
+    extstore_get_stats(storage, &st);
+    st.page_data = calloc(st.page_count, sizeof(struct extstore_page_data));
+    extstore_get_page_data(storage, &st);
+
+    for (i = 0; i < st.page_count; i++) {
+        APPEND_NUM_STAT(i, "version", "%llu",
+                (unsigned long long) st.page_data[i].version);
+        APPEND_NUM_STAT(i, "bytes", "%llu",
+                (unsigned long long) st.page_data[i].bytes_used);
+        APPEND_NUM_STAT(i, "bucket", "%u",
+                st.page_data[i].bucket);
+        APPEND_NUM_STAT(i, "free_bucket", "%u",
+                st.page_data[i].free_bucket);
+    }
+}
+#endif
+static void process_stat(conn *c, token_t *tokens, const size_t ntokens) {
+    const char *subcommand = tokens[SUBCOMMAND_TOKEN].value;
+    assert(c != NULL);
+
+    if (ntokens < 2) {
+        out_string(c, "CLIENT_ERROR bad command line");
+        return;
+    }
+
+    if (ntokens == 2) {
+        server_stats(&append_stats, c);
+        (void)get_stats(NULL, 0, &append_stats, c);
+    } else if (strcmp(subcommand, "reset") == 0) {
+        stats_reset();
+        out_string(c, "RESET");
+        return;
+    } else if (strcmp(subcommand, "detail") == 0) {
+        /* NOTE: how to tackle detail with binary? */
+        if (ntokens < 4)
+            process_stats_detail(c, "");  /* outputs the error message */
+        else
+            process_stats_detail(c, tokens[2].value);
+        /* Output already generated */
+        return;
+    } else if (strcmp(subcommand, "settings") == 0) {
+        process_stat_settings(&append_stats, c);
+    } else if (strcmp(subcommand, "cachedump") == 0) {
+        char *buf;
+        unsigned int bytes, id, limit = 0;
+
+        if (!settings.dump_enabled) {
+            out_string(c, "CLIENT_ERROR stats cachedump not allowed");
+            return;
+        }
+
+        if (ntokens < 5) {
+            out_string(c, "CLIENT_ERROR bad command line");
+            return;
+        }
+
+        if (!safe_strtoul(tokens[2].value, &id) ||
+            !safe_strtoul(tokens[3].value, &limit)) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+            return;
+        }
+
+        if (id >= MAX_NUMBER_OF_SLAB_CLASSES) {
+            out_string(c, "CLIENT_ERROR Illegal slab id");
+            return;
+        }
+
+        buf = item_cachedump(id, limit, &bytes);
+        write_and_free(c, buf, bytes);
+        return;
+    } else if (strcmp(subcommand, "conns") == 0) {
+        process_stats_conns(&append_stats, c);
+#ifdef EXTSTORE
+    } else if (strcmp(subcommand, "extstore") == 0) {
+        process_extstore_stats(&append_stats, c);
+#endif
+    } else {
+        /* getting here means that the subcommand is either engine specific or
+           is invalid. query the engine and see. */
+        if (get_stats(subcommand, strlen(subcommand), &append_stats, c)) {
+            if (c->stats.buffer == NULL) {
+                out_of_memory(c, "SERVER_ERROR out of memory writing stats");
+            } else {
+                write_and_free(c, c->stats.buffer, c->stats.offset);
+                c->stats.buffer = NULL;
+            }
+        } else {
+            out_string(c, "ERROR");
+        }
+        return;
+    }
+
+    /* append terminator and start the transfer */
+    append_stats(NULL, 0, NULL, 0, c);
+
+    if (c->stats.buffer == NULL) {
+        out_of_memory(c, "SERVER_ERROR out of memory writing stats");
+    } else {
+        write_and_free(c, c->stats.buffer, c->stats.offset);
+        c->stats.buffer = NULL;
+    }
+}
+
+/* client flags == 0 means use no storage for client flags */
+static inline int make_ascii_get_suffix(char *suffix, item *it, bool return_cas, int nbytes) {
+    char *p = suffix;
+    *p = ' ';
+    p++;
+    if (FLAGS_SIZE(it) == 0) {
+        *p = '0';
+        p++;
+    } else {
+        p = itoa_u32(*((uint32_t *) ITEM_suffix(it)), p);
+    }
+    *p = ' ';
+    p = itoa_u32(nbytes-2, p+1);
+
+    if (return_cas) {
+        *p = ' ';
+        p = itoa_u64(ITEM_get_cas(it), p+1);
+    }
+
+    *p = '\r';
+    *(p+1) = '\n';
+    *(p+2) = '\0';
+    return (p - suffix) + 2;
+}
+
+#define IT_REFCOUNT_LIMIT 60000
+static inline item* limited_get(char *key, size_t nkey, conn *c, uint32_t exptime, bool should_touch, bool do_update, bool *overflow) {
+    item *it;
+    if (should_touch) {
+        it = item_touch(key, nkey, exptime, c);
+    } else {
+        it = item_get(key, nkey, c, do_update);
+    }
+    if (it && it->refcount > IT_REFCOUNT_LIMIT) {
+        item_remove(it);
+        it = NULL;
+        *overflow = true;
+    } else {
+        *overflow = false;
+    }
+    return it;
+}
+
+// Semantics are different than limited_get; since the item is returned
+// locked, caller can directly change what it needs.
+// though it might eventually be a better interface to sink it all into
+// items.c.
+static inline item* limited_get_locked(char *key, size_t nkey, conn *c, bool do_update, uint32_t *hv, bool *overflow) {
+    item *it;
+    it = item_get_locked(key, nkey, c, do_update, hv);
+    if (it && it->refcount > IT_REFCOUNT_LIMIT) {
+        do_item_remove(it);
+        it = NULL;
+        item_unlock(*hv);
+        *overflow = true;
+    } else {
+        *overflow = false;
+    }
+    return it;
+}
+
+#ifdef EXTSTORE
+// FIXME: This runs in the IO thread. to get better IO performance this should
+// simply mark the io wrapper with the return value and decrement wrapleft, if
+// zero redispatching. Still a bit of work being done in the side thread but
+// minimized at least.
+static void _get_extstore_cb(void *e, obj_io *io, int ret) {
+    // FIXME: assumes success
+    io_wrap *wrap = (io_wrap *)io->data;
+    mc_resp *resp = wrap->resp;
+    conn *c = wrap->c;
+    assert(wrap->active == true);
+    item *read_it = (item *)io->buf;
+    bool miss = false;
+
+    // TODO: How to do counters for hit/misses?
+    if (ret < 1) {
+        miss = true;
+    } else {
+        uint32_t crc2;
+        uint32_t crc = (uint32_t) read_it->exptime;
+        int x;
+        // item is chunked, crc the iov's
+        if (io->iov != NULL) {
+            // first iov is the header, which we don't use beyond crc
+            crc2 = crc32c(0, (char *)io->iov[0].iov_base+STORE_OFFSET, io->iov[0].iov_len-STORE_OFFSET);
+            // make sure it's not sent. hack :(
+            io->iov[0].iov_len = 0;
+            for (x = 1; x < io->iovcnt; x++) {
+                crc2 = crc32c(crc2, (char *)io->iov[x].iov_base, io->iov[x].iov_len);
+            }
+        } else {
+            crc2 = crc32c(0, (char *)read_it+STORE_OFFSET, io->len-STORE_OFFSET);
+        }
+
+        if (crc != crc2) {
+            miss = true;
+            wrap->badcrc = true;
+        }
+    }
+
+    if (miss) {
+        if (wrap->noreply) {
+            // In all GET cases, noreply means we send nothing back.
+            resp->skip = true;
+        } else {
+            // TODO: This should be movable to the worker thread.
+            // Convert the binprot response into a miss response.
+            // The header requires knowing a bunch of stateful crap, so rather
+            // than simply writing out a "new" miss response we mangle what's
+            // already there.
+            if (c->protocol == binary_prot) {
+                protocol_binary_response_header *header =
+                    (protocol_binary_response_header *)resp->wbuf;
+
+                // cut the extra nbytes off of the body_len
+                uint32_t body_len = ntohl(header->response.bodylen);
+                uint8_t hdr_len = header->response.extlen;
+                body_len -= resp->iov[wrap->iovec_data].iov_len + hdr_len;
+                resp->tosend -= resp->iov[wrap->iovec_data].iov_len + hdr_len;
+                header->response.extlen = 0;
+                header->response.status = (uint16_t)htons(PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+                header->response.bodylen = htonl(body_len);
+
+                // truncate the data response.
+                resp->iov[wrap->iovec_data].iov_len = 0;
+                // wipe the extlen iov... wish it was just a flat buffer.
+                resp->iov[wrap->iovec_data-1].iov_len = 0;
+                resp->chunked_data_iov = 0;
+            } else {
+                int i;
+                // Meta commands have EN status lines for miss, rather than
+                // END as a trailer as per normal ascii.
+                if (resp->iov[0].iov_len >= 3
+                        && memcmp(resp->iov[0].iov_base, "VA ", 3) == 0) {
+                    // TODO: These miss translators should use specific callback
+                    // functions attached to the io wrap. This is weird :(
+                    resp->iovcnt = 1;
+                    resp->iov[0].iov_len = 4;
+                    resp->iov[0].iov_base = "EN\r\n";
+                    resp->tosend = 4;
+                } else {
+                    // Wipe the iovecs up through our data injection.
+                    // Allows trailers to be returned (END)
+                    for (i = 0; i <= wrap->iovec_data; i++) {
+                        resp->tosend -= resp->iov[i].iov_len;
+                        resp->iov[i].iov_len = 0;
+                        resp->iov[i].iov_base = NULL;
+                    }
+                }
+                resp->chunked_total = 0;
+                resp->chunked_data_iov = 0;
+            }
+        }
+        wrap->miss = true;
+    } else {
+        assert(read_it->slabs_clsid != 0);
+        // TODO: should always use it instead of ITEM_data to kill more
+        // chunked special casing.
+        if ((read_it->it_flags & ITEM_CHUNKED) == 0) {
+            resp->iov[wrap->iovec_data].iov_base = ITEM_data(read_it);
+        }
+        wrap->miss = false;
+    }
+
+    c->io_wrapleft--;
+    wrap->active = false;
+    //assert(c->io_wrapleft >= 0);
+
+    // All IO's have returned, lets re-attach this connection to our original
+    // thread.
+    if (c->io_wrapleft == 0) {
+        assert(c->io_queued == true);
+        c->io_queued = false;
+        redispatch_conn(c);
+    }
+}
+
+static inline int _get_extstore(conn *c, item *it, mc_resp *resp) {
+#ifdef NEED_ALIGN
+    item_hdr hdr;
+    memcpy(&hdr, ITEM_data(it), sizeof(hdr));
+#else
+    item_hdr *hdr = (item_hdr *)ITEM_data(it);
+#endif
+    size_t ntotal = ITEM_ntotal(it);
+    unsigned int clsid = slabs_clsid(ntotal);
+    item *new_it;
+    bool chunked = false;
+    if (ntotal > settings.slab_chunk_size_max) {
+        // Pull a chunked item header.
+        uint32_t flags;
+        FLAGS_CONV(it, flags);
+        new_it = item_alloc(ITEM_key(it), it->nkey, flags, it->exptime, it->nbytes);
+        assert(new_it == NULL || (new_it->it_flags & ITEM_CHUNKED));
+        chunked = true;
+    } else {
+        new_it = do_item_alloc_pull(ntotal, clsid);
+    }
+    if (new_it == NULL)
+        return -1;
+    assert(!c->io_queued); // FIXME: debugging.
+    // so we can free the chunk on a miss
+    new_it->slabs_clsid = clsid;
+
+    io_wrap *io = do_cache_alloc(c->thread->io_cache);
+    io->active = true;
+    io->miss = false;
+    io->badcrc = false;
+    io->noreply = c->noreply;
+    // io_wrap owns the reference for this object now.
+    io->hdr_it = it;
+    io->resp = resp;
+    io->io.iov = NULL;
+
+    // FIXME: error handling.
+    if (chunked) {
+        unsigned int ciovcnt = 0;
+        size_t remain = new_it->nbytes;
+        item_chunk *chunk = (item_chunk *) ITEM_schunk(new_it);
+        // TODO: This might make sense as a _global_ cache vs a per-thread.
+        // but we still can't load objects requiring > IOV_MAX iovs.
+        // In the meantime, these objects are rare/slow enough that
+        // malloc/freeing a statically sized object won't cause us much pain.
+        io->io.iov = malloc(sizeof(struct iovec) * IOV_MAX);
+        if (io->io.iov == NULL) {
+            item_remove(new_it);
+            do_cache_free(c->thread->io_cache, io);
+            return -1;
+        }
+
+        // fill the header so we can get the full data + crc back.
+        io->io.iov[0].iov_base = new_it;
+        io->io.iov[0].iov_len = ITEM_ntotal(new_it) - new_it->nbytes;
+        ciovcnt++;
+
+        while (remain > 0) {
+            chunk = do_item_alloc_chunk(chunk, remain);
+            // FIXME: _pure evil_, silently erroring if item is too large.
+            if (chunk == NULL || ciovcnt > IOV_MAX-1) {
+                item_remove(new_it);
+                free(io->io.iov);
+                // TODO: wrapper function for freeing up an io wrap?
+                io->io.iov = NULL;
+                do_cache_free(c->thread->io_cache, io);
+                return -1;
+            }
+            io->io.iov[ciovcnt].iov_base = chunk->data;
+            io->io.iov[ciovcnt].iov_len = (remain < chunk->size) ? remain : chunk->size;
+            chunk->used = (remain < chunk->size) ? remain : chunk->size;
+            remain -= chunk->size;
+            ciovcnt++;
+        }
+
+        io->io.iovcnt = ciovcnt;
+    }
+
+    // Chunked or non chunked we reserve a response iov here.
+    io->iovec_data = resp->iovcnt;
+    int iovtotal = (c->protocol == binary_prot) ? it->nbytes - 2 : it->nbytes;
+    if (chunked) {
+        resp_add_chunked_iov(resp, new_it, iovtotal);
+    } else {
+        resp_add_iov(resp, "", iovtotal);
+    }
+
+    io->io.buf = (void *)new_it;
+    io->c = c;
+
+    // We need to stack the sub-struct IO's together as well.
+    if (c->io_wraplist) {
+        io->io.next = &c->io_wraplist->io;
+    } else {
+        io->io.next = NULL;
+    }
+
+    // IO queue for this connection.
+    io->next = c->io_wraplist;
+    c->io_wraplist = io;
+    assert(c->io_wrapleft >= 0);
+    c->io_wrapleft++;
+    // reference ourselves for the callback.
+    io->io.data = (void *)io;
+
+    // Now, fill in io->io based on what was in our header.
+#ifdef NEED_ALIGN
+    io->io.page_version = hdr.page_version;
+    io->io.page_id = hdr.page_id;
+    io->io.offset = hdr.offset;
+#else
+    io->io.page_version = hdr->page_version;
+    io->io.page_id = hdr->page_id;
+    io->io.offset = hdr->offset;
+#endif
+    io->io.len = ntotal;
+    io->io.mode = OBJ_IO_READ;
+    io->io.cb = _get_extstore_cb;
+
+    //fprintf(stderr, "EXTSTORE: IO stacked %u\n", io->iovec_data);
+    // FIXME: This stat needs to move to reflect # of flash hits vs misses
+    // for now it's a good gauge on how often we request out to flash at
+    // least.
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.get_extstore++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    return 0;
+}
+#endif
+
+item * conn_fetch_resp_item(conn * c){
+    if(c && c->resp){
+        return c->resp->item;
+    }
+    return NULL;
+}
+
+item * conn_fetch_item(conn * c){
+    if(c){
+        return c->item;
+    }
+    return NULL;
+}
+
+
+/* ntokens is overwritten here... shrug.. */
+static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens, bool return_cas, bool should_touch) {
+    char *key;
+    size_t nkey;
+    item *it;
+    token_t *key_token = &tokens[KEY_TOKEN];
+    int32_t exptime_int = 0;
+    rel_time_t exptime = 0;
+    bool fail_length = false;
+    assert(c != NULL);
+    mc_resp *resp = c->resp;
+
+    if (should_touch) {
+        // For get and touch commands, use first token as exptime
+        if (!safe_strtol(tokens[1].value, &exptime_int)) {
+            out_string(c, "CLIENT_ERROR invalid exptime argument");
+            return;
+        }
+        key_token++;
+        exptime = realtime(EXPTIME_TO_POSITIVE_TIME(exptime_int));
+    }
+
+    do {
+        while(key_token->length != 0) {
+            bool overflow; // not used here.
+            key = key_token->value;
+            nkey = key_token->length;
+
+            if (nkey > KEY_MAX_LENGTH) {
+                fail_length = true;
+                goto stop;
+            }
+
+            it = limited_get(key, nkey, c, exptime, should_touch, DO_UPDATE, &overflow);
+            if (settings.detail_enabled) {
+                stats_prefix_record_get(key, nkey, NULL != it);
+            }
+            if (it) {
+                /*
+                 * Construct the response. Each hit adds three elements to the
+                 * outgoing data list:
+                 *   "VALUE "
+                 *   key
+                 *   " " + flags + " " + data length + "\r\n" + data (with \r\n)
+                 */
+
+                {
+                  MEMCACHED_COMMAND_GET(c->sfd, ITEM_key(it), it->nkey,
+                                        it->nbytes, ITEM_get_cas(it));
+                  int nbytes = it->nbytes;;
+                  nbytes = it->nbytes;
+                  char *p = resp->wbuf;
+                  memcpy(p, "VALUE ", 6);
+                  p += 6;
+                  memcpy(p, ITEM_key(it), it->nkey);
+                  p += it->nkey;
+                  p += make_ascii_get_suffix(p, it, return_cas, nbytes);
+                  resp_add_iov(resp, resp->wbuf, p - resp->wbuf);
+
+#ifdef EXTSTORE
+                  if (it->it_flags & ITEM_HDR) {
+                      if (_get_extstore(c, it, resp) != 0) {
+                          pthread_mutex_lock(&c->thread->stats.mutex);
+                          c->thread->stats.get_oom_extstore++;
+                          pthread_mutex_unlock(&c->thread->stats.mutex);
+
+                          item_remove(it);
+                          goto stop;
+                      }
+                  } else if ((it->it_flags & ITEM_CHUNKED) == 0) {
+                      resp_add_iov(resp, ITEM_data(it), it->nbytes);
+                  } else {
+                      resp_add_chunked_iov(resp, it, it->nbytes);
+                  }
+#else
+                  if ((it->it_flags & ITEM_CHUNKED) == 0) {
+                      resp_add_iov(resp, ITEM_data(it), it->nbytes);
+                  } else {
+                      resp_add_chunked_iov(resp, it, it->nbytes);
+                  }
+#endif
+                }
+
+                if (settings.verbose > 1) {
+                    int ii;
+                    fprintf(stderr, ">%d sending key ", c->sfd);
+                    for (ii = 0; ii < it->nkey; ++ii) {
+                        fprintf(stderr, "%c", key[ii]);
+                    }
+                    fprintf(stderr, "\n");
+                }
+
+                /* item_get() has incremented it->refcount for us */
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                if (should_touch) {
+                    c->thread->stats.touch_cmds++;
+                    c->thread->stats.slab_stats[ITEM_clsid(it)].touch_hits++;
+                } else {
+                    c->thread->stats.lru_hits[it->slabs_clsid]++;
+                    c->thread->stats.get_cmds++;
+                }
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+#ifdef EXTSTORE
+                /* If ITEM_HDR, an io_wrap owns the reference. */
+                if ((it->it_flags & ITEM_HDR) == 0) {
+                    resp->item = it;
+                }
+#else
+                resp->item = it;
+#endif
+            } else {
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                if (should_touch) {
+                    c->thread->stats.touch_cmds++;
+                    c->thread->stats.touch_misses++;
+                } else {
+                    c->thread->stats.get_misses++;
+                    c->thread->stats.get_cmds++;
+                }
+                MEMCACHED_COMMAND_GET(c->sfd, key, nkey, -1, 0);
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+            }
+
+            key_token++;
+            if (key_token->length != 0) {
+                if (!resp_start(c)) {
+                    goto stop;
+                }
+                resp = c->resp;
+            }
+        }
+
+        /*
+         * If the command string hasn't been fully processed, get the next set
+         * of tokens.
+         */
+        if (key_token->value != NULL) {
+            ntokens = tokenize_command(key_token->value, tokens, MAX_TOKENS);
+            key_token = tokens;
+            if (!resp_start(c)) {
+                goto stop;
+            }
+            resp = c->resp;
+        }
+    } while(key_token->value != NULL);
+stop:
+
+    if (settings.verbose > 1)
+        fprintf(stderr, ">%d END\n", c->sfd);
+
+    /*
+        If the loop was terminated because of out-of-memory, it is not
+        reliable to add END\r\n to the buffer, because it might not end
+        in \r\n. So we send SERVER_ERROR instead.
+    */
+    if (key_token->value != NULL) {
+        // Kill any stacked responses we had.
+        conn_release_items(c);
+        // Start a new response object for the error message.
+        if (!resp_start(c)) {
+            // severe out of memory error.
+            conn_set_state(c, conn_closing);
+            return;
+        }
+        if (fail_length) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+        } else {
+            out_of_memory(c, "SERVER_ERROR out of memory writing get response");
+        }
+    } else {
+        // Tag the end token onto the most recent response object.
+        resp_add_iov(resp, "END\r\n", 5);
+        conn_set_state(c, conn_mwrite);
+    }
+}
+
+// slow snprintf for debugging purposes.
+static void process_meta_command(conn *c, token_t *tokens, const size_t ntokens) {
+    assert(c != NULL);
+
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    char *key = tokens[KEY_TOKEN].value;
+    size_t nkey = tokens[KEY_TOKEN].length;
+
+    bool overflow; // not used here.
+    item *it = limited_get(key, nkey, c, 0, false, DONT_UPDATE, &overflow);
+    if (it) {
+        mc_resp *resp = c->resp;
+        size_t total = 0;
+        size_t ret;
+        // similar to out_string().
+        memcpy(resp->wbuf, "ME ", 3);
+        total += 3;
+        memcpy(resp->wbuf + total, ITEM_key(it), it->nkey);
+        total += it->nkey;
+        resp->wbuf[total] = ' ';
+        total++;
+
+        ret = snprintf(resp->wbuf + total, WRITE_BUFFER_SIZE - (it->nkey + 12),
+                "exp=%d la=%llu cas=%llu fetch=%s cls=%u size=%lu\r\n",
+                (it->exptime == 0) ? -1 : (current_time - it->exptime),
+                (unsigned long long)(current_time - it->time),
+                (unsigned long long)ITEM_get_cas(it),
+                (it->it_flags & ITEM_FETCHED) ? "yes" : "no",
+                ITEM_clsid(it),
+                (unsigned long) ITEM_ntotal(it));
+
+        item_remove(it);
+        resp->wbytes = total + ret;
+        resp_add_iov(resp, resp->wbuf, resp->wbytes);
+        conn_set_state(c, conn_new_cmd);
+    } else {
+        out_string(c, "EN");
+    }
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.meta_cmds++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+}
+
+#define MFLAG_MAX_OPT_LENGTH 20
+#define MFLAG_MAX_OPAQUE_LENGTH 32
+
+struct _meta_flags {
+    unsigned int has_error :1; // flipped if we found an error during parsing.
+    unsigned int no_update :1;
+    unsigned int locked :1;
+    unsigned int vivify :1;
+    unsigned int la :1;
+    unsigned int hit :1;
+    unsigned int value :1;
+    unsigned int set_stale :1;
+    unsigned int no_reply :1;
+    unsigned int has_cas :1;
+    unsigned int new_ttl :1;
+    char mode; // single character mode switch, common to ms/ma
+    rel_time_t exptime;
+    rel_time_t autoviv_exptime;
+    rel_time_t recache_time;
+    int32_t value_len;
+    uint32_t client_flags;
+    uint64_t req_cas_id;
+    uint64_t delta; // ma
+    uint64_t initial; // ma
+};
+
+static int _meta_flag_preparse(token_t *tokens, const size_t ntokens,
+        struct _meta_flags *of, char **errstr) {
+    unsigned int i;
+    int32_t tmp_int;
+    uint8_t seen[127] = {0};
+    // Start just past the key token. Look at first character of each token.
+    for (i = KEY_TOKEN+1; i < ntokens-1; i++) {
+        uint8_t o = (uint8_t)tokens[i].value[0];
+        // zero out repeat flags so we don't over-parse for return data.
+        if (o >= 127 || seen[o] != 0) {
+            *errstr = "CLIENT_ERROR duplicate flag";
+            return -1;
+        }
+        seen[o] = 1;
+        switch (o) {
+            /* Negative exptimes can underflow and end up immortal. realtime() will
+               immediately expire values that are greater than REALTIME_MAXDELTA, but less
+               than process_started, so lets aim for that. */
+            case 'N':
+                of->locked = 1;
+                of->vivify = 1;
+                if (!safe_strtol(tokens[i].value+1, &tmp_int)) {
+                    *errstr = "CLIENT_ERROR bad token in command line format";
+                    of->has_error = 1;
+                } else {
+                    of->autoviv_exptime = realtime(EXPTIME_TO_POSITIVE_TIME(tmp_int));
+                }
+                break;
+            case 'T':
+                of->locked = 1;
+                if (!safe_strtol(tokens[i].value+1, &tmp_int)) {
+                    *errstr = "CLIENT_ERROR bad token in command line format";
+                    of->has_error = 1;
+                } else {
+                    of->exptime = realtime(EXPTIME_TO_POSITIVE_TIME(tmp_int));
+                    of->new_ttl = true;
+                }
+                break;
+            case 'R':
+                of->locked = 1;
+                if (!safe_strtol(tokens[i].value+1, &tmp_int)) {
+                    *errstr = "CLIENT_ERROR bad token in command line format";
+                    of->has_error = 1;
+                } else {
+                    of->recache_time = realtime(EXPTIME_TO_POSITIVE_TIME(tmp_int));
+                }
+                break;
+            case 'l':
+                of->la = 1;
+                of->locked = 1; // need locked to delay LRU bump
+                break;
+            case 'O':
+                break;
+            case 'k': // known but no special handling
+            case 's':
+            case 't':
+            case 'c':
+            case 'f':
+                break;
+            case 'v':
+                of->value = 1;
+                break;
+            case 'h':
+                of->locked = 1; // need locked to delay LRU bump
+                break;
+            case 'u':
+                of->no_update = 1;
+                break;
+            case 'q':
+                of->no_reply = 1;
+                break;
+            // mset-related.
+            case 'F':
+                if (!safe_strtoul(tokens[i].value+1, &of->client_flags)) {
+                    of->has_error = true;
+                }
+                break;
+            case 'S':
+                if (!safe_strtol(tokens[i].value+1, &tmp_int)) {
+                    of->has_error = true;
+                } else {
+                    // Size is adjusted for underflow or overflow once the
+                    // \r\n terminator is added.
+                    if (tmp_int < 0 || tmp_int > (INT_MAX - 2)) {
+                        *errstr = "CLIENT_ERROR invalid length";
+                        of->has_error = true;
+                    } else {
+                        of->value_len = tmp_int + 2; // \r\n
+                    }
+                }
+                break;
+            case 'C': // mset, mdelete, marithmetic
+                if (!safe_strtoull(tokens[i].value+1, &of->req_cas_id)) {
+                    *errstr = "CLIENT_ERROR bad token in command line format";
+                    of->has_error = true;
+                } else {
+                    of->has_cas = true;
+                }
+                break;
+            case 'M': // mset and marithmetic mode switch
+                if (tokens[i].length != 2) {
+                    *errstr = "CLIENT_ERROR incorrect length for M token";
+                    of->has_error = 1;
+                } else {
+                    of->mode = tokens[i].value[1];
+                }
+                break;
+            case 'J': // marithmetic initial value
+                if (!safe_strtoull(tokens[i].value+1, &of->initial)) {
+                    *errstr = "CLIENT_ERROR invalid numeric initial value";
+                    of->has_error = 1;
+                }
+                break;
+            case 'D': // marithmetic delta value
+                if (!safe_strtoull(tokens[i].value+1, &of->delta)) {
+                    *errstr = "CLIENT_ERROR invalid numeric delta value";
+                    of->has_error = 1;
+                }
+                break;
+            case 'I':
+                of->set_stale = 1;
+                break;
+            default: // unknown flag, bail.
+                *errstr = "CLIENT_ERROR invalid flag";
+                return -1;
+        }
+    }
+
+    return of->has_error ? -1 : 0;
+}
+
+#define META_SPACE(p) { \
+    *p = ' '; \
+    p++; \
+}
+
+#define META_CHAR(p, c) { \
+    *p = ' '; \
+    *(p+1) = c; \
+    p += 2; \
+}
+
+static void process_mget_command(conn *c, token_t *tokens, const size_t ntokens) {
+    char *key;
+    size_t nkey;
+    item *it;
+    unsigned int i = 0;
+    struct _meta_flags of = {0}; // option bitflags.
+    uint32_t hv; // cached hash value for unlocking an item.
+    bool failed = false;
+    bool item_created = false;
+    bool won_token = false;
+    bool ttl_set = false;
+    char *errstr = "CLIENT_ERROR bad command line format";
+    mc_resp *resp = c->resp;
+    char *p = resp->wbuf;
+
+    assert(c != NULL);
+    WANT_TOKENS_MIN(ntokens, 3);
+
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_errstring(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    // NOTE: final token has length == 0.
+    // KEY_TOKEN == 1. 0 is command.
+
+    if (ntokens == 3) {
+        // TODO: any way to fix this?
+        out_errstring(c, "CLIENT_ERROR bad command line format");
+        return;
+    } else if (ntokens > MFLAG_MAX_OPT_LENGTH) {
+        // TODO: ensure the command tokenizer gives us at least this many
+        out_errstring(c, "CLIENT_ERROR options flags are too long");
+        return;
+    }
+
+    // scrubs duplicated options and sets flags for how to load the item.
+    if (_meta_flag_preparse(tokens, ntokens, &of, &errstr) != 0) {
+        out_errstring(c, errstr);
+        return;
+    }
+    c->noreply = of.no_reply;
+
+    // TODO: need to indicate if the item was overflowed or not?
+    // I think we do, since an overflow shouldn't trigger an alloc/replace.
+    bool overflow = false;
+    if (!of.locked) {
+        it = limited_get(key, nkey, c, 0, false, !of.no_update, &overflow);
+    } else {
+        // If we had to lock the item, we're doing our own bump later.
+        it = limited_get_locked(key, nkey, c, DONT_UPDATE, &hv, &overflow);
+    }
+
+    // Since we're a new protocol, we can actually inform users that refcount
+    // overflow is happening by straight up throwing an error.
+    // We definitely don't want to re-autovivify by accident.
+    if (overflow) {
+        assert(it == NULL);
+        out_errstring(c, "SERVER_ERROR refcount overflow during fetch");
+        return;
+    }
+
+    if (it == NULL && of.vivify) {
+        // Fill in the exptime during parsing later.
+        it = item_alloc(key, nkey, 0, realtime(0), 2);
+        // We don't actually need any of do_store_item's logic:
+        // - already fetched and missed an existing item.
+        // - lock is still held.
+        // - not append/prepend/replace
+        // - not testing CAS
+        if (it != NULL) {
+            // I look forward to the day I get rid of this :)
+            memcpy(ITEM_data(it), "\r\n", 2);
+            // NOTE: This initializes the CAS value.
+            do_item_link(it, hv);
+            item_created = true;
+        }
+    }
+
+    // don't have to check result of add_iov() since the iov size defaults are
+    // enough.
+    if (it) {
+        if (of.value) {
+            memcpy(p, "VA ", 3);
+            p = itoa_u32(it->nbytes-2, p+3);
+        } else {
+            memcpy(p, "OK", 2);
+            p += 2;
+        }
+
+        for (i = KEY_TOKEN+1; i < ntokens-1; i++) {
+            switch (tokens[i].value[0]) {
+                case 'T':
+                    ttl_set = true;
+                    it->exptime = of.exptime;
+                    break;
+                case 'N':
+                    if (item_created) {
+                        it->exptime = of.autoviv_exptime;
+                        won_token = true;
+                    }
+                    break;
+                case 'R':
+                    // If we haven't autovivified and supplied token is less
+                    // than current TTL, mark a win.
+                    if ((it->it_flags & ITEM_TOKEN_SENT) == 0
+                            && !item_created
+                            && it->exptime != 0
+                            && it->exptime < of.recache_time) {
+                        won_token = true;
+                    }
+                    break;
+                case 's':
+                    META_CHAR(p, 's');
+                    p = itoa_u32(it->nbytes-2, p);
+                    break;
+                case 't':
+                    // TTL remaining as of this request.
+                    // needs to be relative because server clocks may not be in sync.
+                    META_CHAR(p, 't');
+                    if (it->exptime == 0) {
+                        *p = '-';
+                        *(p+1) = '1';
+                        p += 2;
+                    } else {
+                        p = itoa_u32(it->exptime - current_time, p);
+                    }
+                    break;
+                case 'c':
+                    META_CHAR(p, 'c');
+                    p = itoa_u64(ITEM_get_cas(it), p);
+                    break;
+                case 'f':
+                    META_CHAR(p, 'f');
+                    if (FLAGS_SIZE(it) == 0) {
+                        *p = '0';
+                        p++;
+                    } else {
+                        p = itoa_u32(*((uint32_t *) ITEM_suffix(it)), p);
+                    }
+                    break;
+                case 'l':
+                    META_CHAR(p, 'l');
+                    p = itoa_u32(current_time - it->time, p);
+                    break;
+                case 'h':
+                    META_CHAR(p, 'h');
+                    if (it->it_flags & ITEM_FETCHED) {
+                        *p = '1';
+                    } else {
+                        *p = '0';
+                    }
+                    p++;
+                    break;
+                case 'O':
+                    if (tokens[i].length > MFLAG_MAX_OPAQUE_LENGTH) {
+                        errstr = "CLIENT_ERROR opaque token too long";
+                        goto error;
+                    }
+                    META_SPACE(p);
+                    memcpy(p, tokens[i].value, tokens[i].length);
+                    p += tokens[i].length;
+                    break;
+                case 'k':
+                    META_CHAR(p, 'k');
+                    memcpy(p, ITEM_key(it), it->nkey);
+                    p += it->nkey;
+                    break;
+            }
+        }
+
+        // Has this item already sent a token?
+        // Important to do this here so we don't send W with Z.
+        // Isn't critical, but easier for client authors to understand.
+        if (it->it_flags & ITEM_TOKEN_SENT) {
+            META_CHAR(p, 'Z');
+        }
+        if (it->it_flags & ITEM_STALE) {
+            META_CHAR(p, 'X');
+            // FIXME: think hard about this. is this a default, or a flag?
+            if ((it->it_flags & ITEM_TOKEN_SENT) == 0) {
+                // If we're stale but no token already sent, now send one.
+                won_token = true;
+            }
+        }
+
+        if (won_token) {
+            // Mark a win into the flag buffer.
+            META_CHAR(p, 'W');
+            it->it_flags |= ITEM_TOKEN_SENT;
+        }
+
+        *p = '\r';
+        *(p+1) = '\n';
+        *(p+2) = '\0';
+        p += 2;
+        // finally, chain in the buffer.
+        resp_add_iov(resp, resp->wbuf, p - resp->wbuf);
+
+        if (of.value) {
+#ifdef EXTSTORE
+            if (it->it_flags & ITEM_HDR) {
+                if (_get_extstore(c, it, resp) != 0) {
+                    pthread_mutex_lock(&c->thread->stats.mutex);
+                    c->thread->stats.get_oom_extstore++;
+                    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+                    failed = true;
+                }
+            } else if ((it->it_flags & ITEM_CHUNKED) == 0) {
+                resp_add_iov(resp, ITEM_data(it), it->nbytes);
+            } else {
+                resp_add_chunked_iov(resp, it, it->nbytes);
+            }
+#else
+            if ((it->it_flags & ITEM_CHUNKED) == 0) {
+                resp_add_iov(resp, ITEM_data(it), it->nbytes);
+            } else {
+                resp_add_chunked_iov(resp, it, it->nbytes);
+            }
+#endif
+        }
+
+        // need to hold the ref at least because of the key above.
+#ifdef EXTSTORE
+        if (!failed) {
+            if ((it->it_flags & ITEM_HDR) != 0 && of.value) {
+                // Only have extstore clean if header and returning value.
+                resp->item = NULL;
+            } else {
+                resp->item = it;
+            }
+        } else {
+            // Failed to set up extstore fetch.
+            if (of.locked) {
+                do_item_remove(it);
+            } else {
+                item_remove(it);
+            }
+        }
+#else
+        resp->item = it;
+#endif
+    } else {
+        failed = true;
+    }
+
+    if (of.locked) {
+        // Delayed bump so we could get fetched/last access time pre-update.
+        if (!of.no_update && it != NULL) {
+            do_item_bump(c, it, hv);
+        }
+        item_unlock(hv);
+    }
+
+    // we count this command as a normal one if we've gotten this far.
+    // TODO: for autovivify case, miss never happens. Is this okay?
+    if (!failed) {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        if (ttl_set) {
+            c->thread->stats.touch_cmds++;
+            c->thread->stats.slab_stats[ITEM_clsid(it)].touch_hits++;
+        } else {
+            c->thread->stats.lru_hits[it->slabs_clsid]++;
+            c->thread->stats.get_cmds++;
+        }
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        conn_set_state(c, conn_new_cmd);
+    } else {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        if (ttl_set) {
+            c->thread->stats.touch_cmds++;
+            c->thread->stats.touch_misses++;
+        } else {
+            c->thread->stats.get_misses++;
+            c->thread->stats.get_cmds++;
+        }
+        MEMCACHED_COMMAND_GET(c->sfd, key, nkey, -1, 0);
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        // This gets elided in noreply mode.
+        out_string(c, "EN");
+    }
+    return;
+error:
+    if (it) {
+        do_item_remove(it);
+        if (of.locked) {
+            item_unlock(hv);
+        }
+    }
+    out_errstring(c, errstr);
+}
+
+static void process_mset_command(conn *c, token_t *tokens, const size_t ntokens) {
+    char *key;
+    size_t nkey;
+    item *it;
+    int i;
+    short comm = NREAD_SET;
+    struct _meta_flags of = {0}; // option bitflags.
+    char *errstr = "CLIENT_ERROR bad command line format";
+    uint32_t hv;
+    mc_resp *resp = c->resp;
+    char *p = resp->wbuf;
+
+    assert(c != NULL);
+    WANT_TOKENS_MIN(ntokens, 3);
+
+    // TODO: most of this is identical to mget.
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_errstring(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if (ntokens == 3) {
+        out_errstring(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    if (ntokens > MFLAG_MAX_OPT_LENGTH) {
+        out_errstring(c, "CLIENT_ERROR options flags too long");
+        return;
+    }
+
+    // leave space for the status code.
+    p = resp->wbuf + 3;
+
+    // We need to at least try to get the size to properly slurp bad bytes
+    // after an error.
+    if (_meta_flag_preparse(tokens, ntokens, &of, &errstr) != 0) {
+        goto error;
+    }
+
+    // Set noreply after tokens are understood.
+    c->noreply = of.no_reply;
+
+    bool has_error = false;
+    for (i = KEY_TOKEN+1; i < ntokens-1; i++) {
+        switch (tokens[i].value[0]) {
+            // TODO: macro perhaps?
+            case 'O':
+                if (tokens[i].length > MFLAG_MAX_OPAQUE_LENGTH) {
+                    errstr = "CLIENT_ERROR opaque token too long";
+                    has_error = true;
+                    break;
+                }
+                META_SPACE(p);
+                memcpy(p, tokens[i].value, tokens[i].length);
+                p += tokens[i].length;
+                break;
+            case 'k':
+                META_CHAR(p, 'k');
+                memcpy(p, key, nkey);
+                p += nkey;
+                break;
+        }
+    }
+
+    // "mode switch" to alternative commands
+    switch (of.mode) {
+        case 0:
+            break; // no mode supplied.
+        case 'E': // Add...
+            comm = NREAD_ADD;
+            break;
+        case 'A': // Append.
+            comm = NREAD_APPEND;
+            break;
+        case 'P': // Prepend.
+            comm = NREAD_PREPEND;
+            break;
+        case 'R': // Replace.
+            comm = NREAD_REPLACE;
+            break;
+        case 'S': // Set. Default.
+            comm = NREAD_SET;
+            break;
+        default:
+            errstr = "CLIENT_ERROR invalid mode for ms M token";
+            goto error;
+    }
+
+    // The item storage function doesn't exactly map to mset.
+    // If a CAS value is supplied, upgrade default SET mode to CAS mode.
+    // Also allows REPLACE to work, as REPLACE + CAS works the same as CAS.
+    // add-with-cas works the same as add; but could only LRU bump if match..
+    // APPEND/PREPEND allow a simplified CAS check.
+    if (of.has_cas && (comm == NREAD_SET || comm == NREAD_REPLACE)) {
+        comm = NREAD_CAS;
+    }
+
+    // We attempt to process as much as we can in hopes of getting a valid and
+    // adjusted vlen, or else the data swallowed after error will be for 0b.
+    if (has_error)
+        goto error;
+
+    it = item_alloc(key, nkey, of.client_flags, of.exptime, of.value_len);
+
+    if (it == 0) {
+        enum store_item_type status;
+        // TODO: These could be normalized codes (TL and OM). Need to
+        // reorganize the output stuff a bit though.
+        if (! item_size_ok(nkey, of.client_flags, of.value_len)) {
+            errstr = "SERVER_ERROR object too large for cache";
+            status = TOO_LARGE;
+        } else {
+            errstr = "SERVER_ERROR out of memory storing object";
+            status = NO_MEMORY;
+        }
+        // FIXME: LOGGER_LOG specific to mset, include options.
+        LOGGER_LOG(c->thread->l, LOG_MUTATIONS, LOGGER_ITEM_STORE,
+                NULL, status, comm, key, nkey, 0, 0);
+
+        /* Avoid stale data persisting in cache because we failed alloc. */
+        // NOTE: only if SET mode?
+        it = item_get_locked(key, nkey, c, DONT_UPDATE, &hv);
+        if (it) {
+            do_item_unlink(it, hv);
+            STORAGE_delete(c->thread->storage, it);
+            do_item_remove(it);
+        }
+        item_unlock(hv);
+
+        goto error;
+    }
+    ITEM_set_cas(it, of.req_cas_id);
+
+    c->item = it;
+#ifdef NEED_ALIGN
+    if (it->it_flags & ITEM_CHUNKED) {
+        c->ritem = ITEM_schunk(it);
+    } else {
+        c->ritem = ITEM_data(it);
+    }
+#else
+    c->ritem = ITEM_data(it);
+#endif
+    c->rlbytes = it->nbytes;
+    c->cmd = comm;
+    if (of.set_stale && comm == NREAD_CAS) {
+        c->set_stale = true;
+    }
+    resp->wbytes = p - resp->wbuf;
+    memcpy(resp->wbuf + resp->wbytes, "\r\n", 2);
+    resp->wbytes += 2;
+    // We've written the status line into wbuf, use wbytes to finalize later.
+    resp_add_iov(resp, resp->wbuf, resp->wbytes);
+    c->mset_res = true;
+    conn_set_state(c, conn_nread);
+    return;
+error:
+    /* swallow the data line */
+    c->sbytes = of.value_len;
+
+    // Note: no errors possible after the item was successfully allocated.
+    // So we're just looking at dumping error codes and returning.
+    out_errstring(c, errstr);
+    // TODO: pass state in? else switching twice meh.
+    conn_set_state(c, conn_swallow);
+}
+
+static void process_mdelete_command(conn *c, token_t *tokens, const size_t ntokens) {
+    char *key;
+    size_t nkey;
+    uint64_t req_cas_id = 0;
+    item *it = NULL;
+    int i;
+    uint32_t hv;
+    struct _meta_flags of = {0}; // option bitflags.
+    char *errstr = "CLIENT_ERROR bad command line format";
+    mc_resp *resp = c->resp;
+    // reserve 3 bytes for status code
+    char *p = resp->wbuf + 3;
+
+    assert(c != NULL);
+    WANT_TOKENS_MIN(ntokens, 3);
+
+    // TODO: most of this is identical to mget.
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if (ntokens > MFLAG_MAX_OPT_LENGTH) {
+        out_string(c, "CLIENT_ERROR options flags too long");
+        return;
+    }
+
+    // scrubs duplicated options and sets flags for how to load the item.
+    if (_meta_flag_preparse(tokens, ntokens, &of, &errstr) != 0) {
+        out_errstring(c, "CLIENT_ERROR invalid or duplicate flag");
+        return;
+    }
+    c->noreply = of.no_reply;
+
+    assert(c != NULL);
+    for (i = KEY_TOKEN+1; i < ntokens-1; i++) {
+        switch (tokens[i].value[0]) {
+            // TODO: macro perhaps?
+            case 'O':
+                if (tokens[i].length > MFLAG_MAX_OPAQUE_LENGTH) {
+                    errstr = "CLIENT_ERROR opaque token too long";
+                    goto error;
+                }
+                META_SPACE(p);
+                memcpy(p, tokens[i].value, tokens[i].length);
+                p += tokens[i].length;
+                break;
+            case 'k':
+                META_CHAR(p, 'k');
+                memcpy(p, key, nkey);
+                p += nkey;
+                break;
+        }
+    }
+
+    it = item_get_locked(key, nkey, c, DONT_UPDATE, &hv);
+    if (it) {
+        MEMCACHED_COMMAND_DELETE(c->sfd, ITEM_key(it), it->nkey);
+
+        // allow only deleting/marking if a CAS value matches.
+        if (of.has_cas && ITEM_get_cas(it) != req_cas_id) {
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.delete_misses++;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+
+            memcpy(resp->wbuf, "EX ", 3);
+            goto cleanup;
+        }
+
+        // If we're to set this item as stale, we don't actually want to
+        // delete it. We mark the stale bit, bump CAS, and update exptime if
+        // we were supplied a new TTL.
+        if (of.set_stale) {
+            if (of.new_ttl) {
+                it->exptime = of.exptime;
+            }
+            it->it_flags |= ITEM_STALE;
+            // Also need to remove TOKEN_SENT, so next client can win.
+            it->it_flags &= ~ITEM_TOKEN_SENT;
+
+            ITEM_set_cas(it, (settings.use_cas) ? get_cas_id() : 0);
+
+            // Clients can noreply nominal responses.
+            if (c->noreply)
+                resp->skip = true;
+            memcpy(resp->wbuf, "OK ", 3);
+        } else {
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.slab_stats[ITEM_clsid(it)].delete_hits++;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+
+            do_item_unlink(it, hv);
+            STORAGE_delete(c->thread->storage, it);
+            if (c->noreply)
+                resp->skip = true;
+            memcpy(resp->wbuf, "OK ", 3);
+        }
+        goto cleanup;
+    } else {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.delete_misses++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        memcpy(resp->wbuf, "NF ", 3);
+        goto cleanup;
+    }
+cleanup:
+    if (it) {
+        do_item_remove(it);
+    }
+    // Item is always returned locked, even if missing.
+    item_unlock(hv);
+    resp->wbytes = p - resp->wbuf;
+    memcpy(resp->wbuf + resp->wbytes, "\r\n", 2);
+    resp->wbytes += 2;
+    resp_add_iov(resp, resp->wbuf, resp->wbytes);
+    conn_set_state(c, conn_new_cmd);
+    return;
+error:
+    out_errstring(c, errstr);
+}
+
+static void process_marithmetic_command(conn *c, token_t *tokens, const size_t ntokens) {
+    char *key;
+    size_t nkey;
+    int i;
+    struct _meta_flags of = {0}; // option bitflags.
+    char *errstr = "CLIENT_ERROR bad command line format";
+    mc_resp *resp = c->resp;
+    // no reservation (like del/set) since we post-process the status line.
+    char *p = resp->wbuf;
+
+    // If no argument supplied, incr or decr by one.
+    of.delta = 1;
+    of.initial = 0; // redundant, for clarity.
+    bool incr = true; // default mode is to increment.
+    bool locked = false;
+    uint32_t hv = 0;
+    item *it = NULL; // item returned by do_add_delta.
+
+    assert(c != NULL);
+    WANT_TOKENS_MIN(ntokens, 3);
+
+    // TODO: most of this is identical to mget.
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if (ntokens > MFLAG_MAX_OPT_LENGTH) {
+        out_string(c, "CLIENT_ERROR options flags too long");
+        return;
+    }
+
+    // scrubs duplicated options and sets flags for how to load the item.
+    if (_meta_flag_preparse(tokens, ntokens, &of, &errstr) != 0) {
+        out_errstring(c, "CLIENT_ERROR invalid or duplicate flag");
+        return;
+    }
+    c->noreply = of.no_reply;
+
+    assert(c != NULL);
+    // "mode switch" to alternative commands
+    switch (of.mode) {
+        case 0: // no switch supplied.
+            break;
+        case 'I': // Incr (default)
+        case '+':
+            incr = true;
+            break;
+        case 'D': // Decr.
+        case '-':
+            incr = false;
+            break;
+        default:
+            errstr = "CLIENT_ERROR invalid mode for ma M token";
+            goto error;
+            break;
+    }
+
+    // take hash value and manually lock item... hold lock during store phase
+    // on miss and avoid recalculating the hash multiple times.
+    hv = hash(key, nkey);
+    item_lock(hv);
+    locked = true;
+    char tmpbuf[INCR_MAX_STORAGE_LEN];
+
+    // return a referenced item if it exists, so we can modify it here, rather
+    // than adding even more parameters to do_add_delta.
+    bool item_created = false;
+    switch(do_add_delta(c, key, nkey, incr, of.delta, tmpbuf, &of.req_cas_id, hv, &it)) {
+    case OK:
+        if (c->noreply)
+            resp->skip = true;
+        memcpy(resp->wbuf, "OK ", 3);
+        break;
+    case NON_NUMERIC:
+        errstr = "CLIENT_ERROR cannot increment or decrement non-numeric value";
+        goto error;
+        break;
+    case EOM:
+        errstr = "SERVER_ERROR out of memory";
+        goto error;
+        break;
+    case DELTA_ITEM_NOT_FOUND:
+        if (of.vivify) {
+            itoa_u64(of.initial, tmpbuf);
+            int vlen = strlen(tmpbuf);
+
+            it = item_alloc(key, nkey, 0, 0, vlen+2);
+            if (it != NULL) {
+                memcpy(ITEM_data(it), tmpbuf, vlen);
+                memcpy(ITEM_data(it) + vlen, "\r\n", 2);
+                if (do_store_item(it, NREAD_ADD, c, hv)) {
+                    item_created = true;
+                } else {
+                    // Not sure how we can get here if we're holding the lock.
+                    memcpy(resp->wbuf, "NS ", 3);
+                }
+            } else {
+                errstr = "SERVER_ERROR Out of memory allocating new item";
+                goto error;
+            }
+        } else {
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            if (incr) {
+                c->thread->stats.incr_misses++;
+            } else {
+                c->thread->stats.decr_misses++;
+            }
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+            // won't have a valid it here.
+            memcpy(p, "NF ", 3);
+            p += 3;
+        }
+        break;
+    case DELTA_ITEM_CAS_MISMATCH:
+        // also returns without a valid it.
+        memcpy(p, "EX ", 3);
+        p += 3;
+        break;
+    }
+
+    // final loop
+    // allows building the response with information after vivifying from a
+    // miss, or returning a new CAS value after add_delta().
+    if (it) {
+        size_t vlen = strlen(tmpbuf);
+        if (of.value) {
+            memcpy(p, "VA ", 3);
+            p = itoa_u32(vlen, p+3);
+        } else {
+            memcpy(p, "OK", 2);
+            p += 2;
+        }
+
+        for (i = KEY_TOKEN+1; i < ntokens-1; i++) {
+            switch (tokens[i].value[0]) {
+                case 'c':
+                    META_CHAR(p, 'c');
+                    p = itoa_u64(ITEM_get_cas(it), p);
+                    break;
+                case 't':
+                    META_CHAR(p, 't');
+                    if (it->exptime == 0) {
+                        *p = '-';
+                        *(p+1) = '1';
+                        p += 2;
+                    } else {
+                        p = itoa_u32(it->exptime - current_time, p);
+                    }
+                    break;
+                case 'T':
+                    it->exptime = of.exptime;
+                    break;
+                case 'N':
+                    if (item_created) {
+                        it->exptime = of.autoviv_exptime;
+                    }
+                    break;
+                // TODO: macro perhaps?
+                case 'O':
+                    if (tokens[i].length > MFLAG_MAX_OPAQUE_LENGTH) {
+                        errstr = "CLIENT_ERROR opaque token too long";
+                        goto error;
+                    }
+                    META_SPACE(p);
+                    memcpy(p, tokens[i].value, tokens[i].length);
+                    p += tokens[i].length;
+                    break;
+                case 'k':
+                    META_CHAR(p, 'k');
+                    memcpy(p, key, nkey);
+                    p += nkey;
+                    break;
+            }
+        }
+
+        if (of.value) {
+            *p = '\r';
+            *(p+1) = '\n';
+            p += 2;
+            memcpy(p, tmpbuf, vlen);
+            p += vlen;
+        }
+
+        do_item_remove(it);
+    } else {
+        // No item to handle. still need to return opaque/key tokens
+        for (i = KEY_TOKEN+1; i < ntokens-1; i++) {
+            switch (tokens[i].value[0]) {
+                // TODO: macro perhaps?
+                case 'O':
+                    if (tokens[i].length > MFLAG_MAX_OPAQUE_LENGTH) {
+                        errstr = "CLIENT_ERROR opaque token too long";
+                        goto error;
+                    }
+                    META_SPACE(p);
+                    memcpy(p, tokens[i].value, tokens[i].length);
+                    p += tokens[i].length;
+                    break;
+                case 'k':
+                    META_CHAR(p, 'k');
+                    memcpy(p, key, nkey);
+                    p += nkey;
+                    break;
+            }
+        }
+    }
+
+    item_unlock(hv);
+
+    resp->wbytes = p - resp->wbuf;
+    memcpy(resp->wbuf + resp->wbytes, "\r\n", 2);
+    resp->wbytes += 2;
+    resp_add_iov(resp, resp->wbuf, resp->wbytes);
+    conn_set_state(c, conn_new_cmd);
+    return;
+error:
+    if (it != NULL)
+        do_item_remove(it);
+    if (locked)
+        item_unlock(hv);
+    out_errstring(c, errstr);
+}
+
+
+static void process_update_command(conn *c, token_t *tokens, const size_t ntokens, int comm, bool handle_cas) {
+    char *key;
+    size_t nkey;
+    unsigned int flags;
+    int32_t exptime_int = 0;
+    rel_time_t exptime = 0;
+    int vlen;
+    uint64_t req_cas_id=0;
+    item *it;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if (! (safe_strtoul(tokens[2].value, (uint32_t *)&flags)
+           && safe_strtol(tokens[3].value, &exptime_int)
+           && safe_strtol(tokens[4].value, (int32_t *)&vlen))) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    exptime = realtime(EXPTIME_TO_POSITIVE_TIME(exptime_int));
+
+    // does cas value exist?
+    if (handle_cas) {
+        if (!safe_strtoull(tokens[5].value, &req_cas_id)) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+            return;
+        }
+    }
+
+    if (vlen < 0 || vlen > (INT_MAX - 2)) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+    vlen += 2;
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_set(key, nkey);
+    }
+
+    it = item_alloc(key, nkey, flags, exptime, vlen);
+
+    if (it == 0) {
+        enum store_item_type status;
+        if (! item_size_ok(nkey, flags, vlen)) {
+            out_string(c, "SERVER_ERROR object too large for cache");
+            status = TOO_LARGE;
+        } else {
+            out_of_memory(c, "SERVER_ERROR out of memory storing object");
+            status = NO_MEMORY;
+        }
+        LOGGER_LOG(c->thread->l, LOG_MUTATIONS, LOGGER_ITEM_STORE,
+                NULL, status, comm, key, nkey, 0, 0, c->sfd);
+        /* swallow the data line */
+        conn_set_state(c, conn_swallow);
+        c->sbytes = vlen;
+
+        /* Avoid stale data persisting in cache because we failed alloc.
+         * Unacceptable for SET. Anywhere else too? */
+        if (comm == NREAD_SET) {
+            it = item_get(key, nkey, c, DONT_UPDATE);
+            if (it) {
+                item_unlink(it);
+                STORAGE_delete(c->thread->storage, it);
+                item_remove(it);
+            }
+        }
+
+        return;
+    }
+    ITEM_set_cas(it, req_cas_id);
+
+    c->item = it;
+#ifdef NEED_ALIGN
+    if (it->it_flags & ITEM_CHUNKED) {
+        c->ritem = ITEM_schunk(it);
+    } else {
+        c->ritem = ITEM_data(it);
+    }
+#else
+    c->ritem = ITEM_data(it);
+#endif
+    c->rlbytes = it->nbytes;
+    c->cmd = comm;
+    conn_set_state(c, conn_nread);
+}
+
+static void process_touch_command(conn *c, token_t *tokens, const size_t ntokens) {
+    char *key;
+    size_t nkey;
+    int32_t exptime_int = 0;
+    rel_time_t exptime = 0;
+    item *it;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if (!safe_strtol(tokens[2].value, &exptime_int)) {
+        out_string(c, "CLIENT_ERROR invalid exptime argument");
+        return;
+    }
+
+    exptime = realtime(EXPTIME_TO_POSITIVE_TIME(exptime_int));
+    it = item_touch(key, nkey, exptime, c);
+    if (it) {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.touch_cmds++;
+        c->thread->stats.slab_stats[ITEM_clsid(it)].touch_hits++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        out_string(c, "TOUCHED");
+        item_remove(it);
+    } else {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.touch_cmds++;
+        c->thread->stats.touch_misses++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        out_string(c, "NOT_FOUND");
+    }
+}
+
+static void process_arithmetic_command(conn *c, token_t *tokens, const size_t ntokens, const bool incr) {
+    char temp[INCR_MAX_STORAGE_LEN];
+    uint64_t delta;
+    char *key;
+    size_t nkey;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if (!safe_strtoull(tokens[2].value, &delta)) {
+        out_string(c, "CLIENT_ERROR invalid numeric delta argument");
+        return;
+    }
+
+    switch(add_delta(c, key, nkey, incr, delta, temp, NULL)) {
+    case OK:
+        out_string(c, temp);
+        break;
+    case NON_NUMERIC:
+        out_string(c, "CLIENT_ERROR cannot increment or decrement non-numeric value");
+        break;
+    case EOM:
+        out_of_memory(c, "SERVER_ERROR out of memory");
+        break;
+    case DELTA_ITEM_NOT_FOUND:
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        if (incr) {
+            c->thread->stats.incr_misses++;
+        } else {
+            c->thread->stats.decr_misses++;
+        }
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        out_string(c, "NOT_FOUND");
+        break;
+    case DELTA_ITEM_CAS_MISMATCH:
+        break; /* Should never get here */
+    }
+}
+
+/*
+ * adds a delta value to a numeric item.
+ *
+ * c     connection requesting the operation
+ * it    item to adjust
+ * incr  true to increment value, false to decrement
+ * delta amount to adjust value by
+ * buf   buffer for response string
+ *
+ * returns a response string to send back to the client.
+ */
+enum delta_result_type do_add_delta(conn *c, const char *key, const size_t nkey,
+                                    const bool incr, const int64_t delta,
+                                    char *buf, uint64_t *cas,
+                                    const uint32_t hv,
+                                    item **it_ret) {
+    char *ptr;
+    uint64_t value;
+    int res;
+    item *it;
+
+    it = do_item_get(key, nkey, hv, c, DONT_UPDATE);
+    if (!it) {
+        return DELTA_ITEM_NOT_FOUND;
+    }
+
+    /* Can't delta zero byte values. 2-byte are the "\r\n" */
+    /* Also can't delta for chunked items. Too large to be a number */
+#ifdef EXTSTORE
+    if (it->nbytes <= 2 || (it->it_flags & (ITEM_CHUNKED|ITEM_HDR)) != 0) {
+#else
+    if (it->nbytes <= 2 || (it->it_flags & (ITEM_CHUNKED)) != 0) {
+#endif
+        do_item_remove(it);
+        return NON_NUMERIC;
+    }
+
+    if (cas != NULL && *cas != 0 && ITEM_get_cas(it) != *cas) {
+        do_item_remove(it);
+        return DELTA_ITEM_CAS_MISMATCH;
+    }
+
+    ptr = ITEM_data(it);
+
+    if (!safe_strtoull(ptr, &value)) {
+        do_item_remove(it);
+        return NON_NUMERIC;
+    }
+
+    if (incr) {
+        value += delta;
+        MEMCACHED_COMMAND_INCR(c->sfd, ITEM_key(it), it->nkey, value);
+    } else {
+        if(delta > value) {
+            value = 0;
+        } else {
+            value -= delta;
+        }
+        MEMCACHED_COMMAND_DECR(c->sfd, ITEM_key(it), it->nkey, value);
+    }
+
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    if (incr) {
+        c->thread->stats.slab_stats[ITEM_clsid(it)].incr_hits++;
+    } else {
+        c->thread->stats.slab_stats[ITEM_clsid(it)].decr_hits++;
+    }
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    itoa_u64(value, buf);
+    res = strlen(buf);
+    /* refcount == 2 means we are the only ones holding the item, and it is
+     * linked. We hold the item's lock in this function, so refcount cannot
+     * increase. */
+    if (res + 2 <= it->nbytes && it->refcount == 2) { /* replace in-place */
+        /* When changing the value without replacing the item, we
+           need to update the CAS on the existing item. */
+        /* We also need to fiddle it in the sizes tracker in case the tracking
+         * was enabled at runtime, since it relies on the CAS value to know
+         * whether to remove an item or not. */
+        item_stats_sizes_remove(it);
+        ITEM_set_cas(it, (settings.use_cas) ? get_cas_id() : 0);
+        item_stats_sizes_add(it);
+        memcpy(ITEM_data(it), buf, res);
+        memset(ITEM_data(it) + res, ' ', it->nbytes - res - 2);
+        do_item_update(it);
+    } else if (it->refcount > 1) {
+        item *new_it;
+        uint32_t flags;
+        FLAGS_CONV(it, flags);
+        new_it = do_item_alloc(ITEM_key(it), it->nkey, flags, it->exptime, res + 2);
+        if (new_it == 0) {
+            do_item_remove(it);
+            return EOM;
+        }
+        memcpy(ITEM_data(new_it), buf, res);
+        memcpy(ITEM_data(new_it) + res, "\r\n", 2);
+        item_replace(it, new_it, hv);
+        // Overwrite the older item's CAS with our new CAS since we're
+        // returning the CAS of the old item below.
+        ITEM_set_cas(it, (settings.use_cas) ? ITEM_get_cas(new_it) : 0);
+        do_item_remove(new_it);       /* release our reference */
+    } else {
+        /* Should never get here. This means we somehow fetched an unlinked
+         * item. TODO: Add a counter? */
+        if (settings.verbose) {
+            fprintf(stderr, "Tried to do incr/decr on invalid item\n");
+        }
+        if (it->refcount == 1)
+            do_item_remove(it);
+        return DELTA_ITEM_NOT_FOUND;
+    }
+
+    if (cas) {
+        *cas = ITEM_get_cas(it);    /* swap the incoming CAS value */
+    }
+    if (it_ret != NULL) {
+        *it_ret = it;
+    } else {
+        do_item_remove(it);         /* release our reference */
+    }
+    return OK;
+}
+
+static void process_delete_command(conn *c, token_t *tokens, const size_t ntokens) {
+    char *key;
+    size_t nkey;
+    item *it;
+    uint32_t hv;
+
+    assert(c != NULL);
+
+    if (ntokens > 3) {
+        bool hold_is_zero = strcmp(tokens[KEY_TOKEN+1].value, "0") == 0;
+        bool sets_noreply = set_noreply_maybe(c, tokens, ntokens);
+        bool valid = (ntokens == 4 && (hold_is_zero || sets_noreply))
+            || (ntokens == 5 && hold_is_zero && sets_noreply);
+        if (!valid) {
+            out_string(c, "CLIENT_ERROR bad command line format.  "
+                       "Usage: delete <key> [noreply]");
+            return;
+        }
+    }
+
+
+    key = tokens[KEY_TOKEN].value;
+    nkey = tokens[KEY_TOKEN].length;
+
+    if(nkey > KEY_MAX_LENGTH) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_delete(key, nkey);
+    }
+
+    it = item_get_locked(key, nkey, c, DONT_UPDATE, &hv);
+    if (it) {
+        MEMCACHED_COMMAND_DELETE(c->sfd, ITEM_key(it), it->nkey);
+
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.slab_stats[ITEM_clsid(it)].delete_hits++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        do_item_unlink(it, hv);
+        STORAGE_delete(c->thread->storage, it);
+        do_item_remove(it);      /* release our reference */
+        out_string(c, "DELETED");
+    } else {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.delete_misses++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        out_string(c, "NOT_FOUND");
+    }
+    item_unlock(hv);
+}
+
+static void process_verbosity_command(conn *c, token_t *tokens, const size_t ntokens) {
+    unsigned int level;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    level = strtoul(tokens[1].value, NULL, 10);
+    settings.verbose = level > MAX_VERBOSITY_LEVEL ? MAX_VERBOSITY_LEVEL : level;
+    out_string(c, "OK");
+    return;
+}
+
+#ifdef MEMCACHED_DEBUG
+static void process_misbehave_command(conn *c) {
+    int allowed = 0;
+
+    // try opening new TCP socket
+    int i = socket(AF_INET, SOCK_STREAM, 0);
+    if (i != -1) {
+        allowed++;
+        close(i);
+    }
+
+    // try executing new commands
+    i = system("sleep 0");
+    if (i != -1) {
+        allowed++;
+    }
+
+    if (allowed) {
+        out_string(c, "ERROR");
+    } else {
+        out_string(c, "OK");
+    }
+}
+#endif
+
+static void process_slabs_automove_command(conn *c, token_t *tokens, const size_t ntokens) {
+    unsigned int level;
+    double ratio;
+
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    if (strcmp(tokens[2].value, "ratio") == 0) {
+        if (ntokens < 5 || !safe_strtod(tokens[3].value, &ratio)) {
+            out_string(c, "ERROR");
+            return;
+        }
+        settings.slab_automove_ratio = ratio;
+    } else {
+        level = strtoul(tokens[2].value, NULL, 10);
+        if (level == 0) {
+            settings.slab_automove = 0;
+        } else if (level == 1 || level == 2) {
+            settings.slab_automove = level;
+        } else {
+            out_string(c, "ERROR");
+            return;
+        }
+    }
+    out_string(c, "OK");
+    return;
+}
+
+/* TODO: decide on syntax for sampling? */
+static void process_watch_command(conn *c, token_t *tokens, const size_t ntokens) {
+    uint16_t f = 0;
+    int x;
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+    if (!settings.watch_enabled) {
+        out_string(c, "CLIENT_ERROR watch commands not allowed");
+        return;
+    }
+
+    if (resp_has_stack(c)) {
+        out_string(c, "ERROR cannot pipeline other commands before watch");
+        return;
+    }
+
+    if (ntokens > 2) {
+        for (x = COMMAND_TOKEN + 1; x < ntokens - 1; x++) {
+            if ((strcmp(tokens[x].value, "rawcmds") == 0)) {
+                f |= LOG_RAWCMDS;
+            } else if ((strcmp(tokens[x].value, "evictions") == 0)) {
+                f |= LOG_EVICTIONS;
+            } else if ((strcmp(tokens[x].value, "fetchers") == 0)) {
+                f |= LOG_FETCHERS;
+            } else if ((strcmp(tokens[x].value, "mutations") == 0)) {
+                f |= LOG_MUTATIONS;
+            } else if ((strcmp(tokens[x].value, "sysevents") == 0)) {
+                f |= LOG_SYSEVENTS;
+            } else {
+                out_string(c, "ERROR");
+                return;
+            }
+        }
+    } else {
+        f |= LOG_FETCHERS;
+    }
+
+    switch(logger_add_watcher(c, c->sfd, f)) {
+        case LOGGER_ADD_WATCHER_TOO_MANY:
+            out_string(c, "WATCHER_TOO_MANY log watcher limit reached");
+            break;
+        case LOGGER_ADD_WATCHER_FAILED:
+            out_string(c, "WATCHER_FAILED failed to add log watcher");
+            break;
+        case LOGGER_ADD_WATCHER_OK:
+            conn_set_state(c, conn_watch);
+            event_del(&c->event);
+            break;
+    }
+}
+
+static void process_memlimit_command(conn *c, token_t *tokens, const size_t ntokens) {
+    uint32_t memlimit;
+    assert(c != NULL);
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    if (!safe_strtoul(tokens[1].value, &memlimit)) {
+        out_string(c, "ERROR");
+    } else {
+        if (memlimit < 8) {
+            out_string(c, "MEMLIMIT_TOO_SMALL cannot set maxbytes to less than 8m");
+        } else {
+            if (memlimit > 1000000000) {
+                out_string(c, "MEMLIMIT_ADJUST_FAILED input value is megabytes not bytes");
+            } else if (slabs_adjust_mem_limit((size_t) memlimit * 1024 * 1024)) {
+                if (settings.verbose > 0) {
+                    fprintf(stderr, "maxbytes adjusted to %llum\n", (unsigned long long)memlimit);
+                }
+
+                out_string(c, "OK");
+            } else {
+                out_string(c, "MEMLIMIT_ADJUST_FAILED out of bounds or unable to adjust");
+            }
+        }
+    }
+}
+
+static void process_lru_command(conn *c, token_t *tokens, const size_t ntokens) {
+    uint32_t pct_hot;
+    uint32_t pct_warm;
+    double hot_factor;
+    int32_t ttl;
+    double factor;
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    if (strcmp(tokens[1].value, "tune") == 0 && ntokens >= 7) {
+        if (!safe_strtoul(tokens[2].value, &pct_hot) ||
+            !safe_strtoul(tokens[3].value, &pct_warm) ||
+            !safe_strtod(tokens[4].value, &hot_factor) ||
+            !safe_strtod(tokens[5].value, &factor)) {
+            out_string(c, "ERROR");
+        } else {
+            if (pct_hot + pct_warm > 80) {
+                out_string(c, "ERROR hot and warm pcts must not exceed 80");
+            } else if (factor <= 0 || hot_factor <= 0) {
+                out_string(c, "ERROR hot/warm age factors must be greater than 0");
+            } else {
+                settings.hot_lru_pct = pct_hot;
+                settings.warm_lru_pct = pct_warm;
+                settings.hot_max_factor = hot_factor;
+                settings.warm_max_factor = factor;
+                out_string(c, "OK");
+            }
+        }
+    } else if (strcmp(tokens[1].value, "mode") == 0 && ntokens >= 4 &&
+               settings.lru_maintainer_thread) {
+        if (strcmp(tokens[2].value, "flat") == 0) {
+            settings.lru_segmented = false;
+            out_string(c, "OK");
+        } else if (strcmp(tokens[2].value, "segmented") == 0) {
+            settings.lru_segmented = true;
+            out_string(c, "OK");
+        } else {
+            out_string(c, "ERROR");
+        }
+    } else if (strcmp(tokens[1].value, "temp_ttl") == 0 && ntokens >= 4 &&
+               settings.lru_maintainer_thread) {
+        if (!safe_strtol(tokens[2].value, &ttl)) {
+            out_string(c, "ERROR");
+        } else {
+            if (ttl < 0) {
+                settings.temp_lru = false;
+            } else {
+                settings.temp_lru = true;
+                settings.temporary_ttl = ttl;
+            }
+            out_string(c, "OK");
+        }
+    } else {
+        out_string(c, "ERROR");
+    }
+}
+#ifdef EXTSTORE
+static void process_extstore_command(conn *c, token_t *tokens, const size_t ntokens) {
+    set_noreply_maybe(c, tokens, ntokens);
+    bool ok = true;
+    if (ntokens < 4) {
+        ok = false;
+    } else if (strcmp(tokens[1].value, "free_memchunks") == 0 && ntokens > 4) {
+        /* per-slab-class free chunk setting. */
+        unsigned int clsid = 0;
+        unsigned int limit = 0;
+        if (!safe_strtoul(tokens[2].value, &clsid) ||
+                !safe_strtoul(tokens[3].value, &limit)) {
+            ok = false;
+        } else {
+            if (clsid < MAX_NUMBER_OF_SLAB_CLASSES) {
+                settings.ext_free_memchunks[clsid] = limit;
+            } else {
+                ok = false;
+            }
+        }
+    } else if (strcmp(tokens[1].value, "item_size") == 0) {
+        if (!safe_strtoul(tokens[2].value, &settings.ext_item_size))
+            ok = false;
+    } else if (strcmp(tokens[1].value, "item_age") == 0) {
+        if (!safe_strtoul(tokens[2].value, &settings.ext_item_age))
+            ok = false;
+    } else if (strcmp(tokens[1].value, "low_ttl") == 0) {
+        if (!safe_strtoul(tokens[2].value, &settings.ext_low_ttl))
+            ok = false;
+    } else if (strcmp(tokens[1].value, "recache_rate") == 0) {
+        if (!safe_strtoul(tokens[2].value, &settings.ext_recache_rate))
+            ok = false;
+    } else if (strcmp(tokens[1].value, "compact_under") == 0) {
+        if (!safe_strtoul(tokens[2].value, &settings.ext_compact_under))
+            ok = false;
+    } else if (strcmp(tokens[1].value, "drop_under") == 0) {
+        if (!safe_strtoul(tokens[2].value, &settings.ext_drop_under))
+            ok = false;
+    } else if (strcmp(tokens[1].value, "max_frag") == 0) {
+        if (!safe_strtod(tokens[2].value, &settings.ext_max_frag))
+            ok = false;
+    } else if (strcmp(tokens[1].value, "drop_unread") == 0) {
+        unsigned int v;
+        if (!safe_strtoul(tokens[2].value, &v)) {
+            ok = false;
+        } else {
+            settings.ext_drop_unread = v == 0 ? false : true;
+        }
+    } else {
+        ok = false;
+    }
+    if (!ok) {
+        out_string(c, "ERROR");
+    } else {
+        out_string(c, "OK");
+    }
+}
+#endif
+static void process_flush_all_command(conn *c, token_t *tokens, const size_t ntokens) {
+    time_t exptime = 0;
+    rel_time_t new_oldest = 0;
+
+    set_noreply_maybe(c, tokens, ntokens);
+
+    pthread_mutex_lock(&c->thread->stats.mutex);
+    c->thread->stats.flush_cmds++;
+    pthread_mutex_unlock(&c->thread->stats.mutex);
+
+    if (!settings.flush_enabled) {
+        // flush_all is not allowed but we log it on stats
+        out_string(c, "CLIENT_ERROR flush_all not allowed");
+        return;
+    }
+
+    if (ntokens != (c->noreply ? 3 : 2)) {
+        exptime = strtol(tokens[1].value, NULL, 10);
+        if(errno == ERANGE) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+            return;
+        }
+    }
+
+    /*
+      If exptime is zero realtime() would return zero too, and
+      realtime(exptime) - 1 would overflow to the max unsigned
+      value.  So we process exptime == 0 the same way we do when
+      no delay is given at all.
+    */
+    if (exptime > 0) {
+        new_oldest = realtime(exptime);
+    } else { /* exptime == 0 */
+        new_oldest = current_time;
+    }
+
+    if (settings.use_cas) {
+        settings.oldest_live = new_oldest - 1;
+        if (settings.oldest_live <= current_time)
+            settings.oldest_cas = get_cas_id();
+    } else {
+        settings.oldest_live = new_oldest;
+    }
+    out_string(c, "OK");
+}
+
+static void process_version_command(conn *c) {
+    out_string(c, "VERSION " VERSION);
+}
+
+static void process_quit_command(conn *c) {
+    conn_set_state(c, conn_mwrite);
+    c->close_after_write = true;
+}
+
+static void process_shutdown_command(conn *c) {
+    if (settings.shutdown_command) {
+        conn_set_state(c, conn_closing);
+        raise(SIGINT);
+    } else {
+        out_string(c, "ERROR: shutdown not enabled");
+    }
+}
+
+static void process_slabs_command(conn *c, token_t *tokens, const size_t ntokens) {
+    if (ntokens == 5 && strcmp(tokens[COMMAND_TOKEN + 1].value, "reassign") == 0) {
+        int src, dst, rv;
+
+        if (settings.slab_reassign == false) {
+            out_string(c, "CLIENT_ERROR slab reassignment disabled");
+            return;
+        }
+
+        src = strtol(tokens[2].value, NULL, 10);
+        dst = strtol(tokens[3].value, NULL, 10);
+
+        if (errno == ERANGE) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+            return;
+        }
+
+        rv = slabs_reassign(src, dst);
+        switch (rv) {
+        case REASSIGN_OK:
+            out_string(c, "OK");
+            break;
+        case REASSIGN_RUNNING:
+            out_string(c, "BUSY currently processing reassign request");
+            break;
+        case REASSIGN_BADCLASS:
+            out_string(c, "BADCLASS invalid src or dst class id");
+            break;
+        case REASSIGN_NOSPARE:
+            out_string(c, "NOSPARE source class has no spare pages");
+            break;
+        case REASSIGN_SRC_DST_SAME:
+            out_string(c, "SAME src and dst class are identical");
+            break;
+        }
+        return;
+    } else if (ntokens >= 4 &&
+        (strcmp(tokens[COMMAND_TOKEN + 1].value, "automove") == 0)) {
+        process_slabs_automove_command(c, tokens, ntokens);
+    } else {
+        out_string(c, "ERROR");
+    }
+}
+
+static void process_lru_crawler_command(conn *c, token_t *tokens, const size_t ntokens) {
+    if (ntokens == 4 && strcmp(tokens[COMMAND_TOKEN + 1].value, "crawl") == 0) {
+        int rv;
+        if (settings.lru_crawler == false) {
+            out_string(c, "CLIENT_ERROR lru crawler disabled");
+            return;
+        }
+
+        rv = lru_crawler_crawl(tokens[2].value, CRAWLER_EXPIRED, NULL, 0,
+                settings.lru_crawler_tocrawl);
+        switch(rv) {
+        case CRAWLER_OK:
+            out_string(c, "OK");
+            break;
+        case CRAWLER_RUNNING:
+            out_string(c, "BUSY currently processing crawler request");
+            break;
+        case CRAWLER_BADCLASS:
+            out_string(c, "BADCLASS invalid class id");
+            break;
+        case CRAWLER_NOTSTARTED:
+            out_string(c, "NOTSTARTED no items to crawl");
+            break;
+        case CRAWLER_ERROR:
+            out_string(c, "ERROR an unknown error happened");
+            break;
+        }
+        return;
+    } else if (ntokens == 4 && strcmp(tokens[COMMAND_TOKEN + 1].value, "metadump") == 0) {
+        if (settings.lru_crawler == false) {
+            out_string(c, "CLIENT_ERROR lru crawler disabled");
+            return;
+        }
+        if (!settings.dump_enabled) {
+            out_string(c, "ERROR metadump not allowed");
+            return;
+        }
+        if (resp_has_stack(c)) {
+            out_string(c, "ERROR cannot pipeline other commands before metadump");
+            return;
+        }
+
+        int rv = lru_crawler_crawl(tokens[2].value, CRAWLER_METADUMP,
+                c, c->sfd, LRU_CRAWLER_CAP_REMAINING);
+        switch(rv) {
+            case CRAWLER_OK:
+                // TODO: documentation says this string is returned, but
+                // it never was before. We never switch to conn_write so
+                // this o_s call never worked. Need to talk to users and
+                // decide if removing the OK from docs is fine.
+                //out_string(c, "OK");
+                // TODO: Don't reuse conn_watch here.
+                conn_set_state(c, conn_watch);
+                event_del(&c->event);
+                break;
+            case CRAWLER_RUNNING:
+                out_string(c, "BUSY currently processing crawler request");
+                break;
+            case CRAWLER_BADCLASS:
+                out_string(c, "BADCLASS invalid class id");
+                break;
+            case CRAWLER_NOTSTARTED:
+                out_string(c, "NOTSTARTED no items to crawl");
+                break;
+            case CRAWLER_ERROR:
+                out_string(c, "ERROR an unknown error happened");
+                break;
+        }
+        return;
+    } else if (ntokens == 4 && strcmp(tokens[COMMAND_TOKEN + 1].value, "tocrawl") == 0) {
+        uint32_t tocrawl;
+         if (!safe_strtoul(tokens[2].value, &tocrawl)) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+            return;
+        }
+        settings.lru_crawler_tocrawl = tocrawl;
+        out_string(c, "OK");
+        return;
+    } else if (ntokens == 4 && strcmp(tokens[COMMAND_TOKEN + 1].value, "sleep") == 0) {
+        uint32_t tosleep;
+        if (!safe_strtoul(tokens[2].value, &tosleep)) {
+            out_string(c, "CLIENT_ERROR bad command line format");
+            return;
+        }
+        if (tosleep > 1000000) {
+            out_string(c, "CLIENT_ERROR sleep must be one second or less");
+            return;
+        }
+        settings.lru_crawler_sleep = tosleep;
+        out_string(c, "OK");
+        return;
+    } else if (ntokens == 3) {
+        if ((strcmp(tokens[COMMAND_TOKEN + 1].value, "enable") == 0)) {
+            if (start_item_crawler_thread() == 0) {
+                out_string(c, "OK");
+            } else {
+                out_string(c, "ERROR failed to start lru crawler thread");
+            }
+        } else if ((strcmp(tokens[COMMAND_TOKEN + 1].value, "disable") == 0)) {
+            if (stop_item_crawler_thread(CRAWLER_NOWAIT) == 0) {
+                out_string(c, "OK");
+            } else {
+                out_string(c, "ERROR failed to stop lru crawler thread");
+            }
+        } else {
+            out_string(c, "ERROR");
+        }
+        return;
+    } else {
+        out_string(c, "ERROR");
+    }
+}
+#ifdef TLS
+static void process_refresh_certs_command(conn *c, token_t *tokens, const size_t ntokens) {
+    set_noreply_maybe(c, tokens, ntokens);
+    char *errmsg = NULL;
+    if (refresh_certs(&errmsg)) {
+        out_string(c, "OK");
+    } else {
+        write_and_free(c, errmsg, strlen(errmsg));
+    }
+    return;
+}
+#endif
+
+// TODO: pipelined commands are incompatible with shifting connections to a
+// side thread. Given this only happens in two instances (watch and
+// lru_crawler metadump) it should be fine for things to bail. It _should_ be
+// unusual for these commands.
+// This is hard to fix since tokenize_command() mutilates the read buffer, so
+// we can't drop out and back in again.
+// Leaving this note here to spend more time on a fix when necessary, or if an
+// opportunity becomes obvious.
+void process_command(conn *c, char *command) {
+
+    token_t tokens[MAX_TOKENS];
+    size_t ntokens;
+    int comm;
+
+    assert(c != NULL);
+
+    MEMCACHED_PROCESS_COMMAND_START(c->sfd, c->rcurr, c->rbytes);
+
+    if (settings.verbose > 1)
+        fprintf(stderr, "<%d %s\n", c->sfd, command);
+
+    /*
+     * for commands set/add/replace, we build an item and read the data
+     * directly into it, then continue in nread_complete().
+     */
+
+    // Prep the response object for this query.
+    if (!resp_start(c)) {
+        conn_set_state(c, conn_closing);
+        return;
+    }
+
+    ntokens = tokenize_command(command, tokens, MAX_TOKENS);
+    // All commands need a minimum of two tokens: cmd and NULL finalizer
+    // There are also no valid commands shorter than two bytes.
+    if (ntokens < 2 || tokens[COMMAND_TOKEN].length < 2) {
+        out_string(c, "ERROR");
+        return;
+    }
+
+    // Meta commands are all 2-char in length.
+    char first = tokens[COMMAND_TOKEN].value[0];
+    if (first == 'm' && tokens[COMMAND_TOKEN].length == 2) {
+        switch (tokens[COMMAND_TOKEN].value[1]) {
+            case 'g':
+                process_mget_command(c, tokens, ntokens);
+                break;
+            case 's':
+                process_mset_command(c, tokens, ntokens);
+                break;
+            case 'd':
+                process_mdelete_command(c, tokens, ntokens);
+                break;
+            case 'n':
+                out_string(c, "MN");
+                // mn command forces immediate writeback flush.
+                conn_set_state(c, conn_mwrite);
+                break;
+            case 'a':
+                process_marithmetic_command(c, tokens, ntokens);
+                break;
+            case 'e':
+                process_meta_command(c, tokens, ntokens);
+                break;
+            default:
+                out_string(c, "ERROR");
+                break;
+        }
+    } else if (first == 'g') {
+        // Various get commands are very common.
+        WANT_TOKENS_MIN(ntokens, 3);
+        if (strcmp(tokens[COMMAND_TOKEN].value, "get") == 0) {
+
+            process_get_command(c, tokens, ntokens, false, false);
+        } else if (strcmp(tokens[COMMAND_TOKEN].value, "gets") == 0) {
+
+            process_get_command(c, tokens, ntokens, true, false);
+        } else if (strcmp(tokens[COMMAND_TOKEN].value, "gat") == 0) {
+
+            process_get_command(c, tokens, ntokens, false, true);
+        } else if (strcmp(tokens[COMMAND_TOKEN].value, "gats") == 0) {
+
+            process_get_command(c, tokens, ntokens, true, true);
+        } else {
+            out_string(c, "ERROR");
+        }
+    } else if (first == 's') {
+        if (strcmp(tokens[COMMAND_TOKEN].value, "set") == 0 && (comm = NREAD_SET)) {
+
+            WANT_TOKENS_OR(ntokens, 6, 7);
+            process_update_command(c, tokens, ntokens, comm, false);
+        } else if (strcmp(tokens[COMMAND_TOKEN].value, "stats") == 0) {
+
+            process_stat(c, tokens, ntokens);
+        } else if (strcmp(tokens[COMMAND_TOKEN].value, "shutdown") == 0) {
+
+            process_shutdown_command(c);
+        } else if (strcmp(tokens[COMMAND_TOKEN].value, "slabs") == 0) {
+
+            process_slabs_command(c, tokens, ntokens);
+        } else {
+            out_string(c, "ERROR");
+        }
+    } else if (first == 'a') {
+        if ((strcmp(tokens[COMMAND_TOKEN].value, "add") == 0 && (comm = NREAD_ADD)) ||
+            (strcmp(tokens[COMMAND_TOKEN].value, "append") == 0 && (comm = NREAD_APPEND)) ) {
+
+            WANT_TOKENS_OR(ntokens, 6, 7);
+            process_update_command(c, tokens, ntokens, comm, false);
+        } else {
+            out_string(c, "ERROR");
+        }
+    } else if (first == 'c') {
+        if (strcmp(tokens[COMMAND_TOKEN].value, "cas") == 0 && (comm = NREAD_CAS)) {
+
+            WANT_TOKENS_OR(ntokens, 7, 8);
+            process_update_command(c, tokens, ntokens, comm, true);
+        } else if (strcmp(tokens[COMMAND_TOKEN].value, "cache_memlimit") == 0) {
+
+            WANT_TOKENS_OR(ntokens, 3, 4);
+            process_memlimit_command(c, tokens, ntokens);
+        } else {
+            out_string(c, "ERROR");
+        }
+    } else if (first == 'i') {
+        if (strcmp(tokens[COMMAND_TOKEN].value, "incr") == 0) {
+
+            WANT_TOKENS_OR(ntokens, 4, 5);
+            process_arithmetic_command(c, tokens, ntokens, 1);
+        } else {
+            out_string(c, "ERROR");
+        }
+    } else if (first == 'd') {
+        if (strcmp(tokens[COMMAND_TOKEN].value, "delete") == 0) {
+
+            WANT_TOKENS(ntokens, 3, 5);
+            process_delete_command(c, tokens, ntokens);
+        } else if (strcmp(tokens[COMMAND_TOKEN].value, "decr") == 0) {
+
+            WANT_TOKENS_OR(ntokens, 4, 5);
+            process_arithmetic_command(c, tokens, ntokens, 0);
+        } else {
+            out_string(c, "ERROR");
+        }
+    } else if (first == 't') {
+        if (strcmp(tokens[COMMAND_TOKEN].value, "touch") == 0) {
+
+            WANT_TOKENS_OR(ntokens, 4, 5);
+            process_touch_command(c, tokens, ntokens);
+        } else {
+            out_string(c, "ERROR");
+        }
+    } else if (
+                (strcmp(tokens[COMMAND_TOKEN].value, "replace") == 0 && (comm = NREAD_REPLACE)) ||
+                (strcmp(tokens[COMMAND_TOKEN].value, "prepend") == 0 && (comm = NREAD_PREPEND)) ) {
+
+        WANT_TOKENS_OR(ntokens, 6, 7);
+        process_update_command(c, tokens, ntokens, comm, false);
+
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "bget") == 0) {
+        // ancient "binary get" command which isn't in any documentation, was
+        // removed > 10 years ago, etc. Keeping for compatibility reasons but
+        // we should look deeper into client code and remove this.
+        WANT_TOKENS_MIN(ntokens, 3);
+        process_get_command(c, tokens, ntokens, false, false);
+
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "flush_all") == 0) {
+
+        WANT_TOKENS(ntokens, 2, 4);
+        process_flush_all_command(c, tokens, ntokens);
+
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "version") == 0) {
+
+        process_version_command(c);
+
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "quit") == 0) {
+
+        process_quit_command(c);
+
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "lru_crawler") == 0) {
+
+        process_lru_crawler_command(c, tokens, ntokens);
+
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "watch") == 0) {
+
+        process_watch_command(c, tokens, ntokens);
+
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "verbosity") == 0) {
+        WANT_TOKENS_OR(ntokens, 3, 4);
+        process_verbosity_command(c, tokens, ntokens);
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "lru") == 0) {
+        WANT_TOKENS_MIN(ntokens, 3);
+        process_lru_command(c, tokens, ntokens);
+#ifdef MEMCACHED_DEBUG
+    // commands which exist only for testing the memcached's security protection
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "misbehave") == 0) {
+        process_misbehave_command(c);
+#endif
+#ifdef EXTSTORE
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "extstore") == 0) {
+        WANT_TOKENS_MIN(ntokens, 3);
+        process_extstore_command(c, tokens, ntokens);
+#endif
+#ifdef TLS
+    } else if (strcmp(tokens[COMMAND_TOKEN].value, "refresh_certs") == 0) {
+        process_refresh_certs_command(c, tokens, ntokens);
+#endif
+    } else {
+        if (strncmp(tokens[ntokens - 2].value, "HTTP/", 5) == 0) {
+            conn_set_state(c, conn_closing);
+        } else {
+            out_string(c, "ERROR");
+        }
+    }
+    return;
+}
+
+static int try_read_command_negotiate(conn *c) {
+    assert(c->protocol == negotiating_prot);
+    assert(c != NULL);
+    assert(c->rcurr <= (c->rbuf + c->rsize));
+    assert(c->rbytes > 0);
+
+    if ((unsigned char)c->rbuf[0] == (unsigned char)PROTOCOL_BINARY_REQ) {
+        c->protocol = binary_prot;
+        c->try_read_command = try_read_command_binary;
+    } else {
+        // authentication doesn't work with negotiated protocol.
+        c->protocol = ascii_prot;
+        c->try_read_command = try_read_command_ascii;
+    }
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "%d: Client using the %s protocol\n", c->sfd,
+                prot_text(c->protocol));
+    }
+
+    return c->try_read_command(c);
+}
+
+static int try_read_command_udp(conn *c) {
+    assert(c != NULL);
+    assert(c->rcurr <= (c->rbuf + c->rsize));
+    assert(c->rbytes > 0);
+
+    if ((unsigned char)c->rbuf[0] == (unsigned char)PROTOCOL_BINARY_REQ) {
+        c->protocol = binary_prot;
+        return try_read_command_binary(c);
+    } else {
+        c->protocol = ascii_prot;
+        return try_read_command_ascii(c);
+    }
+}
+
+static int try_read_command_binary(conn *c) {
+    /* Do we have the complete packet header? */
+    if (c->rbytes < sizeof(c->binary_header)) {
+        /* need more data! */
+        return 0;
+    } else {
+        memcpy(&c->binary_header, c->rcurr, sizeof(c->binary_header));
+        protocol_binary_request_header* req;
+        req = &c->binary_header;
+
+        if (settings.verbose > 1) {
+            /* Dump the packet before we convert it to host order */
+            int ii;
+            fprintf(stderr, "<%d Read binary protocol data:", c->sfd);
+            for (ii = 0; ii < sizeof(req->bytes); ++ii) {
+                if (ii % 4 == 0) {
+                    fprintf(stderr, "\n<%d   ", c->sfd);
+                }
+                fprintf(stderr, " 0x%02x", req->bytes[ii]);
+            }
+            fprintf(stderr, "\n");
+        }
+
+        c->binary_header = *req;
+        c->binary_header.request.keylen = ntohs(req->request.keylen);
+        c->binary_header.request.bodylen = ntohl(req->request.bodylen);
+        c->binary_header.request.cas = ntohll(req->request.cas);
+
+        if (c->binary_header.request.magic != PROTOCOL_BINARY_REQ) {
+            if (settings.verbose) {
+                fprintf(stderr, "Invalid magic:  %x\n",
+                        c->binary_header.request.magic);
+            }
+            conn_set_state(c, conn_closing);
+            return -1;
+        }
+
+        uint8_t extlen = c->binary_header.request.extlen;
+        uint16_t keylen = c->binary_header.request.keylen;
+        if (c->rbytes < keylen + extlen + sizeof(c->binary_header)) {
+            // Still need more bytes. Let try_read_network() realign the
+            // read-buffer and fetch more data as necessary.
+            return 0;
+        }
+
+        if (!resp_start(c)) {
+            conn_set_state(c, conn_closing);
+            return -1;
+        }
+
+        c->cmd = c->binary_header.request.opcode;
+        c->keylen = c->binary_header.request.keylen;
+        c->opaque = c->binary_header.request.opaque;
+        /* clear the returned cas value */
+        c->cas = 0;
+
+        c->last_cmd_time = current_time;
+        // sigh. binprot has no "largest possible extlen" define, and I don't
+        // want to refactor a ton of code either. Header is only ever used out
+        // of c->binary_header, but the extlen stuff is used for the latter
+        // bytes. Just wastes 24 bytes on the stack this way.
+        char extbuf[sizeof(c->binary_header) + BIN_MAX_EXTLEN+1];
+        memcpy(extbuf + sizeof(c->binary_header), c->rcurr + sizeof(c->binary_header),
+                extlen > BIN_MAX_EXTLEN ? BIN_MAX_EXTLEN : extlen);
+        c->rbytes -= sizeof(c->binary_header) + extlen + keylen;
+        c->rcurr += sizeof(c->binary_header) + extlen + keylen;
+
+        dispatch_bin_command(c, extbuf);
+    }
+
+    return 1;
+}
+
+static int try_read_command_asciiauth(conn *c) {
+    token_t tokens[MAX_TOKENS];
+    size_t ntokens;
+    char *cont = NULL;
+
+    if (!c->resp) {
+        if (!resp_start(c)) {
+            conn_set_state(c, conn_closing);
+            return 1;
+        }
+    }
+
+    // TODO: move to another function.
+    if (!c->sasl_started) {
+        char *el;
+        uint32_t size = 0;
+
+        // impossible for the auth command to be this short.
+        if (c->rbytes < 2)
+            return 0;
+
+        el = memchr(c->rcurr, '\n', c->rbytes);
+
+        // If no newline after 1k, getting junk data, close out.
+        if (!el) {
+            if (c->rbytes > 1024) {
+                conn_set_state(c, conn_closing);
+                return 1;
+            }
+            return 0;
+        }
+
+        // Looking for: "set foo 0 0 N\r\nuser pass\r\n"
+        // key, flags, and ttl are ignored. N is used to see if we have the rest.
+
+        // so tokenize doesn't walk past into the value.
+        // it's fine to leave the \r in, as strtoul will stop at it.
+        *el = '\0';
+
+        ntokens = tokenize_command(c->rcurr, tokens, MAX_TOKENS);
+        // ensure the buffer is consumed.
+        c->rbytes -= (el - c->rcurr) + 1;
+        c->rcurr += (el - c->rcurr) + 1;
+
+        // final token is a NULL ender, so we have one more than expected.
+        if (ntokens < 6
+                || strcmp(tokens[0].value, "set") != 0
+                || !safe_strtoul(tokens[4].value, &size)) {
+            out_string(c, "CLIENT_ERROR unauthenticated");
+            return 1;
+        }
+
+        // we don't actually care about the key at all; it can be anything.
+        // we do care about the size of the remaining read.
+        c->rlbytes = size + 2;
+
+        c->sasl_started = true; // reuse from binprot sasl, but not sasl :)
+    }
+
+    if (c->rbytes < c->rlbytes) {
+        // need more bytes.
+        return 0;
+    }
+
+    cont = c->rcurr;
+    // advance buffer. no matter what we're stopping.
+    c->rbytes -= c->rlbytes;
+    c->rcurr += c->rlbytes;
+    c->sasl_started = false;
+
+    // must end with \r\n
+    // NB: I thought ASCII sets also worked with just \n, but according to
+    // complete_nread_ascii only \r\n is valid.
+    if (strncmp(cont + c->rlbytes - 2, "\r\n", 2) != 0) {
+        out_string(c, "CLIENT_ERROR bad command line termination");
+        return 1;
+    }
+
+    // payload should be "user pass", so we can use the tokenizer.
+    cont[c->rlbytes - 2] = '\0';
+    ntokens = tokenize_command(cont, tokens, MAX_TOKENS);
+
+    if (ntokens < 3) {
+        out_string(c, "CLIENT_ERROR bad authentication token format");
+        return 1;
+    }
+
+    if (authfile_check(tokens[0].value, tokens[1].value) == 1) {
+        out_string(c, "STORED");
+        c->authenticated = true;
+        c->try_read_command = try_read_command_ascii;
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.auth_cmds++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+    } else {
+        out_string(c, "CLIENT_ERROR authentication failure");
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.auth_cmds++;
+        c->thread->stats.auth_errors++;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+    }
+
+    return 1;
+}
+
+static int try_read_command_ascii(conn *c) {
+    char *el, *cont;
+
+    if (c->rbytes == 0)
+        return 0;
+
+    el = memchr(c->rcurr, '\n', c->rbytes);
+    if (!el) {
+        if (c->rbytes > 1024) {
+            /*
+             * We didn't have a '\n' in the first k. This _has_ to be a
+             * large multiget, if not we should just nuke the connection.
+             */
+            char *ptr = c->rcurr;
+            while (*ptr == ' ') { /* ignore leading whitespaces */
+                ++ptr;
+            }
+
+            if (ptr - c->rcurr > 100 ||
+                (strncmp(ptr, "get ", 4) && strncmp(ptr, "gets ", 5))) {
+
+                conn_set_state(c, conn_closing);
+                return 1;
+            }
+
+            // ASCII multigets are unbound, so our fixed size rbuf may not
+            // work for this particular workload... For backcompat we'll use a
+            // malloc/realloc/free routine just for this.
+            if (!c->rbuf_malloced) {
+                if (!rbuf_switch_to_malloc(c)) {
+                    conn_set_state(c, conn_closing);
+                    return 1;
+                }
+            }
+        }
+
+        return 0;
+    }
+    cont = el + 1;
+    if ((el - c->rcurr) > 1 && *(el - 1) == '\r') {
+        el--;
+    }
+    *el = '\0';
+
+    assert(cont <= (c->rcurr + c->rbytes));
+
+    c->last_cmd_time = current_time;
+    process_command(c, c->rcurr);
+
+    c->rbytes -= (cont - c->rcurr);
+    c->rcurr = cont;
+
+    assert(c->rcurr <= (c->rbuf + c->rsize));
+
+    return 1;
+}
+
+/*
+ * read a UDP request.
+ */
+static enum try_read_result try_read_udp(conn *c) {
+    int res;
+
+    assert(c != NULL);
+
+    c->request_addr_size = sizeof(c->request_addr);
+    res = recvfrom(c->sfd, c->rbuf, c->rsize,
+                   0, (struct sockaddr *)&c->request_addr,
+                   &c->request_addr_size);
+    if (res > 8) {
+        unsigned char *buf = (unsigned char *)c->rbuf;
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.bytes_read += res;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        /* Beginning of UDP packet is the request ID; save it. */
+        c->request_id = buf[0] * 256 + buf[1];
+
+        /* If this is a multi-packet request, drop it. */
+        if (buf[4] != 0 || buf[5] != 1) {
+            out_string(c, "SERVER_ERROR multi-packet request not supported");
+            return READ_NO_DATA_RECEIVED;
+        }
+
+        /* Don't care about any of the rest of the header. */
+        res -= 8;
+        memmove(c->rbuf, c->rbuf + 8, res);
+
+        c->rbytes = res;
+        c->rcurr = c->rbuf;
+        return READ_DATA_RECEIVED;
+    }
+    return READ_NO_DATA_RECEIVED;
+}
+
+/*
+ * read from network as much as we can, handle buffer overflow and connection
+ * close.
+ * before reading, move the remaining incomplete fragment of a command
+ * (if any) to the beginning of the buffer.
+ *
+ * To protect us from someone flooding a connection with bogus data causing
+ * the connection to eat up all available memory, break out and start looking
+ * at the data I've got after a number of reallocs...
+ *
+ * @return enum try_read_result
+ */
+static enum try_read_result try_read_network(conn *c) {
+    enum try_read_result gotdata = READ_NO_DATA_RECEIVED;
+    int res;
+    int num_allocs = 0;
+    assert(c != NULL);
+
+    if (c->rcurr != c->rbuf) {
+        if (c->rbytes != 0) /* otherwise there's nothing to copy */
+            memmove(c->rbuf, c->rcurr, c->rbytes);
+        c->rcurr = c->rbuf;
+    }
+
+    while (1) {
+        // TODO: move to rbuf_* func?
+        if (c->rbytes >= c->rsize && c->rbuf_malloced) {
+            if (num_allocs == 4) {
+                return gotdata;
+            }
+            ++num_allocs;
+            char *new_rbuf = realloc(c->rbuf, c->rsize * 2);
+            if (!new_rbuf) {
+                STATS_LOCK();
+                stats.malloc_fails++;
+                STATS_UNLOCK();
+                if (settings.verbose > 0) {
+                    fprintf(stderr, "Couldn't realloc input buffer\n");
+                }
+                c->rbytes = 0; /* ignore what we read */
+                out_of_memory(c, "SERVER_ERROR out of memory reading request");
+                c->close_after_write = true;
+                return READ_MEMORY_ERROR;
+            }
+            c->rcurr = c->rbuf = new_rbuf;
+            c->rsize *= 2;
+        }
+
+        int avail = c->rsize - c->rbytes;
+        res = c->read(c, c->rbuf + c->rbytes, avail);
+        if (res > 0) {
+            pthread_mutex_lock(&c->thread->stats.mutex);
+            c->thread->stats.bytes_read += res;
+            pthread_mutex_unlock(&c->thread->stats.mutex);
+            gotdata = READ_DATA_RECEIVED;
+            c->rbytes += res;
+            if (res == avail && c->rbuf_malloced) {
+                // Resize rbuf and try a few times if huge ascii multiget.
+                continue;
+            } else {
+                break;
+            }
+        }
+        if (res == 0) {
+            return READ_ERROR;
+        }
+        if (res == -1) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                break;
+            }
+            return READ_ERROR;
+        }
+    }
+    return gotdata;
+}
+
+static bool update_event(conn *c, const int new_flags) {
+    assert(c != NULL);
+
+    struct event_base *base = c->event.ev_base;
+    if (c->ev_flags == new_flags)
+        return true;
+    if (event_del(&c->event) == -1) return false;
+    event_set(&c->event, c->sfd, new_flags, event_handler, (void *)c);
+    event_base_set(base, &c->event);
+    c->ev_flags = new_flags;
+    if (event_add(&c->event, 0) == -1) return false;
+    return true;
+}
+
+/*
+ * Sets whether we are listening for new connections or not.
+ */
+void do_accept_new_conns(const bool do_accept) {
+    conn *next;
+
+    for (next = listen_conn; next; next = next->next) {
+        if (do_accept) {
+            update_event(next, EV_READ | EV_PERSIST);
+            if (listen(next->sfd, settings.backlog) != 0) {
+                perror("listen");
+            }
+        }
+        else {
+            update_event(next, 0);
+            if (listen(next->sfd, 0) != 0) {
+                perror("listen");
+            }
+        }
+    }
+
+    if (do_accept) {
+        struct timeval maxconns_exited;
+        uint64_t elapsed_us;
+        gettimeofday(&maxconns_exited,NULL);
+        STATS_LOCK();
+        elapsed_us =
+            (maxconns_exited.tv_sec - stats.maxconns_entered.tv_sec) * 1000000
+            + (maxconns_exited.tv_usec - stats.maxconns_entered.tv_usec);
+        stats.time_in_listen_disabled_us += elapsed_us;
+        stats_state.accepting_conns = true;
+        STATS_UNLOCK();
+    } else {
+        STATS_LOCK();
+        stats_state.accepting_conns = false;
+        gettimeofday(&stats.maxconns_entered,NULL);
+        stats.listen_disabled_num++;
+        STATS_UNLOCK();
+        allow_new_conns = false;
+        maxconns_handler(-42, 0, 0);
+    }
+}
+
+#define TRANSMIT_ONE_RESP true
+#define TRANSMIT_ALL_RESP false
+static int _transmit_pre(conn *c, struct iovec *iovs, int iovused, bool one_resp) {
+    mc_resp *resp = c->resp_head;
+    while (resp && iovused + resp->iovcnt < IOV_MAX-1) {
+        if (resp->skip) {
+            // Don't actually unchain the resp obj here since it's singly-linked.
+            // Just let the post function handle it linearly.
+            resp = resp->next;
+            continue;
+        }
+        if (resp->chunked_data_iov) {
+            // Handle chunked items specially.
+            // They spend much more time in send so we can be a bit wasteful
+            // in rebuilding iovecs for them.
+            item_chunk *ch = (item_chunk *)ITEM_schunk((item *)resp->iov[resp->chunked_data_iov].iov_base);
+            int x;
+            for (x = 0; x < resp->iovcnt; x++) {
+                // This iov is tracking how far we've copied so far.
+                if (x == resp->chunked_data_iov) {
+                    int done = resp->chunked_total - resp->iov[x].iov_len;
+                    // Start from the len to allow binprot to cut the \r\n
+                    int todo = resp->iov[x].iov_len;
+                    while (ch && todo > 0 && iovused < IOV_MAX-1) {
+                        int skip = 0;
+                        if (!ch->used) {
+                            ch = ch->next;
+                            continue;
+                        }
+                        // Skip parts we've already sent.
+                        if (done >= ch->used) {
+                            done -= ch->used;
+                            ch = ch->next;
+                            continue;
+                        } else if (done) {
+                            skip = done;
+                            done = 0;
+                        }
+                        iovs[iovused].iov_base = ch->data + skip;
+                        // Stupid binary protocol makes this go negative.
+                        iovs[iovused].iov_len = ch->used - skip > todo ? todo : ch->used - skip;
+                        iovused++;
+                        todo -= ch->used - skip;
+                        ch = ch->next;
+                    }
+                } else {
+                    iovs[iovused].iov_base = resp->iov[x].iov_base;
+                    iovs[iovused].iov_len = resp->iov[x].iov_len;
+                    iovused++;
+                }
+                if (iovused >= IOV_MAX-1)
+                    break;
+            }
+        } else {
+            memcpy(&iovs[iovused], resp->iov, sizeof(struct iovec)*resp->iovcnt);
+            iovused += resp->iovcnt;
+        }
+
+        // done looking at first response, walk down the chain.
+        resp = resp->next;
+        // used for UDP mode: UDP cannot send multiple responses per packet.
+        if (one_resp)
+            break;
+    }
+    return iovused;
+}
+
+/*
+ * Decrements and completes responses based on how much data was transmitted.
+ * Takes the connection and current result bytes.
+ */
+static void _transmit_post(conn *c, ssize_t res) {
+    // We've written some of the data. Remove the completed
+    // responses from the list of pending writes.
+    mc_resp *resp = c->resp_head;
+    while (resp) {
+        int x;
+        if (resp->skip) {
+            resp = resp_finish(c, resp);
+            continue;
+        }
+
+        // fastpath check. all small responses should cut here.
+        if (res >= resp->tosend) {
+            res -= resp->tosend;
+            resp = resp_finish(c, resp);
+            continue;
+        }
+
+        // it's fine to re-check iov's that were zeroed out before.
+        for (x = 0; x < resp->iovcnt; x++) {
+            struct iovec *iov = &resp->iov[x];
+            if (res >= iov->iov_len) {
+                resp->tosend -= iov->iov_len;
+                res -= iov->iov_len;
+                iov->iov_len = 0;
+            } else {
+                // Dumb special case for chunked items. Currently tracking
+                // where to inject the chunked item via iov_base.
+                // Extra not-great since chunked items can't be the first
+                // index, so we have to check for non-zero c_d_iov first.
+                if (!resp->chunked_data_iov || x != resp->chunked_data_iov) {
+                    iov->iov_base = (char *)iov->iov_base + res;
+                }
+                iov->iov_len -= res;
+                resp->tosend -= res;
+                res = 0;
+                break;
+            }
+        }
+
+        // are we done with this response object?
+        if (resp->tosend == 0) {
+            resp = resp_finish(c, resp);
+        } else {
+            // Jammed up here. This is the new head.
+            break;
+        }
+    }
+}
+
+/*
+ * Transmit the next chunk of data from our list of msgbuf structures.
+ *
+ * Returns:
+ *   TRANSMIT_COMPLETE   All done writing.
+ *   TRANSMIT_INCOMPLETE More data remaining to write.
+ *   TRANSMIT_SOFT_ERROR Can't write any more right now.
+ *   TRANSMIT_HARD_ERROR Can't write (c->state is set to conn_closing)
+ */
+static enum transmit_result transmit(conn *c) {
+    assert(c != NULL);
+    struct iovec iovs[IOV_MAX];
+    struct msghdr msg;
+    int iovused = 0;
+
+    // init the msg.
+    memset(&msg, 0, sizeof(struct msghdr));
+    msg.msg_iov = iovs;
+
+    iovused = _transmit_pre(c, iovs, iovused, TRANSMIT_ALL_RESP);
+
+    // Alright, send.
+    ssize_t res;
+    msg.msg_iovlen = iovused;
+    res = c->sendmsg(c, &msg, 0);
+    if (res >= 0) {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.bytes_written += res;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        // Decrement any partial IOV's and complete any finished resp's.
+        _transmit_post(c, res);
+
+        if (c->resp_head) {
+            return TRANSMIT_INCOMPLETE;
+        } else {
+            return TRANSMIT_COMPLETE;
+        }
+    }
+
+    if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+        if (!update_event(c, EV_WRITE | EV_PERSIST)) {
+            if (settings.verbose > 0)
+                fprintf(stderr, "Couldn't update event\n");
+            conn_set_state(c, conn_closing);
+            return TRANSMIT_HARD_ERROR;
+        }
+        return TRANSMIT_SOFT_ERROR;
+    }
+    /* if res == -1 and error is not EAGAIN or EWOULDBLOCK,
+       we have a real error, on which we close the connection */
+    if (settings.verbose > 0)
+        perror("Failed to write, and not due to blocking");
+
+    conn_set_state(c, conn_closing);
+    return TRANSMIT_HARD_ERROR;
+}
+
+static void build_udp_header(unsigned char *hdr, mc_resp *resp) {
+    // We need to communicate the total number of packets
+    // If this isn't set, it's the first time this response is building a udp
+    // header, so "tosend" must be static.
+    if (!resp->udp_total) {
+        uint32_t total;
+        total = resp->tosend / UDP_MAX_PAYLOAD_SIZE;
+        if (resp->tosend % UDP_MAX_PAYLOAD_SIZE)
+            total++;
+        // The spec doesn't really say what we should do here. It's _probably_
+        // better to bail out?
+        if (total > USHRT_MAX) {
+            total = USHRT_MAX;
+        }
+        resp->udp_total = total;
+    }
+
+    // TODO: why wasn't this hto*'s and casts?
+    // this ends up sending UDP hdr data specifically in host byte order.
+    *hdr++ = resp->request_id / 256;
+    *hdr++ = resp->request_id % 256;
+    *hdr++ = resp->udp_sequence / 256;
+    *hdr++ = resp->udp_sequence % 256;
+    *hdr++ = resp->udp_total / 256;
+    *hdr++ = resp->udp_total % 256;
+    *hdr++ = 0;
+    *hdr++ = 0;
+    resp->udp_sequence++;
+}
+
+/*
+ * UDP specific transmit function. Uses its own function rather than check
+ * IS_UDP() five times. If we ever implement sendmmsg or similar support they
+ * will diverge even more.
+ * Does not use TLS.
+ *
+ * Returns:
+ *   TRANSMIT_COMPLETE   All done writing.
+ *   TRANSMIT_INCOMPLETE More data remaining to write.
+ *   TRANSMIT_SOFT_ERROR Can't write any more right now.
+ *   TRANSMIT_HARD_ERROR Can't write (c->state is set to conn_closing)
+ */
+static enum transmit_result transmit_udp(conn *c) {
+    assert(c != NULL);
+    struct iovec iovs[IOV_MAX];
+    struct msghdr msg;
+    mc_resp *resp;
+    int iovused = 0;
+    unsigned char udp_hdr[UDP_HEADER_SIZE];
+
+    // We only send one UDP packet per call (ugh), so we can only operate on a
+    // single response at a time.
+    resp = c->resp_head;
+
+    if (!resp) {
+        return TRANSMIT_COMPLETE;
+    }
+
+    if (resp->skip) {
+        resp = resp_finish(c, resp);
+        return TRANSMIT_INCOMPLETE;
+    }
+
+    // clear the message and initialize it.
+    memset(&msg, 0, sizeof(struct msghdr));
+    msg.msg_iov = iovs;
+
+    // the UDP source to return to.
+    msg.msg_name = &resp->request_addr;
+    msg.msg_namelen = resp->request_addr_size;
+
+    // First IOV is the custom UDP header.
+    iovs[0].iov_base = udp_hdr;
+    iovs[0].iov_len = UDP_HEADER_SIZE;
+    build_udp_header(udp_hdr, resp);
+    iovused++;
+
+    // Fill the IOV's the standard way.
+    // TODO: might get a small speedup if we let it break early with a length
+    // limit.
+    iovused = _transmit_pre(c, iovs, iovused, TRANSMIT_ONE_RESP);
+
+    // Clip the IOV's to the max UDP packet size.
+    // If we add support for send_mmsg, this can be where we split msg's.
+    {
+        int x = 0;
+        int len = 0;
+        for (x = 0; x < iovused; x++) {
+            if (len + iovs[x].iov_len >= UDP_MAX_PAYLOAD_SIZE) {
+                iovs[x].iov_len = UDP_MAX_PAYLOAD_SIZE - len;
+                x++;
+                break;
+            } else {
+                len += iovs[x].iov_len;
+            }
+        }
+        iovused = x;
+    }
+
+    ssize_t res;
+    msg.msg_iovlen = iovused;
+    // NOTE: uses system sendmsg since we have no support for indirect UDP.
+    res = sendmsg(c->sfd, &msg, 0);
+    if (res >= 0) {
+        pthread_mutex_lock(&c->thread->stats.mutex);
+        c->thread->stats.bytes_written += res;
+        pthread_mutex_unlock(&c->thread->stats.mutex);
+
+        // Ignore the header size from forwarding the IOV's
+        res -= UDP_HEADER_SIZE;
+
+        // Decrement any partial IOV's and complete any finished resp's.
+        _transmit_post(c, res);
+
+        if (c->resp_head) {
+            return TRANSMIT_INCOMPLETE;
+        } else {
+            return TRANSMIT_COMPLETE;
+        }
+    }
+
+    if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+        if (!update_event(c, EV_WRITE | EV_PERSIST)) {
+            if (settings.verbose > 0)
+                fprintf(stderr, "Couldn't update event\n");
+            conn_set_state(c, conn_closing);
+            return TRANSMIT_HARD_ERROR;
+        }
+        return TRANSMIT_SOFT_ERROR;
+    }
+    /* if res == -1 and error is not EAGAIN or EWOULDBLOCK,
+       we have a real error, on which we close the connection */
+    if (settings.verbose > 0)
+        perror("Failed to write, and not due to blocking");
+
+    conn_set_state(c, conn_read);
+    return TRANSMIT_HARD_ERROR;
+}
+
+
+/* Does a looped read to fill data chunks */
+/* TODO: restrict number of times this can loop.
+ * Also, benchmark using readv's.
+ */
+static int read_into_chunked_item(conn *c) {
+    int total = 0;
+    int res;
+    assert(c->rcurr != c->ritem);
+
+    while (c->rlbytes > 0) {
+        item_chunk *ch = (item_chunk *)c->ritem;
+        if (ch->size == ch->used) {
+            // FIXME: ch->next is currently always 0. remove this?
+            if (ch->next) {
+                c->ritem = (char *) ch->next;
+            } else {
+                /* Allocate next chunk. Binary protocol needs 2b for \r\n */
+                c->ritem = (char *) do_item_alloc_chunk(ch, c->rlbytes +
+                       ((c->protocol == binary_prot) ? 2 : 0));
+                if (!c->ritem) {
+                    // We failed an allocation. Let caller handle cleanup.
+                    total = -2;
+                    break;
+                }
+                // ritem has new chunk, restart the loop.
+                continue;
+                //assert(c->rlbytes == 0);
+            }
+        }
+
+        int unused = ch->size - ch->used;
+        /* first check if we have leftovers in the conn_read buffer */
+        if (c->rbytes > 0) {
+            total = 0;
+            int tocopy = c->rbytes > c->rlbytes ? c->rlbytes : c->rbytes;
+            tocopy = tocopy > unused ? unused : tocopy;
+            if (c->ritem != c->rcurr) {
+                memmove(ch->data + ch->used, c->rcurr, tocopy);
+            }
+            total += tocopy;
+            c->rlbytes -= tocopy;
+            c->rcurr += tocopy;
+            c->rbytes -= tocopy;
+            ch->used += tocopy;
+            if (c->rlbytes == 0) {
+                break;
+            }
+        } else {
+            /*  now try reading from the socket */
+            res = c->read(c, ch->data + ch->used,
+                    (unused > c->rlbytes ? c->rlbytes : unused));
+            if (res > 0) {
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.bytes_read += res;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+                ch->used += res;
+                total += res;
+                c->rlbytes -= res;
+            } else {
+                /* Reset total to the latest result so caller can handle it */
+                total = res;
+                break;
+            }
+        }
+    }
+
+    /* At some point I will be able to ditch the \r\n from item storage and
+       remove all of these kludges.
+       The above binprot check ensures inline space for \r\n, but if we do
+       exactly enough allocs there will be no additional chunk for \r\n.
+     */
+    if (c->rlbytes == 0 && c->protocol == binary_prot && total >= 0) {
+        item_chunk *ch = (item_chunk *)c->ritem;
+        if (ch->size - ch->used < 2) {
+            c->ritem = (char *) do_item_alloc_chunk(ch, 2);
+            if (!c->ritem) {
+                total = -2;
+            }
+        }
+    }
+    return total;
+}
+
+static void drive_machine(conn *c) {
+    bool stop = false;
+    int sfd;
+    socklen_t addrlen;
+    struct sockaddr_storage addr;
+    int nreqs = settings.reqs_per_event;
+    int res;
+    const char *str;
+#ifdef HAVE_ACCEPT4
+    static int  use_accept4 = 1;
+#else
+    static int  use_accept4 = 0;
+#endif
+
+    assert(c != NULL);
+
+    while (!stop) {
+
+        switch(c->state) {
+        case conn_listening:
+            addrlen = sizeof(addr);
+#ifdef HAVE_ACCEPT4
+            if (use_accept4) {
+                sfd = accept4(c->sfd, (struct sockaddr *)&addr, &addrlen, SOCK_NONBLOCK);
+            } else {
+                sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
+            }
+#else
+            sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
+#endif
+            if (sfd == -1) {
+                if (use_accept4 && errno == ENOSYS) {
+                    use_accept4 = 0;
+                    continue;
+                }
+                perror(use_accept4 ? "accept4()" : "accept()");
+                if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                    /* these are transient, so don't log anything */
+                    stop = true;
+                } else if (errno == EMFILE) {
+                    if (settings.verbose > 0)
+                        fprintf(stderr, "Too many open connections\n");
+                    accept_new_conns(false);
+                    stop = true;
+                } else {
+                    perror("accept()");
+                    stop = true;
+                }
+                break;
+            }
+            if (!use_accept4) {
+                if (fcntl(sfd, F_SETFL, fcntl(sfd, F_GETFL) | O_NONBLOCK) < 0) {
+                    perror("setting O_NONBLOCK");
+                    close(sfd);
+                    break;
+                }
+            }
+
+            bool reject;
+            if (settings.maxconns_fast) {
+                STATS_LOCK();
+                reject = stats_state.curr_conns + stats_state.reserved_fds >= settings.maxconns - 1;
+                if (reject) {
+                    stats.rejected_conns++;
+                }
+                STATS_UNLOCK();
+            } else {
+                reject = false;
+            }
+
+            if (reject) {
+                str = "ERROR Too many open connections\r\n";
+                res = write(sfd, str, strlen(str));
+                close(sfd);
+            } else {
+                void *ssl_v = NULL;
+#ifdef TLS
+                SSL *ssl = NULL;
+                if (c->ssl_enabled) {
+                    assert(IS_TCP(c->transport) && settings.ssl_enabled);
+
+                    if (settings.ssl_ctx == NULL) {
+                        if (settings.verbose) {
+                            fprintf(stderr, "SSL context is not initialized\n");
+                        }
+                        close(sfd);
+                        break;
+                    }
+                    SSL_LOCK();
+                    ssl = SSL_new(settings.ssl_ctx);
+                    SSL_UNLOCK();
+                    if (ssl == NULL) {
+                        if (settings.verbose) {
+                            fprintf(stderr, "Failed to created the SSL object\n");
+                        }
+                        close(sfd);
+                        break;
+                    }
+                    SSL_set_fd(ssl, sfd);
+                    int ret = SSL_accept(ssl);
+                    if (ret <= 0) {
+                        int err = SSL_get_error(ssl, ret);
+                        if (err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) {
+                            if (settings.verbose) {
+                                fprintf(stderr, "SSL connection failed with error code : %d : %s\n", err, strerror(errno));
+                            }
+                            SSL_free(ssl);
+                            close(sfd);
+                            STATS_LOCK();
+                            stats.ssl_handshake_errors++;
+                            STATS_UNLOCK();
+                            break;
+                        }
+                    }
+                }
+                ssl_v = (void*) ssl;
+#endif
+
+                dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,
+                                     READ_BUFFER_CACHED, c->transport, ssl_v);
+            }
+
+            stop = true;
+            break;
+
+        case conn_waiting:
+            rbuf_release(c);
+            if (!update_event(c, EV_READ | EV_PERSIST)) {
+                if (settings.verbose > 0)
+                    fprintf(stderr, "Couldn't update event\n");
+                conn_set_state(c, conn_closing);
+                break;
+            }
+
+            conn_set_state(c, conn_read);
+            stop = true;
+            break;
+
+        case conn_read:
+            if (!IS_UDP(c->transport)) {
+                // Assign a read buffer if necessary.
+                if (!rbuf_alloc(c)) {
+                    // TODO: Some way to allow for temporary failures.
+                    conn_set_state(c, conn_closing);
+                    break;
+                }
+                res = try_read_network(c);
+            } else {
+                // UDP connections always have a static buffer.
+                res = try_read_udp(c);
+            }
+
+            switch (res) {
+            case READ_NO_DATA_RECEIVED:
+                conn_set_state(c, conn_waiting);
+                break;
+            case READ_DATA_RECEIVED:
+                conn_set_state(c, conn_parse_cmd);
+                break;
+            case READ_ERROR:
+                conn_set_state(c, conn_closing);
+                break;
+            case READ_MEMORY_ERROR: /* Failed to allocate more memory */
+                /* State already set by try_read_network */
+                break;
+            }
+            break;
+
+        case conn_parse_cmd:
+            c->noreply = false;
+            if (c->try_read_command(c) == 0) {
+                /* wee need more data! */
+                if (c->resp_head) {
+                    // Buffered responses waiting, flush in the meantime.
+                    conn_set_state(c, conn_mwrite);
+                } else {
+                    conn_set_state(c, conn_waiting);
+                }
+            }
+
+            break;
+
+        case conn_new_cmd:
+            /* Only process nreqs at a time to avoid starving other
+               connections */
+
+            --nreqs;
+            if (nreqs >= 0) {
+                reset_cmd_handler(c);
+            } else if (c->resp_head) {
+                // flush response pipe on yield.
+                conn_set_state(c, conn_mwrite);
+            } else {
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.conn_yields++;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+                if (c->rbytes > 0) {
+                    /* We have already read in data into the input buffer,
+                       so libevent will most likely not signal read events
+                       on the socket (unless more data is available. As a
+                       hack we should just put in a request to write data,
+                       because that should be possible ;-)
+                    */
+                    if (!update_event(c, EV_WRITE | EV_PERSIST)) {
+                        if (settings.verbose > 0)
+                            fprintf(stderr, "Couldn't update event\n");
+                        conn_set_state(c, conn_closing);
+                        break;
+                    }
+                }
+                stop = true;
+            }
+            break;
+
+        case conn_nread:
+            if (c->rlbytes == 0) {
+                complete_nread(c);
+                break;
+            }
+
+            /* Check if rbytes < 0, to prevent crash */
+            if (c->rlbytes < 0) {
+                if (settings.verbose) {
+                    fprintf(stderr, "Invalid rlbytes to read: len %d\n", c->rlbytes);
+                }
+                conn_set_state(c, conn_closing);
+                break;
+            }
+
+            if ((((item *)c->item)->it_flags & ITEM_CHUNKED) == 0) {
+                /* first check if we have leftovers in the conn_read buffer */
+                if (c->rbytes > 0) {
+                    int tocopy = c->rbytes > c->rlbytes ? c->rlbytes : c->rbytes;
+                    memmove(c->ritem, c->rcurr, tocopy);
+                    c->ritem += tocopy;
+                    c->rlbytes -= tocopy;
+                    c->rcurr += tocopy;
+                    c->rbytes -= tocopy;
+                    if (c->rlbytes == 0) {
+                        break;
+                    }
+                }
+
+                /*  now try reading from the socket */
+                res = c->read(c, c->ritem, c->rlbytes);
+                if (res > 0) {
+                    pthread_mutex_lock(&c->thread->stats.mutex);
+                    c->thread->stats.bytes_read += res;
+                    pthread_mutex_unlock(&c->thread->stats.mutex);
+                    if (c->rcurr == c->ritem) {
+                        c->rcurr += res;
+                    }
+                    c->ritem += res;
+                    c->rlbytes -= res;
+                    break;
+                }
+            } else {
+                res = read_into_chunked_item(c);
+                if (res > 0)
+                    break;
+            }
+
+            if (res == 0) { /* end of stream */
+                conn_set_state(c, conn_closing);
+                break;
+            }
+
+            if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+                if (!update_event(c, EV_READ | EV_PERSIST)) {
+                    if (settings.verbose > 0)
+                        fprintf(stderr, "Couldn't update event\n");
+                    conn_set_state(c, conn_closing);
+                    break;
+                }
+                stop = true;
+                break;
+            }
+
+            /* Memory allocation failure */
+            if (res == -2) {
+                out_of_memory(c, "SERVER_ERROR Out of memory during read");
+                c->sbytes = c->rlbytes;
+                conn_set_state(c, conn_swallow);
+                // Ensure this flag gets cleared. It gets killed on conn_new()
+                // so any conn_closing is fine, calling complete_nread is
+                // fine. This swallow semms to be the only other case.
+                c->set_stale = false;
+                c->mset_res = false;
+                break;
+            }
+            /* otherwise we have a real error, on which we close the connection */
+            if (settings.verbose > 0) {
+                fprintf(stderr, "Failed to read, and not due to blocking:\n"
+                        "errno: %d %s \n"
+                        "rcurr=%lx ritem=%lx rbuf=%lx rlbytes=%d rsize=%d\n",
+                        errno, strerror(errno),
+                        (long)c->rcurr, (long)c->ritem, (long)c->rbuf,
+                        (int)c->rlbytes, (int)c->rsize);
+            }
+            conn_set_state(c, conn_closing);
+            break;
+
+        case conn_swallow:
+            /* we are reading sbytes and throwing them away */
+            if (c->sbytes <= 0) {
+                conn_set_state(c, conn_new_cmd);
+                break;
+            }
+
+            /* first check if we have leftovers in the conn_read buffer */
+            if (c->rbytes > 0) {
+                int tocopy = c->rbytes > c->sbytes ? c->sbytes : c->rbytes;
+                c->sbytes -= tocopy;
+                c->rcurr += tocopy;
+                c->rbytes -= tocopy;
+                break;
+            }
+
+            /*  now try reading from the socket */
+            res = c->read(c, c->rbuf, c->rsize > c->sbytes ? c->sbytes : c->rsize);
+            if (res > 0) {
+                pthread_mutex_lock(&c->thread->stats.mutex);
+                c->thread->stats.bytes_read += res;
+                pthread_mutex_unlock(&c->thread->stats.mutex);
+                c->sbytes -= res;
+                break;
+            }
+            if (res == 0) { /* end of stream */
+                conn_set_state(c, conn_closing);
+                break;
+            }
+            if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+                if (!update_event(c, EV_READ | EV_PERSIST)) {
+                    if (settings.verbose > 0)
+                        fprintf(stderr, "Couldn't update event\n");
+                    conn_set_state(c, conn_closing);
+                    break;
+                }
+                stop = true;
+                break;
+            }
+            /* otherwise we have a real error, on which we close the connection */
+            if (settings.verbose > 0)
+                fprintf(stderr, "Failed to read, and not due to blocking\n");
+            conn_set_state(c, conn_closing);
+            break;
+
+        case conn_write:
+        case conn_mwrite:
+#ifdef EXTSTORE
+            /* have side IO's that must process before transmit() can run.
+             * remove the connection from the worker thread and dispatch the
+             * IO queue
+             */
+            if (c->io_wrapleft) {
+                assert(c->io_queued == false);
+                assert(c->io_wraplist != NULL);
+                // TODO: create proper state for this condition
+                conn_set_state(c, conn_watch);
+                event_del(&c->event);
+                c->io_queued = true;
+                extstore_submit(c->thread->storage, &c->io_wraplist->io);
+                stop = true;
+                break;
+            }
+#endif
+            switch (!IS_UDP(c->transport) ? transmit(c) : transmit_udp(c)) {
+            case TRANSMIT_COMPLETE:
+                if (c->state == conn_mwrite) {
+                    // Free up IO wraps and any half-uploaded items.
+                    conn_release_items(c);
+                    conn_set_state(c, conn_new_cmd);
+                    if (c->close_after_write) {
+                        conn_set_state(c, conn_closing);
+                    }
+                } else {
+                    if (settings.verbose > 0)
+                        fprintf(stderr, "Unexpected state %d\n", c->state);
+                    conn_set_state(c, conn_closing);
+                }
+                break;
+
+            case TRANSMIT_INCOMPLETE:
+            case TRANSMIT_HARD_ERROR:
+                break;                   /* Continue in state machine. */
+
+            case TRANSMIT_SOFT_ERROR:
+                stop = true;
+                break;
+            }
+            break;
+
+        case conn_closing:
+            if (IS_UDP(c->transport))
+                conn_cleanup(c);
+            else
+                conn_close(c);
+            stop = true;
+            break;
+
+        case conn_closed:
+            /* This only happens if dormando is an idiot. */
+            abort();
+            break;
+
+        case conn_watch:
+            /* We handed off our connection to the logger thread. */
+            stop = true;
+            break;
+        case conn_max_state:
+            assert(false);
+            break;
+        }
+    }
+
+    return;
+}
+
+void event_handler(const int fd, const short which, void *arg) {
+    conn *c;
+
+    c = (conn *)arg;
+    assert(c != NULL);
+
+    c->which = which;
+
+    /* sanity */
+    if (fd != c->sfd) {
+        if (settings.verbose > 0)
+            fprintf(stderr, "Catastrophic: event fd doesn't match conn fd!\n");
+        conn_close(c);
+        return;
+    }
+
+    drive_machine(c);
+
+    /* wait for next event */
+    return;
+}
+
+static int new_socket(struct addrinfo *ai) {
+    int sfd;
+    int flags;
+
+    if ((sfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) {
+        return -1;
+    }
+
+    if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 ||
+        fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) {
+        perror("setting O_NONBLOCK");
+        close(sfd);
+        return -1;
+    }
+    return sfd;
+}
+
+
+/*
+ * Sets a socket's send buffer size to the maximum allowed by the system.
+ */
+static void maximize_sndbuf(const int sfd) {
+    socklen_t intsize = sizeof(int);
+    int last_good = 0;
+    int min, max, avg;
+    int old_size;
+
+    /* Start with the default size. */
+    if (getsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &old_size, &intsize) != 0) {
+        if (settings.verbose > 0)
+            perror("getsockopt(SO_SNDBUF)");
+        return;
+    }
+
+    /* Binary-search for the real maximum. */
+    min = old_size;
+    max = MAX_SENDBUF_SIZE;
+
+    while (min <= max) {
+        avg = ((unsigned int)(min + max)) / 2;
+        if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (void *)&avg, intsize) == 0) {
+            last_good = avg;
+            min = avg + 1;
+        } else {
+            max = avg - 1;
+        }
+    }
+
+    if (settings.verbose > 1)
+        fprintf(stderr, "<%d send buffer was %d, now %d\n", sfd, old_size, last_good);
+}
+
+/**
+ * Create a socket and bind it to a specific port number
+ * @param interface the interface to bind to
+ * @param port the port number to bind to
+ * @param transport the transport protocol (TCP / UDP)
+ * @param portnumber_file A filepointer to write the port numbers to
+ *        when they are successfully added to the list of ports we
+ *        listen on.
+ */
+static int server_socket(const char *interface,
+                         int port,
+                         enum network_transport transport,
+                         FILE *portnumber_file, bool ssl_enabled) {
+    int sfd;
+    struct linger ling = {0, 0};
+    struct addrinfo *ai;
+    struct addrinfo *next;
+    struct addrinfo hints = { .ai_flags = AI_PASSIVE,
+                              .ai_family = AF_UNSPEC };
+    char port_buf[NI_MAXSERV];
+    int error;
+    int success = 0;
+    int flags =1;
+
+    hints.ai_socktype = IS_UDP(transport) ? SOCK_DGRAM : SOCK_STREAM;
+
+    if (port == -1) {
+        port = 0;
+    }
+    snprintf(port_buf, sizeof(port_buf), "%d", port);
+    error= getaddrinfo(interface, port_buf, &hints, &ai);
+    if (error != 0) {
+        if (error != EAI_SYSTEM)
+          fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(error));
+        else
+          perror("getaddrinfo()");
+        return 1;
+    }
+
+    for (next= ai; next; next= next->ai_next) {
+        conn *listen_conn_add;
+        if ((sfd = new_socket(next)) == -1) {
+            /* getaddrinfo can return "junk" addresses,
+             * we make sure at least one works before erroring.
+             */
+            if (errno == EMFILE) {
+                /* ...unless we're out of fds */
+                perror("server_socket");
+                exit(EX_OSERR);
+            }
+            continue;
+        }
+
+#ifdef IPV6_V6ONLY
+        if (next->ai_family == AF_INET6) {
+            error = setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &flags, sizeof(flags));
+            if (error != 0) {
+                perror("setsockopt");
+                close(sfd);
+                continue;
+            }
+        }
+#endif
+
+        setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
+        if (IS_UDP(transport)) {
+            maximize_sndbuf(sfd);
+        } else {
+            error = setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
+            if (error != 0)
+                perror("setsockopt");
+
+            error = setsockopt(sfd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
+            if (error != 0)
+                perror("setsockopt");
+
+            error = setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
+            if (error != 0)
+                perror("setsockopt");
+        }
+
+        if (bind(sfd, next->ai_addr, next->ai_addrlen) == -1) {
+            if (errno != EADDRINUSE) {
+                perror("bind()");
+                close(sfd);
+                freeaddrinfo(ai);
+                return 1;
+            }
+            close(sfd);
+            continue;
+        } else {
+            success++;
+            if (!IS_UDP(transport) && listen(sfd, settings.backlog) == -1) {
+                perror("listen()");
+                close(sfd);
+                freeaddrinfo(ai);
+                return 1;
+            }
+            if (portnumber_file != NULL &&
+                (next->ai_addr->sa_family == AF_INET ||
+                 next->ai_addr->sa_family == AF_INET6)) {
+                union {
+                    struct sockaddr_in in;
+                    struct sockaddr_in6 in6;
+                } my_sockaddr;
+                socklen_t len = sizeof(my_sockaddr);
+                if (getsockname(sfd, (struct sockaddr*)&my_sockaddr, &len)==0) {
+                    if (next->ai_addr->sa_family == AF_INET) {
+                        fprintf(portnumber_file, "%s INET: %u\n",
+                                IS_UDP(transport) ? "UDP" : "TCP",
+                                ntohs(my_sockaddr.in.sin_port));
+                    } else {
+                        fprintf(portnumber_file, "%s INET6: %u\n",
+                                IS_UDP(transport) ? "UDP" : "TCP",
+                                ntohs(my_sockaddr.in6.sin6_port));
+                    }
+                }
+            }
+        }
+
+        if (IS_UDP(transport)) {
+            int c;
+
+            for (c = 0; c < settings.num_threads_per_udp; c++) {
+                /* Allocate one UDP file descriptor per worker thread;
+                 * this allows "stats conns" to separately list multiple
+                 * parallel UDP requests in progress.
+                 *
+                 * The dispatch code round-robins new connection requests
+                 * among threads, so this is guaranteed to assign one
+                 * FD to each thread.
+                 */
+                int per_thread_fd;
+                if (c == 0) {
+                    per_thread_fd = sfd;
+                } else {
+                    per_thread_fd = dup(sfd);
+                    if (per_thread_fd < 0) {
+                        perror("Failed to duplicate file descriptor");
+                        exit(EXIT_FAILURE);
+                    }
+                }
+                dispatch_conn_new(per_thread_fd, conn_read,
+                                  EV_READ | EV_PERSIST,
+                                  UDP_READ_BUFFER_SIZE, transport, NULL);
+            }
+        } else {
+            if (!(listen_conn_add = conn_new(sfd, conn_listening,
+                                             EV_READ | EV_PERSIST, 1,
+                                             transport, main_base, NULL))) {
+                fprintf(stderr, "failed to create listening connection\n");
+                exit(EXIT_FAILURE);
+            }
+#ifdef TLS
+            listen_conn_add->ssl_enabled = ssl_enabled;
+#else
+            assert(ssl_enabled == false);
+#endif
+            listen_conn_add->next = listen_conn;
+            listen_conn = listen_conn_add;
+        }
+    }
+
+    freeaddrinfo(ai);
+
+    /* Return zero iff we detected no errors in starting up connections */
+    return success == 0;
+}
+
+static int server_sockets(int port, enum network_transport transport,
+                          FILE *portnumber_file) {
+    bool ssl_enabled = false;
+
+#ifdef TLS
+    const char *notls = "notls";
+    ssl_enabled = settings.ssl_enabled;
+#endif
+
+    if (settings.inter == NULL) {
+        return server_socket(settings.inter, port, transport, portnumber_file, ssl_enabled);
+    } else {
+        // tokenize them and bind to each one of them..
+        char *b;
+        int ret = 0;
+        char *list = strdup(settings.inter);
+
+        if (list == NULL) {
+            fprintf(stderr, "Failed to allocate memory for parsing server interface string\n");
+            return 1;
+        }
+        for (char *p = strtok_r(list, ";,", &b);
+            p != NULL;
+            p = strtok_r(NULL, ";,", &b)) {
+            int the_port = port;
+#ifdef TLS
+            ssl_enabled = settings.ssl_enabled;
+            // "notls" option is valid only when memcached is run with SSL enabled.
+            if (strncmp(p, notls, strlen(notls)) == 0) {
+                if (!settings.ssl_enabled) {
+                    fprintf(stderr, "'notls' option is valid only when SSL is enabled\n");
+                    free(list);
+                    return 1;
+                }
+                ssl_enabled = false;
+                p += strlen(notls) + 1;
+            }
+#endif
+
+            char *h = NULL;
+            if (*p == '[') {
+                // expecting it to be an IPv6 address enclosed in []
+                // i.e. RFC3986 style recommended by RFC5952
+                char *e = strchr(p, ']');
+                if (e == NULL) {
+                    fprintf(stderr, "Invalid IPV6 address: \"%s\"", p);
+                    free(list);
+                    return 1;
+                }
+                h = ++p; // skip the opening '['
+                *e = '\0';
+                p = ++e; // skip the closing ']'
+            }
+
+            char *s = strchr(p, ':');
+            if (s != NULL) {
+                // If no more semicolons - attempt to treat as port number.
+                // Otherwise the only valid option is an unenclosed IPv6 without port, until
+                // of course there was an RFC3986 IPv6 address previously specified -
+                // in such a case there is no good option, will just send it to fail as port number.
+                if (strchr(s + 1, ':') == NULL || h != NULL) {
+                    *s = '\0';
+                    ++s;
+                    if (!safe_strtol(s, &the_port)) {
+                        fprintf(stderr, "Invalid port number: \"%s\"", s);
+                        free(list);
+                        return 1;
+                    }
+                }
+            }
+
+            if (h != NULL)
+                p = h;
+
+            if (strcmp(p, "*") == 0) {
+                p = NULL;
+            }
+            ret |= server_socket(p, the_port, transport, portnumber_file, ssl_enabled);
+        }
+        free(list);
+        return ret;
+    }
+}
+
+static int new_socket_unix(void) {
+    int sfd;
+    int flags;
+
+    if ((sfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+        perror("socket()");
+        return -1;
+    }
+
+    if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 ||
+        fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) {
+        perror("setting O_NONBLOCK");
+        close(sfd);
+        return -1;
+    }
+    return sfd;
+}
+
+static int server_socket_unix(const char *path, int access_mask) {
+    int sfd;
+    struct linger ling = {0, 0};
+    struct sockaddr_un addr;
+    struct stat tstat;
+    int flags =1;
+    int old_umask;
+
+    if (!path) {
+        return 1;
+    }
+
+    if ((sfd = new_socket_unix()) == -1) {
+        return 1;
+    }
+
+    /*
+     * Clean up a previous socket file if we left it around
+     */
+    if (lstat(path, &tstat) == 0) {
+        if (S_ISSOCK(tstat.st_mode))
+            unlink(path);
+    }
+
+    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
+    setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
+    setsockopt(sfd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
+
+    /*
+     * the memset call clears nonstandard fields in some implementations
+     * that otherwise mess things up.
+     */
+    memset(&addr, 0, sizeof(addr));
+
+    addr.sun_family = AF_UNIX;
+    strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+    assert(strcmp(addr.sun_path, path) == 0);
+    old_umask = umask( ~(access_mask&0777));
+    if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+        perror("bind()");
+        close(sfd);
+        umask(old_umask);
+        return 1;
+    }
+    umask(old_umask);
+    if (listen(sfd, settings.backlog) == -1) {
+        perror("listen()");
+        close(sfd);
+        return 1;
+    }
+    if (!(listen_conn = conn_new(sfd, conn_listening,
+                                 EV_READ | EV_PERSIST, 1,
+                                 local_transport, main_base, NULL))) {
+        fprintf(stderr, "failed to create listening connection\n");
+        exit(EXIT_FAILURE);
+    }
+
+    return 0;
+}
+
+/*
+ * We keep the current time of day in a global variable that's updated by a
+ * timer event. This saves us a bunch of time() system calls (we really only
+ * need to get the time once a second, whereas there can be tens of thousands
+ * of requests a second) and allows us to use server-start-relative timestamps
+ * rather than absolute UNIX timestamps, a space savings on systems where
+ * sizeof(time_t) > sizeof(unsigned int).
+ */
+volatile rel_time_t current_time;
+static struct event clockevent;
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+static bool monotonic = false;
+static int64_t monotonic_start;
+#endif
+
+/* libevent uses a monotonic clock when available for event scheduling. Aside
+ * from jitter, simply ticking our internal timer here is accurate enough.
+ * Note that users who are setting explicit dates for expiration times *must*
+ * ensure their clocks are correct before starting memcached. */
+static void clock_handler(const int fd, const short which, void *arg) {
+    struct timeval t = {.tv_sec = 1, .tv_usec = 0};
+    static bool initialized = false;
+
+    if (initialized) {
+        /* only delete the event if it's actually there. */
+        evtimer_del(&clockevent);
+    } else {
+        initialized = true;
+    }
+
+    // While we're here, check for hash table expansion.
+    // This function should be quick to avoid delaying the timer.
+    assoc_start_expand(stats_state.curr_items);
+    // also, if HUP'ed we need to do some maintenance.
+    // for now that's just the authfile reload.
+    if (settings.sig_hup) {
+        settings.sig_hup = false;
+
+        authfile_load(settings.auth_file);
+    }
+
+    evtimer_set(&clockevent, clock_handler, 0);
+    event_base_set(main_base, &clockevent);
+    evtimer_add(&clockevent, &t);
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+    if (monotonic) {
+        struct timespec ts;
+        if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
+            return;
+        current_time = (rel_time_t) (ts.tv_sec - monotonic_start);
+        return;
+    }
+#endif
+    {
+        struct timeval tv;
+        gettimeofday(&tv, NULL);
+        current_time = (rel_time_t) (tv.tv_sec - process_started);
+    }
+}
+
+static const char* flag_enabled_disabled(bool flag) {
+    return (flag ? "enabled" : "disabled");
+}
+
+static void verify_default(const char* param, bool condition) {
+    if (!condition) {
+        printf("Default value of [%s] has changed."
+            " Modify the help text and default value check.\n", param);
+        exit(EXIT_FAILURE);
+    }
+}
+
+static void usage(void) {
+    printf(PACKAGE " " VERSION "\n");
+    printf("-p, --port=<num>          TCP port to listen on (default: %d)\n"
+           "-U, --udp-port=<num>      UDP port to listen on (default: %d, off)\n"
+           "-s, --unix-socket=<file>  UNIX socket to listen on (disables network support)\n"
+           "-A, --enable-shutdown     enable ascii \"shutdown\" command\n"
+           "-a, --unix-mask=<mask>    access mask for UNIX socket, in octal (default: %o)\n"
+           "-l, --listen=<addr>       interface to listen on (default: INADDR_ANY)\n"
+#ifdef TLS
+           "                          if TLS/SSL is enabled, 'notls' prefix can be used to\n"
+           "                          disable for specific listeners (-l notls:<ip>:<port>) \n"
+#endif
+           "-d, --daemon              run as a daemon\n"
+           "-r, --enable-coredumps    maximize core file limit\n"
+           "-u, --user=<user>         assume identity of <username> (only when run as root)\n"
+           "-m, --memory-limit=<num>  item memory in megabytes (default: %lu)\n"
+           "-M, --disable-evictions   return error on memory exhausted instead of evicting\n"
+           "-c, --conn-limit=<num>    max simultaneous connections (default: %d)\n"
+           "-k, --lock-memory         lock down all paged memory\n"
+           "-v, --verbose             verbose (print errors/warnings while in event loop)\n"
+           "-vv                       very verbose (also print client commands/responses)\n"
+           "-vvv                      extremely verbose (internal state transitions)\n"
+           "-h, --help                print this help and exit\n"
+           "-i, --license             print memcached and libevent license\n"
+           "-V, --version             print version and exit\n"
+           "-P, --pidfile=<file>      save PID in <file>, only used with -d option\n"
+           "-f, --slab-growth-factor=<num> chunk size growth factor (default: %2.2f)\n"
+           "-n, --slab-min-size=<bytes> min space used for key+value+flags (default: %d)\n",
+           settings.port, settings.udpport, settings.access, (unsigned long) settings.maxbytes / (1 << 20),
+           settings.maxconns, settings.factor, settings.chunk_size);
+    verify_default("udp-port",settings.udpport == 0);
+    printf("-L, --enable-largepages  try to use large memory pages (if available)\n");
+    printf("-D <char>     Use <char> as the delimiter between key prefixes and IDs.\n"
+           "              This is used for per-prefix stats reporting. The default is\n"
+           "              \"%c\" (colon). If this option is specified, stats collection\n"
+           "              is turned on automatically; if not, then it may be turned on\n"
+           "              by sending the \"stats detail on\" command to the server.\n",
+           settings.prefix_delimiter);
+    printf("-t, --threads=<num>       number of threads to use (default: %d)\n", settings.num_threads);
+    printf("-R, --max-reqs-per-event  maximum number of requests per event, limits the\n"
+           "                          requests processed per connection to prevent \n"
+           "                          starvation (default: %d)\n", settings.reqs_per_event);
+    printf("-C, --disable-cas         disable use of CAS\n");
+    printf("-b, --listen-backlog=<num> set the backlog queue limit (default: %d)\n", settings.backlog);
+    printf("-B, --protocol=<name>     protocol - one of ascii, binary, or auto (default: %s)\n",
+           prot_text(settings.binding_protocol));
+    printf("-I, --max-item-size=<num> adjusts max item size\n"
+           "                          (default: %dm, min: %dk, max: %dm)\n",
+           settings.item_size_max/ (1 << 20), ITEM_SIZE_MAX_LOWER_LIMIT / (1 << 10),  ITEM_SIZE_MAX_UPPER_LIMIT / (1 << 20));
+#ifdef ENABLE_SASL
+    printf("-S, --enable-sasl         turn on Sasl authentication\n");
+#endif
+    printf("-F, --disable-flush-all   disable flush_all command\n");
+    printf("-X, --disable-dumping     disable stats cachedump and lru_crawler metadump\n");
+    printf("-W  --disable-watch       disable watch commands (live logging)\n");
+    printf("-Y, --auth-file=<file>    (EXPERIMENTAL) enable ASCII protocol authentication. format:\n"
+           "                          user:pass\\nuser2:pass2\\n\n");
+    printf("-e, --memory-file=<file>  (EXPERIMENTAL) mmap a file for item memory.\n"
+           "                          use only in ram disks or persistent memory mounts!\n"
+           "                          enables restartable cache (stop with SIGUSR1)\n");
+#ifdef TLS
+    printf("-Z, --enable-ssl          enable TLS/SSL\n");
+#endif
+    printf("-o, --extended            comma separated list of extended options\n"
+           "                          most options have a 'no_' prefix to disable\n"
+           "   - maxconns_fast:       immediately close new connections after limit (default: %s)\n"
+           "   - hashpower:           an integer multiplier for how large the hash\n"
+           "                          table should be. normally grows at runtime. (default starts at: %d)\n"
+           "                          set based on \"STAT hash_power_level\"\n"
+           "   - tail_repair_time:    time in seconds for how long to wait before\n"
+           "                          forcefully killing LRU tail item.\n"
+           "                          disabled by default; very dangerous option.\n"
+           "   - hash_algorithm:      the hash table algorithm\n"
+           "                          default is murmur3 hash. options: jenkins, murmur3\n"
+           "   - no_lru_crawler:      disable LRU Crawler background thread.\n"
+           "   - lru_crawler_sleep:   microseconds to sleep between items\n"
+           "                          default is %d.\n"
+           "   - lru_crawler_tocrawl: max items to crawl per slab per run\n"
+           "                          default is %u (unlimited)\n",
+           flag_enabled_disabled(settings.maxconns_fast), settings.hashpower_init,
+           settings.lru_crawler_sleep, settings.lru_crawler_tocrawl);
+    printf("   - resp_obj_mem_limit:  limit in megabytes for connection response objects.\n"
+           "                          do not adjust unless you have high (100k+) conn. limits.\n"
+           "                          0 means unlimited (default: %u)\n"
+           "   - read_buf_mem_limit:  limit in megabytes for connection read buffers.\n"
+           "                          do not adjust unless you have high (100k+) conn. limits.\n"
+           "                          0 means unlimited (default: %u)\n",
+           settings.resp_obj_mem_limit,
+           settings.read_buf_mem_limit);
+    verify_default("resp_obj_mem_limit", settings.resp_obj_mem_limit == 0);
+    verify_default("read_buf_mem_limit", settings.read_buf_mem_limit == 0);
+    printf("   - no_lru_maintainer:   disable new LRU system + background thread.\n"
+           "   - hot_lru_pct:         pct of slab memory to reserve for hot lru.\n"
+           "                          (requires lru_maintainer, default pct: %d)\n"
+           "   - warm_lru_pct:        pct of slab memory to reserve for warm lru.\n"
+           "                          (requires lru_maintainer, default pct: %d)\n"
+           "   - hot_max_factor:      items idle > cold lru age * drop from hot lru. (default: %.2f)\n"
+           "   - warm_max_factor:     items idle > cold lru age * this drop from warm. (default: %.2f)\n"
+           "   - temporary_ttl:       TTL's below get separate LRU, can't be evicted.\n"
+           "                          (requires lru_maintainer, default: %d)\n"
+           "   - idle_timeout:        timeout for idle connections. (default: %d, no timeout)\n",
+           settings.hot_lru_pct, settings.warm_lru_pct, settings.hot_max_factor, settings.warm_max_factor,
+           settings.temporary_ttl, settings.idle_timeout);
+    printf("   - slab_chunk_max:      (EXPERIMENTAL) maximum slab size in kilobytes. use extreme care. (default: %d)\n"
+           "   - watcher_logbuf_size: size in kilobytes of per-watcher write buffer. (default: %u)\n"
+           "   - worker_logbuf_size:  size in kilobytes of per-worker-thread buffer\n"
+           "                          read by background thread, then written to watchers. (default: %u)\n"
+           "   - track_sizes:         enable dynamic reports for 'stats sizes' command.\n"
+           "   - no_hashexpand:       disables hash table expansion (dangerous)\n"
+           "   - modern:              enables options which will be default in future.\n"
+           "                          currently: nothing\n"
+           "   - no_modern:           uses defaults of previous major version (1.4.x)\n",
+           settings.slab_chunk_size_max / (1 << 10), settings.logger_watcher_buf_size / (1 << 10),
+           settings.logger_buf_size / (1 << 10));
+    verify_default("tail_repair_time", settings.tail_repair_time == TAIL_REPAIR_TIME_DEFAULT);
+    verify_default("lru_crawler_tocrawl", settings.lru_crawler_tocrawl == 0);
+    verify_default("idle_timeout", settings.idle_timeout == 0);
+#ifdef HAVE_DROP_PRIVILEGES
+    printf("   - drop_privileges:     enable dropping extra syscall privileges\n"
+           "   - no_drop_privileges:  disable drop_privileges in case it causes issues with\n"
+           "                          some customisation.\n"
+           "                          (default is no_drop_privileges)\n");
+    verify_default("drop_privileges", !settings.drop_privileges);
+#ifdef MEMCACHED_DEBUG
+    printf("   - relaxed_privileges:  running tests requires extra privileges. (default: %s)\n",
+           flag_enabled_disabled(settings.relaxed_privileges));
+#endif
+#endif
+#ifdef EXTSTORE
+    printf("\n   - External storage (ext_*) related options (see: https://memcached.org/extstore)\n");
+    printf("   - ext_path:            file to write to for external storage.\n"
+           "                          ie: ext_path=/mnt/d1/extstore:1G\n"
+           "   - ext_page_size:       size in megabytes of storage pages. (default: %u)\n"
+           "   - ext_wbuf_size:       size in megabytes of page write buffers. (default: %u)\n"
+           "   - ext_threads:         number of IO threads to run. (default: %u)\n"
+           "   - ext_item_size:       store items larger than this (bytes, default %u)\n"
+           "   - ext_item_age:        store items idle at least this long (seconds, default: no age limit)\n"
+           "   - ext_low_ttl:         consider TTLs lower than this specially (default: %u)\n"
+           "   - ext_drop_unread:     don't re-write unread values during compaction (default: %s)\n"
+           "   - ext_recache_rate:    recache an item every N accesses (default: %u)\n"
+           "   - ext_compact_under:   compact when fewer than this many free pages\n"
+           "                          (default: 1/4th of the assigned storage)\n"
+           "   - ext_drop_under:      drop COLD items when fewer than this many free pages\n"
+           "                          (default: 1/4th of the assigned storage)\n"
+           "   - ext_max_frag:        max page fragmentation to tolerate (default: %.2f)\n"
+           "   - slab_automove_freeratio: ratio of memory to hold free as buffer.\n"
+           "                          (see doc/storage.txt for more info, default: %.3f)\n",
+           settings.ext_page_size / (1 << 20), settings.ext_wbuf_size / (1 << 20), settings.ext_io_threadcount,
+           settings.ext_item_size, settings.ext_low_ttl,
+           flag_enabled_disabled(settings.ext_drop_unread), settings.ext_recache_rate,
+           settings.ext_max_frag, settings.slab_automove_freeratio);
+    verify_default("ext_item_age", settings.ext_item_age == UINT_MAX);
+#endif
+#ifdef TLS
+    printf("   - ssl_chain_cert:      certificate chain file in PEM format\n"
+           "   - ssl_key:             private key, if not part of the -ssl_chain_cert\n"
+           "   - ssl_keyformat:       private key format (PEM, DER or ENGINE) (default: PEM)\n");
+    printf("   - ssl_verify_mode:     peer certificate verification mode, default is 0(None).\n"
+           "                          valid values are 0(None), 1(Request), 2(Require)\n"
+           "                          or 3(Once)\n");
+    printf("   - ssl_ciphers:         specify cipher list to be used\n"
+           "   - ssl_ca_cert:         PEM format file of acceptable client CA's\n"
+           "   - ssl_wbuf_size:       size in kilobytes of per-connection SSL output buffer\n"
+           "                          (default: %u)\n", settings.ssl_wbuf_size / (1 << 10));
+    printf("   - ssl_session_cache:   enable server-side SSL session cache, to support session\n"
+           "                          resumption\n");
+    verify_default("ssl_keyformat", settings.ssl_keyformat == SSL_FILETYPE_PEM);
+    verify_default("ssl_verify_mode", settings.ssl_verify_mode == SSL_VERIFY_NONE);
+#endif
+    return;
+}
+
+static void usage_license(void) {
+    printf(PACKAGE " " VERSION "\n\n");
+    printf(
+    "Copyright (c) 2003, Danga Interactive, Inc. <http://www.danga.com/>\n"
+    "All rights reserved.\n"
+    "\n"
+    "Redistribution and use in source and binary forms, with or without\n"
+    "modification, are permitted provided that the following conditions are\n"
+    "met:\n"
+    "\n"
+    "    * Redistributions of source code must retain the above copyright\n"
+    "notice, this list of conditions and the following disclaimer.\n"
+    "\n"
+    "    * Redistributions in binary form must reproduce the above\n"
+    "copyright notice, this list of conditions and the following disclaimer\n"
+    "in the documentation and/or other materials provided with the\n"
+    "distribution.\n"
+    "\n"
+    "    * Neither the name of the Danga Interactive nor the names of its\n"
+    "contributors may be used to endorse or promote products derived from\n"
+    "this software without specific prior written permission.\n"
+    "\n"
+    "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n"
+    "\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n"
+    "LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n"
+    "A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n"
+    "OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n"
+    "SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n"
+    "LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
+    "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"
+    "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
+    "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"
+    "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
+    "\n"
+    "\n"
+    "This product includes software developed by Niels Provos.\n"
+    "\n"
+    "[ libevent ]\n"
+    "\n"
+    "Copyright 2000-2003 Niels Provos <provos@citi.umich.edu>\n"
+    "All rights reserved.\n"
+    "\n"
+    "Redistribution and use in source and binary forms, with or without\n"
+    "modification, are permitted provided that the following conditions\n"
+    "are met:\n"
+    "1. Redistributions of source code must retain the above copyright\n"
+    "   notice, this list of conditions and the following disclaimer.\n"
+    "2. Redistributions in binary form must reproduce the above copyright\n"
+    "   notice, this list of conditions and the following disclaimer in the\n"
+    "   documentation and/or other materials provided with the distribution.\n"
+    "3. All advertising materials mentioning features or use of this software\n"
+    "   must display the following acknowledgement:\n"
+    "      This product includes software developed by Niels Provos.\n"
+    "4. The name of the author may not be used to endorse or promote products\n"
+    "   derived from this software without specific prior written permission.\n"
+    "\n"
+    "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
+    "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
+    "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"
+    "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"
+    "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"
+    "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
+    "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"
+    "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
+    "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n"
+    "THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
+    );
+
+    return;
+}
+
+static void save_pid(const char *pid_file) {
+    FILE *fp;
+    if (access(pid_file, F_OK) == 0) {
+        if ((fp = fopen(pid_file, "r")) != NULL) {
+            char buffer[1024];
+            if (fgets(buffer, sizeof(buffer), fp) != NULL) {
+                unsigned int pid;
+                if (safe_strtoul(buffer, &pid) && kill((pid_t)pid, 0) == 0) {
+                    fprintf(stderr, "WARNING: The pid file contained the following (running) pid: %u\n", pid);
+                }
+            }
+            fclose(fp);
+        }
+    }
+
+    /* Create the pid file first with a temporary name, then
+     * atomically move the file to the real name to avoid a race with
+     * another process opening the file to read the pid, but finding
+     * it empty.
+     */
+    char tmp_pid_file[1024];
+    snprintf(tmp_pid_file, sizeof(tmp_pid_file), "%s.tmp", pid_file);
+
+    if ((fp = fopen(tmp_pid_file, "w")) == NULL) {
+        vperror("Could not open the pid file %s for writing", tmp_pid_file);
+        return;
+    }
+
+    fprintf(fp,"%ld\n", (long)getpid());
+    if (fclose(fp) == -1) {
+        vperror("Could not close the pid file %s", tmp_pid_file);
+    }
+
+    if (rename(tmp_pid_file, pid_file) != 0) {
+        vperror("Could not rename the pid file from %s to %s",
+                tmp_pid_file, pid_file);
+    }
+}
+
+static void remove_pidfile(const char *pid_file) {
+  if (pid_file == NULL)
+      return;
+
+  if (unlink(pid_file) != 0) {
+      vperror("Could not remove the pid file %s", pid_file);
+  }
+
+}
+
+static void sig_handler(const int sig) {
+    printf("Signal handled: %s.\n", strsignal(sig));
+    exit(EXIT_SUCCESS);
+}
+
+static void sighup_handler(const int sig) {
+    settings.sig_hup = true;
+}
+
+static void sig_usrhandler(const int sig) {
+    printf("Graceful shutdown signal handled: %s.\n", strsignal(sig));
+    stop_main_loop = true;
+}
+
+#ifndef HAVE_SIGIGNORE
+static int sigignore(int sig) {
+    struct sigaction sa = { .sa_handler = SIG_IGN, .sa_flags = 0 };
+
+    if (sigemptyset(&sa.sa_mask) == -1 || sigaction(sig, &sa, 0) == -1) {
+        return -1;
+    }
+    return 0;
+}
+#endif
+
+
+/*
+ * On systems that supports multiple page sizes we may reduce the
+ * number of TLB-misses by using the biggest available page size
+ */
+static int enable_large_pages(void) {
+#if defined(HAVE_GETPAGESIZES) && defined(HAVE_MEMCNTL)
+    int ret = -1;
+    size_t sizes[32];
+    int avail = getpagesizes(sizes, 32);
+    if (avail != -1) {
+        size_t max = sizes[0];
+        struct memcntl_mha arg = {0};
+        int ii;
+
+        for (ii = 1; ii < avail; ++ii) {
+            if (max < sizes[ii]) {
+                max = sizes[ii];
+            }
+        }
+
+        arg.mha_flags   = 0;
+        arg.mha_pagesize = max;
+        arg.mha_cmd = MHA_MAPSIZE_BSSBRK;
+
+        if (memcntl(0, 0, MC_HAT_ADVISE, (caddr_t)&arg, 0, 0) == -1) {
+            fprintf(stderr, "Failed to set large pages: %s\n",
+                    strerror(errno));
+            fprintf(stderr, "Will use default page size\n");
+        } else {
+            ret = 0;
+        }
+    } else {
+        fprintf(stderr, "Failed to get supported pagesizes: %s\n",
+                strerror(errno));
+        fprintf(stderr, "Will use default page size\n");
+    }
+
+    return ret;
+#elif defined(__linux__) && defined(MADV_HUGEPAGE)
+    /* check if transparent hugepages is compiled into the kernel */
+    struct stat st;
+    int ret = stat("/sys/kernel/mm/transparent_hugepage/enabled", &st);
+    if (ret || !(st.st_mode & S_IFREG)) {
+        fprintf(stderr, "Transparent huge pages support not detected.\n");
+        fprintf(stderr, "Will use default page size.\n");
+        return -1;
+    }
+    return 0;
+#elif defined(__FreeBSD__)
+    int spages;
+    size_t spagesl = sizeof(spages);
+
+    if (sysctlbyname("vm.pmap.pg_ps_enabled", &spages,
+    &spagesl, NULL, 0) != 0) {
+        fprintf(stderr, "Could not evaluate the presence of superpages features.");
+        return -1;
+    }
+    if (spages != 1) {
+        fprintf(stderr, "Superpages support not detected.\n");
+        fprintf(stderr, "Will use default page size.\n");
+        return -1;
+    }
+    return 0;
+#else
+    return -1;
+#endif
+}
+
+/**
+ * Do basic sanity check of the runtime environment
+ * @return true if no errors found, false if we can't use this env
+ */
+static bool sanitycheck(void) {
+    /* One of our biggest problems is old and bogus libevents */
+    const char *ever = event_get_version();
+    if (ever != NULL) {
+        if (strncmp(ever, "1.", 2) == 0) {
+            /* Require at least 1.3 (that's still a couple of years old) */
+            if (('0' <= ever[2] && ever[2] < '3') && !isdigit(ever[3])) {
+                fprintf(stderr, "You are using libevent %s.\nPlease upgrade to"
+                        " a more recent version (1.3 or newer)\n",
+                        event_get_version());
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+static bool _parse_slab_sizes(char *s, uint32_t *slab_sizes) {
+    char *b = NULL;
+    uint32_t size = 0;
+    int i = 0;
+    uint32_t last_size = 0;
+
+    if (strlen(s) < 1)
+        return false;
+
+    for (char *p = strtok_r(s, "-", &b);
+         p != NULL;
+         p = strtok_r(NULL, "-", &b)) {
+        if (!safe_strtoul(p, &size) || size < settings.chunk_size
+             || size > settings.slab_chunk_size_max) {
+            fprintf(stderr, "slab size %u is out of valid range\n", size);
+            return false;
+        }
+        if (last_size >= size) {
+            fprintf(stderr, "slab size %u cannot be lower than or equal to a previous class size\n", size);
+            return false;
+        }
+        if (size <= last_size + CHUNK_ALIGN_BYTES) {
+            fprintf(stderr, "slab size %u must be at least %d bytes larger than previous class\n",
+                    size, CHUNK_ALIGN_BYTES);
+            return false;
+        }
+        slab_sizes[i++] = size;
+        last_size = size;
+        if (i >= MAX_NUMBER_OF_SLAB_CLASSES-1) {
+            fprintf(stderr, "too many slab classes specified\n");
+            return false;
+        }
+    }
+
+    slab_sizes[i] = 0;
+    return true;
+}
+
+
+
+// We need to remember a combination of configuration settings and global
+// state for restart viability and resumption of internal services.
+// Compared to the number of tunables and state values, relatively little
+// does need to be remembered.
+// Time is the hardest; we have to assume the sys clock is correct and re-sync for
+// the lost time after restart.
+static int _mc_meta_save_cb(const char *tag, void *ctx, void *data) {
+    struct _mc_meta_data *meta_to_save = (struct _mc_meta_data *)data;
+
+    // Settings to remember.
+    // TODO: should get a version of version which is numeric, else
+    // comparisons for compat reasons are difficult.
+    // it may be possible to punt on this for now; since we can test for the
+    // absense of another key... such as the new numeric version.
+    //restart_set_kv(ctx, "version", "%s", VERSION);
+    // We hold the original factor or subopts _string_
+    // it can be directly compared without roundtripping through floats or
+    // serializing/deserializing the long options list.
+    restart_set_kv(ctx, "slab_config", "%s", meta_to_save->slab_config);
+    restart_set_kv(ctx, "maxbytes", "%llu", (unsigned long long) settings.maxbytes);
+    restart_set_kv(ctx, "chunk_size", "%d", settings.chunk_size);
+    restart_set_kv(ctx, "item_size_max", "%d", settings.item_size_max);
+    restart_set_kv(ctx, "slab_chunk_size_max", "%d", settings.slab_chunk_size_max);
+    restart_set_kv(ctx, "slab_page_size", "%d", settings.slab_page_size);
+    restart_set_kv(ctx, "use_cas", "%s", settings.use_cas ? "true" : "false");
+    restart_set_kv(ctx, "slab_reassign", "%s", settings.slab_reassign ? "true" : "false");
+
+    // Online state to remember.
+
+    // current time is tough. we need to rely on the clock being correct to
+    // pull the delta between stop and start times. we also need to know the
+    // delta between start time and now to restore monotonic clocks.
+    // for non-monotonic clocks (some OS?), process_started is the only
+    // important one.
+    restart_set_kv(ctx, "current_time", "%u", current_time);
+    // types are great until... this. some systems time_t could be big, but
+    // I'm assuming never negative.
+    restart_set_kv(ctx, "process_started", "%llu", (unsigned long long) process_started);
+    {
+        struct timeval tv;
+        gettimeofday(&tv, NULL);
+        restart_set_kv(ctx, "stop_time", "%lu", tv.tv_sec);
+    }
+
+    // Might as well just fetch the next CAS value to use than tightly
+    // coupling the internal variable into the restart system.
+    restart_set_kv(ctx, "current_cas", "%llu", (unsigned long long) get_cas_id());
+    restart_set_kv(ctx, "oldest_cas", "%llu", (unsigned long long) settings.oldest_cas);
+    restart_set_kv(ctx, "logger_gid", "%llu", logger_get_gid());
+    restart_set_kv(ctx, "hashpower", "%u", stats_state.hash_power_level);
+    // NOTE: oldest_live is a rel_time_t, which aliases for unsigned int.
+    // should future proof this with a 64bit upcast, or fetch value from a
+    // converter function/macro?
+    restart_set_kv(ctx, "oldest_live", "%u", settings.oldest_live);
+    // TODO: use uintptr_t etc? is it portable enough?
+    restart_set_kv(ctx, "mmap_oldbase", "%p", meta_to_save->mmap_base);
+
+    return 0;
+}
+
+// We must see at least this number of checked lines. Else empty/missing lines
+// could cause a false-positive.
+// TODO: Once crc32'ing of the metadata file is done this could be ensured better by
+// the restart module itself (crc32 + count of lines must match on the
+// backend)
+#define RESTART_REQUIRED_META 17
+
+// With this callback we make a decision on if the current configuration
+// matches up enough to allow reusing the cache.
+// We also re-load important runtime information.
+static int _mc_meta_load_cb(const char *tag, void *ctx, void *data) {
+    struct _mc_meta_data *meta_candidate = (struct _mc_meta_data *)data;
+    char *key;
+    char *val;
+    int reuse_mmap = 0;
+    meta_candidate->process_started = 0;
+    meta_candidate->time_delta = 0;
+    meta_candidate->current_time = 0;
+    int lines_seen = 0;
+
+    // TODO: not sure this is any better than just doing an if/else tree with
+    // strcmp's...
+    enum {
+        R_MMAP_OLDBASE = 0,
+        R_MAXBYTES,
+        R_CHUNK_SIZE,
+        R_ITEM_SIZE_MAX,
+        R_SLAB_CHUNK_SIZE_MAX,
+        R_SLAB_PAGE_SIZE,
+        R_SLAB_CONFIG,
+        R_USE_CAS,
+        R_SLAB_REASSIGN,
+        R_CURRENT_CAS,
+        R_OLDEST_CAS,
+        R_OLDEST_LIVE,
+        R_LOGGER_GID,
+        R_CURRENT_TIME,
+        R_STOP_TIME,
+        R_PROCESS_STARTED,
+        R_HASHPOWER,
+    };
+
+    const char *opts[] = {
+        [R_MMAP_OLDBASE] = "mmap_oldbase",
+        [R_MAXBYTES] = "maxbytes",
+        [R_CHUNK_SIZE] = "chunk_size",
+        [R_ITEM_SIZE_MAX] = "item_size_max",
+        [R_SLAB_CHUNK_SIZE_MAX] = "slab_chunk_size_max",
+        [R_SLAB_PAGE_SIZE] = "slab_page_size",
+        [R_SLAB_CONFIG] = "slab_config",
+        [R_USE_CAS] = "use_cas",
+        [R_SLAB_REASSIGN] = "slab_reassign",
+        [R_CURRENT_CAS] = "current_cas",
+        [R_OLDEST_CAS] = "oldest_cas",
+        [R_OLDEST_LIVE] = "oldest_live",
+        [R_LOGGER_GID] = "logger_gid",
+        [R_CURRENT_TIME] = "current_time",
+        [R_STOP_TIME] = "stop_time",
+        [R_PROCESS_STARTED] = "process_started",
+        [R_HASHPOWER] = "hashpower",
+        NULL
+    };
+
+    while (restart_get_kv(ctx, &key, &val) == RESTART_OK) {
+        int type = 0;
+        int32_t val_int = 0;
+        uint32_t val_uint = 0;
+        int64_t bigval_int = 0;
+        uint64_t bigval_uint = 0;
+
+        while (opts[type] != NULL && strcmp(key, opts[type]) != 0) {
+            type++;
+        }
+        if (opts[type] == NULL) {
+            fprintf(stderr, "[restart] unknown/unhandled key: %s\n", key);
+            continue;
+        }
+        lines_seen++;
+
+        // helper for any boolean checkers.
+        bool val_bool = false;
+        bool is_bool = true;
+        if (strcmp(val, "false") == 0) {
+            val_bool = false;
+        } else if (strcmp(val, "true") == 0) {
+            val_bool = true;
+        } else {
+            is_bool = false;
+        }
+
+        switch (type) {
+        case R_MMAP_OLDBASE:
+            if (!safe_strtoull_hex(val, &meta_candidate->old_base)) {
+                fprintf(stderr, "[restart] failed to parse %s: %s\n", key, val);
+                reuse_mmap = -1;
+            }
+            break;
+        case R_MAXBYTES:
+            if (!safe_strtoll(val, &bigval_int) || settings.maxbytes != bigval_int) {
+                reuse_mmap = -1;
+            }
+            break;
+        case R_CHUNK_SIZE:
+            if (!safe_strtol(val, &val_int) || settings.chunk_size != val_int) {
+                reuse_mmap = -1;
+            }
+            break;
+        case R_ITEM_SIZE_MAX:
+            if (!safe_strtol(val, &val_int) || settings.item_size_max != val_int) {
+                reuse_mmap = -1;
+            }
+            break;
+        case R_SLAB_CHUNK_SIZE_MAX:
+            if (!safe_strtol(val, &val_int) || settings.slab_chunk_size_max != val_int) {
+                reuse_mmap = -1;
+            }
+            break;
+        case R_SLAB_PAGE_SIZE:
+            if (!safe_strtol(val, &val_int) || settings.slab_page_size != val_int) {
+                reuse_mmap = -1;
+            }
+            break;
+        case R_SLAB_CONFIG:
+            if (strcmp(val, meta_candidate->slab_config) != 0) {
+                reuse_mmap = -1;
+            }
+            break;
+        case R_USE_CAS:
+            if (!is_bool || settings.use_cas != val_bool) {
+                reuse_mmap = -1;
+            }
+            break;
+        case R_SLAB_REASSIGN:
+            if (!is_bool || settings.slab_reassign != val_bool) {
+                reuse_mmap = -1;
+            }
+            break;
+        case R_CURRENT_CAS:
+            // FIXME: do we need to fail if these values _aren't_ found?
+            if (!safe_strtoull(val, &bigval_uint)) {
+                reuse_mmap = -1;
+            } else {
+                set_cas_id(bigval_uint);
+            }
+            break;
+        case R_OLDEST_CAS:
+            if (!safe_strtoull(val, &bigval_uint)) {
+                reuse_mmap = -1;
+            } else {
+                settings.oldest_cas = bigval_uint;
+            }
+            break;
+        case R_OLDEST_LIVE:
+            if (!safe_strtoul(val, &val_uint)) {
+                reuse_mmap = -1;
+            } else {
+                settings.oldest_live = val_uint;
+            }
+            break;
+        case R_LOGGER_GID:
+            if (!safe_strtoull(val, &bigval_uint)) {
+                reuse_mmap = -1;
+            } else {
+                logger_set_gid(bigval_uint);
+            }
+            break;
+        case R_PROCESS_STARTED:
+            if (!safe_strtoull(val, &bigval_uint)) {
+                reuse_mmap = -1;
+            } else {
+                meta_candidate->process_started = bigval_uint;
+            }
+            break;
+        case R_CURRENT_TIME:
+            if (!safe_strtoul(val, &val_uint)) {
+                reuse_mmap = -1;
+            } else {
+                meta_candidate->current_time = val_uint;
+            }
+            break;
+        case R_STOP_TIME:
+            if (!safe_strtoll(val, &bigval_int)) {
+                reuse_mmap = -1;
+            } else {
+                struct timeval t;
+                gettimeofday(&t, NULL);
+                meta_candidate->time_delta = t.tv_sec - bigval_int;
+                // clock has done something crazy.
+                // there are _lots_ of ways the clock can go wrong here, but
+                // this is a safe sanity check since there's nothing else we
+                // can realistically do.
+                if (meta_candidate->time_delta <= 0) {
+                    reuse_mmap = -1;
+                }
+            }
+            break;
+        case R_HASHPOWER:
+            if (!safe_strtoul(val, &val_uint)) {
+                reuse_mmap = -1;
+            } else {
+                settings.hashpower_init = val_uint;
+            }
+            break;
+        default:
+            fprintf(stderr, "[restart] unhandled key: %s\n", key);
+        }
+
+        if (reuse_mmap != 0) {
+            fprintf(stderr, "[restart] restart incompatible due to setting for [%s] [old value: %s]\n", key, val);
+            break;
+        }
+    }
+
+    if (lines_seen < RESTART_REQUIRED_META) {
+        fprintf(stderr, "[restart] missing some metadata lines\n");
+        reuse_mmap = -1;
+    }
+
+    return reuse_mmap;
+}
+
+int init_memcached(int argc, char ** argv, size_t size_in_mb, uint32_t nthreads){
+    int c;
+    bool lock_memory = false;
+    bool do_daemonize = false;
+    bool preallocate = false;
+    int maxcore = 0;
+    char *username = NULL;
+    char *pid_file = NULL;
+    //moved to file scope
+//    char *memory_file = NULL;
+    struct passwd *pw;
+    struct rlimit rlim;
+    char *buf;
+    char unit = '\0';
+    int size_max = 0;
+    int retval = EXIT_SUCCESS;
+    bool protocol_specified = false;
+    bool tcp_specified = false;
+    bool udp_specified = false;
+    bool start_lru_maintainer = true;
+    bool start_lru_crawler = true;
+    bool start_assoc_maint = true;
+    enum hashfunc_type hash_type = MURMUR3_HASH;
+    uint32_t tocrawl;
+    uint32_t slab_sizes[MAX_NUMBER_OF_SLAB_CLASSES];
+    bool use_slab_sizes = false;
+
+    char *slab_sizes_unparsed = NULL;
+    bool slab_chunk_size_changed = false;
+    // struct for restart code. Initialized up here so we can curry
+    // important settings to save or validate.
+    // moved to file scope
+    meta = malloc(sizeof(struct _mc_meta_data));
+    meta->slab_config = NULL;
+#ifdef EXTSTORE
+    void *storage = NULL;
+    struct extstore_conf_file *storage_file = NULL;
+    struct extstore_conf ext_cf;
+#endif
+    char *subopts, *subopts_orig;
+    char *subopts_value;
+    enum {
+        MAXCONNS_FAST = 0,
+        HASHPOWER_INIT,
+        NO_HASHEXPAND,
+        SLAB_REASSIGN,
+        SLAB_AUTOMOVE,
+        SLAB_AUTOMOVE_RATIO,
+        SLAB_AUTOMOVE_WINDOW,
+        TAIL_REPAIR_TIME,
+        HASH_ALGORITHM,
+        LRU_CRAWLER,
+        LRU_CRAWLER_SLEEP,
+        LRU_CRAWLER_TOCRAWL,
+        LRU_MAINTAINER,
+        HOT_LRU_PCT,
+        WARM_LRU_PCT,
+        HOT_MAX_FACTOR,
+        WARM_MAX_FACTOR,
+        TEMPORARY_TTL,
+        IDLE_TIMEOUT,
+        WATCHER_LOGBUF_SIZE,
+        WORKER_LOGBUF_SIZE,
+        SLAB_SIZES,
+        SLAB_CHUNK_MAX,
+        TRACK_SIZES,
+        NO_INLINE_ASCII_RESP,
+        MODERN,
+        NO_MODERN,
+        NO_CHUNKED_ITEMS,
+        NO_SLAB_REASSIGN,
+        NO_SLAB_AUTOMOVE,
+        NO_MAXCONNS_FAST,
+        INLINE_ASCII_RESP,
+        NO_LRU_CRAWLER,
+        NO_LRU_MAINTAINER,
+        NO_DROP_PRIVILEGES,
+        DROP_PRIVILEGES,
+        RESP_OBJ_MEM_LIMIT,
+        READ_BUF_MEM_LIMIT,
+#ifdef TLS
+        SSL_CERT,
+        SSL_KEY,
+        SSL_VERIFY_MODE,
+        SSL_KEYFORM,
+        SSL_CIPHERS,
+        SSL_CA_CERT,
+        SSL_WBUF_SIZE,
+        SSL_SESSION_CACHE,
+#endif
+#ifdef MEMCACHED_DEBUG
+        RELAXED_PRIVILEGES,
+#endif
+#ifdef EXTSTORE
+        EXT_PAGE_SIZE,
+        EXT_WBUF_SIZE,
+        EXT_THREADS,
+        EXT_IO_DEPTH,
+        EXT_PATH,
+        EXT_ITEM_SIZE,
+        EXT_ITEM_AGE,
+        EXT_LOW_TTL,
+        EXT_RECACHE_RATE,
+        EXT_COMPACT_UNDER,
+        EXT_DROP_UNDER,
+        EXT_MAX_FRAG,
+        EXT_DROP_UNREAD,
+        SLAB_AUTOMOVE_FREERATIO,
+#endif
+    };
+    char *const subopts_tokens[] = {
+        [MAXCONNS_FAST] = "maxconns_fast",
+        [HASHPOWER_INIT] = "hashpower",
+        [NO_HASHEXPAND] = "no_hashexpand",
+        [SLAB_REASSIGN] = "slab_reassign",
+        [SLAB_AUTOMOVE] = "slab_automove",
+        [SLAB_AUTOMOVE_RATIO] = "slab_automove_ratio",
+        [SLAB_AUTOMOVE_WINDOW] = "slab_automove_window",
+        [TAIL_REPAIR_TIME] = "tail_repair_time",
+        [HASH_ALGORITHM] = "hash_algorithm",
+        [LRU_CRAWLER] = "lru_crawler",
+        [LRU_CRAWLER_SLEEP] = "lru_crawler_sleep",
+        [LRU_CRAWLER_TOCRAWL] = "lru_crawler_tocrawl",
+        [LRU_MAINTAINER] = "lru_maintainer",
+        [HOT_LRU_PCT] = "hot_lru_pct",
+        [WARM_LRU_PCT] = "warm_lru_pct",
+        [HOT_MAX_FACTOR] = "hot_max_factor",
+        [WARM_MAX_FACTOR] = "warm_max_factor",
+        [TEMPORARY_TTL] = "temporary_ttl",
+        [IDLE_TIMEOUT] = "idle_timeout",
+        [WATCHER_LOGBUF_SIZE] = "watcher_logbuf_size",
+        [WORKER_LOGBUF_SIZE] = "worker_logbuf_size",
+        [SLAB_SIZES] = "slab_sizes",
+        [SLAB_CHUNK_MAX] = "slab_chunk_max",
+        [TRACK_SIZES] = "track_sizes",
+        [NO_INLINE_ASCII_RESP] = "no_inline_ascii_resp",
+        [MODERN] = "modern",
+        [NO_MODERN] = "no_modern",
+        [NO_CHUNKED_ITEMS] = "no_chunked_items",
+        [NO_SLAB_REASSIGN] = "no_slab_reassign",
+        [NO_SLAB_AUTOMOVE] = "no_slab_automove",
+        [NO_MAXCONNS_FAST] = "no_maxconns_fast",
+        [INLINE_ASCII_RESP] = "inline_ascii_resp",
+        [NO_LRU_CRAWLER] = "no_lru_crawler",
+        [NO_LRU_MAINTAINER] = "no_lru_maintainer",
+        [NO_DROP_PRIVILEGES] = "no_drop_privileges",
+        [DROP_PRIVILEGES] = "drop_privileges",
+        [RESP_OBJ_MEM_LIMIT] = "resp_obj_mem_limit",
+        [READ_BUF_MEM_LIMIT] = "read_buf_mem_limit",
+#ifdef TLS
+        [SSL_CERT] = "ssl_chain_cert",
+        [SSL_KEY] = "ssl_key",
+        [SSL_VERIFY_MODE] = "ssl_verify_mode",
+        [SSL_KEYFORM] = "ssl_keyformat",
+        [SSL_CIPHERS] = "ssl_ciphers",
+        [SSL_CA_CERT] = "ssl_ca_cert",
+        [SSL_WBUF_SIZE] = "ssl_wbuf_size",
+        [SSL_SESSION_CACHE] = "ssl_session_cache",
+#endif
+#ifdef MEMCACHED_DEBUG
+        [RELAXED_PRIVILEGES] = "relaxed_privileges",
+#endif
+#ifdef EXTSTORE
+        [EXT_PAGE_SIZE] = "ext_page_size",
+        [EXT_WBUF_SIZE] = "ext_wbuf_size",
+        [EXT_THREADS] = "ext_threads",
+        [EXT_IO_DEPTH] = "ext_io_depth",
+        [EXT_PATH] = "ext_path",
+        [EXT_ITEM_SIZE] = "ext_item_size",
+        [EXT_ITEM_AGE] = "ext_item_age",
+        [EXT_LOW_TTL] = "ext_low_ttl",
+        [EXT_RECACHE_RATE] = "ext_recache_rate",
+        [EXT_COMPACT_UNDER] = "ext_compact_under",
+        [EXT_DROP_UNDER] = "ext_drop_under",
+        [EXT_MAX_FRAG] = "ext_max_frag",
+        [EXT_DROP_UNREAD] = "ext_drop_unread",
+        [SLAB_AUTOMOVE_FREERATIO] = "slab_automove_freeratio",
+#endif
+        NULL
+    };
+
+    if (!sanitycheck()) {
+        free(meta);
+        return EX_OSERR;
+    }
+
+    /* handle SIGINT, SIGTERM */
+    signal(SIGINT, sig_handler);
+    signal(SIGTERM, sig_handler);
+    signal(SIGHUP, sighup_handler);
+    signal(SIGUSR1, sig_usrhandler);
+
+    /* init settings */
+    settings_init(size_in_mb);
+    settings.num_threads = nthreads;
+    verify_default("hash_algorithm", hash_type == MURMUR3_HASH);
+#ifdef EXTSTORE
+    settings.ext_item_size = 512;
+    settings.ext_item_age = UINT_MAX;
+    settings.ext_low_ttl = 0;
+    settings.ext_recache_rate = 2000;
+    settings.ext_max_frag = 0.8;
+    settings.ext_drop_unread = false;
+    settings.ext_wbuf_size = 1024 * 1024 * 4;
+    settings.ext_compact_under = 0;
+    settings.ext_drop_under = 0;
+    settings.slab_automove_freeratio = 0.01;
+    settings.ext_page_size = 1024 * 1024 * 64;
+    settings.ext_io_threadcount = 1;
+    ext_cf.page_size = settings.ext_page_size;
+    ext_cf.wbuf_size = settings.ext_wbuf_size;
+    ext_cf.io_threadcount = settings.ext_io_threadcount;
+    ext_cf.io_depth = 1;
+    ext_cf.page_buckets = 4;
+    ext_cf.wbuf_count = ext_cf.page_buckets;
+#endif
+
+    /* Run regardless of initializing it later */
+    init_lru_maintainer();
+
+    /* set stderr non-buffering (for running under, say, daemontools) */
+    setbuf(stderr, NULL);
+
+    char *shortopts =
+          "a:"  /* access mask for unix socket */
+          "A"   /* enable admin shutdown command */
+          "Z"   /* enable SSL */
+          "p:"  /* TCP port number to listen on */
+          "s:"  /* unix socket path to listen on */
+          "U:"  /* UDP port number to listen on */
+          "m:"  /* max memory to use for items in megabytes */
+          "M"   /* return error on memory exhausted */
+          "c:"  /* max simultaneous connections */
+          "k"   /* lock down all paged memory */
+          "hiV" /* help, licence info, version */
+          "r"   /* maximize core file limit */
+          "v"   /* verbose */
+          "d"   /* daemon mode */
+          "l:"  /* interface to listen on */
+          "u:"  /* user identity to run as */
+          "P:"  /* save PID in file */
+          "f:"  /* factor? */
+          "n:"  /* minimum space allocated for key+value+flags */
+          "t:"  /* threads */
+          "D:"  /* prefix delimiter? */
+          "L"   /* Large memory pages */
+          "R:"  /* max requests per event */
+          "C"   /* Disable use of CAS */
+          "b:"  /* backlog queue limit */
+          "B:"  /* Binding protocol */
+          "I:"  /* Max item size */
+          "S"   /* Sasl ON */
+          "F"   /* Disable flush_all */
+          "X"   /* Disable dump commands */
+          "W"   /* Disable watch commands */
+          "Y:"   /* Enable token auth */
+          "e:"  /* mmap path for external item memory */
+          "o:"  /* Extended generic options */
+          ;
+
+    /* process arguments */
+#ifdef HAVE_GETOPT_LONG
+    const struct option longopts[] = {
+        {"unix-mask", required_argument, 0, 'a'},
+        {"enable-shutdown", no_argument, 0, 'A'},
+        {"enable-ssl", no_argument, 0, 'Z'},
+        {"port", required_argument, 0, 'p'},
+        {"unix-socket", required_argument, 0, 's'},
+        {"udp-port", required_argument, 0, 'U'},
+        {"memory-limit", required_argument, 0, 'm'},
+        {"disable-evictions", no_argument, 0, 'M'},
+        {"conn-limit", required_argument, 0, 'c'},
+        {"lock-memory", no_argument, 0, 'k'},
+        {"help", no_argument, 0, 'h'},
+        {"license", no_argument, 0, 'i'},
+        {"version", no_argument, 0, 'V'},
+        {"enable-coredumps", no_argument, 0, 'r'},
+        {"verbose", optional_argument, 0, 'v'},
+        {"daemon", no_argument, 0, 'd'},
+        {"listen", required_argument, 0, 'l'},
+        {"user", required_argument, 0, 'u'},
+        {"pidfile", required_argument, 0, 'P'},
+        {"slab-growth-factor", required_argument, 0, 'f'},
+        {"slab-min-size", required_argument, 0, 'n'},
+        {"threads", required_argument, 0, 't'},
+        {"enable-largepages", no_argument, 0, 'L'},
+        {"max-reqs-per-event", required_argument, 0, 'R'},
+        {"disable-cas", no_argument, 0, 'C'},
+        {"listen-backlog", required_argument, 0, 'b'},
+        {"protocol", required_argument, 0, 'B'},
+        {"max-item-size", required_argument, 0, 'I'},
+        {"enable-sasl", no_argument, 0, 'S'},
+        {"disable-flush-all", no_argument, 0, 'F'},
+        {"disable-dumping", no_argument, 0, 'X'},
+        {"disable-watch", no_argument, 0, 'W'},
+        {"auth-file", required_argument, 0, 'Y'},
+        {"memory-file", required_argument, 0, 'e'},
+        {"extended", required_argument, 0, 'o'},
+        {0, 0, 0, 0}
+    };
+    int optindex;
+    while (-1 != (c = getopt_long(argc, argv, shortopts,
+                    longopts, &optindex))) {
+#else
+    while (-1 != (c = getopt(argc, argv, shortopts))) {
+#endif
+        switch (c) {
+        case 'A':
+            /* enables "shutdown" command */
+            settings.shutdown_command = true;
+            break;
+        case 'Z':
+            /* enable secure communication*/
+#ifdef TLS
+            settings.ssl_enabled = true;
+#else
+            fprintf(stderr, "This server is not built with TLS support.\n");
+            exit(EX_USAGE);
+#endif
+            break;
+        case 'a':
+            /* access for unix domain socket, as octal mask (like chmod)*/
+            settings.access= strtol(optarg,NULL,8);
+            break;
+        case 'U':
+            settings.udpport = atoi(optarg);
+            udp_specified = true;
+            break;
+        case 'p':
+            settings.port = atoi(optarg);
+            tcp_specified = true;
+            break;
+        case 's':
+            settings.socketpath = optarg;
+            break;
+        case 'm':
+            settings.maxbytes = ((size_t)atoi(optarg)) * 1024 * 1024;
+            break;
+        case 'M':
+            settings.evict_to_free = 0;
+            break;
+        case 'c':
+            settings.maxconns = atoi(optarg);
+            if (settings.maxconns <= 0) {
+                fprintf(stderr, "Maximum connections must be greater than 0\n");
+                return 1;
+            }
+            break;
+        case 'h':
+            usage();
+            exit(EXIT_SUCCESS);
+        case 'i':
+            usage_license();
+            exit(EXIT_SUCCESS);
+        case 'V':
+            printf(PACKAGE " " VERSION "\n");
+            exit(EXIT_SUCCESS);
+        case 'k':
+            lock_memory = true;
+            break;
+        case 'v':
+            settings.verbose++;
+            break;
+        case 'l':
+            if (settings.inter != NULL) {
+                if (strstr(settings.inter, optarg) != NULL) {
+                    break;
+                }
+                size_t len = strlen(settings.inter) + strlen(optarg) + 2;
+                char *p = malloc(len);
+                if (p == NULL) {
+                    fprintf(stderr, "Failed to allocate memory\n");
+                    return 1;
+                }
+                snprintf(p, len, "%s,%s", settings.inter, optarg);
+                free(settings.inter);
+                settings.inter = p;
+            } else {
+                settings.inter= strdup(optarg);
+            }
+            break;
+        case 'd':
+            do_daemonize = true;
+            break;
+        case 'r':
+            maxcore = 1;
+            break;
+        case 'R':
+            settings.reqs_per_event = atoi(optarg);
+            if (settings.reqs_per_event == 0) {
+                fprintf(stderr, "Number of requests per event must be greater than 0\n");
+                return 1;
+            }
+            break;
+        case 'u':
+            username = optarg;
+            break;
+        case 'P':
+            pid_file = optarg;
+            break;
+        case 'e':
+            memory_file = optarg;
+            break;
+        case 'f':
+            settings.factor = atof(optarg);
+            if (settings.factor <= 1.0) {
+                fprintf(stderr, "Factor must be greater than 1\n");
+                return 1;
+            }
+            meta->slab_config = strdup(optarg);
+            break;
+        case 'n':
+            settings.chunk_size = atoi(optarg);
+            if (settings.chunk_size == 0) {
+                fprintf(stderr, "Chunk size must be greater than 0\n");
+                return 1;
+            }
+            break;
+        case 't':
+            settings.num_threads = atoi(optarg);
+            if (settings.num_threads <= 0) {
+                fprintf(stderr, "Number of threads must be greater than 0\n");
+                return 1;
+            }
+            /* There're other problems when you get above 64 threads.
+             * In the future we should portably detect # of cores for the
+             * default.
+             */
+            if (settings.num_threads > 64) {
+                fprintf(stderr, "WARNING: Setting a high number of worker"
+                                "threads is not recommended.\n"
+                                " Set this value to the number of cores in"
+                                " your machine or less.\n");
+            }
+            break;
+        case 'D':
+            if (! optarg || ! optarg[0]) {
+                fprintf(stderr, "No delimiter specified\n");
+                return 1;
+            }
+            settings.prefix_delimiter = optarg[0];
+            settings.detail_enabled = 1;
+            break;
+        case 'L' :
+            if (enable_large_pages() == 0) {
+                preallocate = true;
+            } else {
+                fprintf(stderr, "Cannot enable large pages on this system\n"
+                    "(There is no support as of this version)\n");
+                return 1;
+            }
+            break;
+        case 'C' :
+            settings.use_cas = false;
+            break;
+        case 'b' :
+            settings.backlog = atoi(optarg);
+            break;
+        case 'B':
+            protocol_specified = true;
+            if (strcmp(optarg, "auto") == 0) {
+                settings.binding_protocol = negotiating_prot;
+            } else if (strcmp(optarg, "binary") == 0) {
+                settings.binding_protocol = binary_prot;
+            } else if (strcmp(optarg, "ascii") == 0) {
+                settings.binding_protocol = ascii_prot;
+            } else {
+                fprintf(stderr, "Invalid value for binding protocol: %s\n"
+                        " -- should be one of auto, binary, or ascii\n", optarg);
+                exit(EX_USAGE);
+            }
+            break;
+        case 'I':
+            buf = strdup(optarg);
+            unit = buf[strlen(buf)-1];
+            if (unit == 'k' || unit == 'm' ||
+                unit == 'K' || unit == 'M') {
+                buf[strlen(buf)-1] = '\0';
+                size_max = atoi(buf);
+                if (unit == 'k' || unit == 'K')
+                    size_max *= 1024;
+                if (unit == 'm' || unit == 'M')
+                    size_max *= 1024 * 1024;
+                settings.item_size_max = size_max;
+            } else {
+                settings.item_size_max = atoi(buf);
+            }
+            free(buf);
+            break;
+        case 'S': /* set Sasl authentication to true. Default is false */
+#ifndef ENABLE_SASL
+            fprintf(stderr, "This server is not built with SASL support.\n");
+            exit(EX_USAGE);
+#endif
+            settings.sasl = true;
+            break;
+       case 'F' :
+            settings.flush_enabled = false;
+            break;
+       case 'X' :
+            settings.dump_enabled = false;
+            break;
+       case 'W' :
+            settings.watch_enabled = false;
+            break;
+       case 'Y' :
+            // dupe the file path now just in case the options get mangled.
+            settings.auth_file = strdup(optarg);
+            break;
+        case 'o': /* It's sub-opts time! */
+            subopts_orig = subopts = strdup(optarg); /* getsubopt() changes the original args */
+
+            while (*subopts != '\0') {
+
+            switch (getsubopt(&subopts, subopts_tokens, &subopts_value)) {
+            case MAXCONNS_FAST:
+                settings.maxconns_fast = true;
+                break;
+            case HASHPOWER_INIT:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing numeric argument for hashpower\n");
+                    return 1;
+                }
+                settings.hashpower_init = atoi(subopts_value);
+                if (settings.hashpower_init < 12) {
+                    fprintf(stderr, "Initial hashtable multiplier of %d is too low\n",
+                        settings.hashpower_init);
+                    return 1;
+                } else if (settings.hashpower_init > 32) {
+                    fprintf(stderr, "Initial hashtable multiplier of %d is too high\n"
+                        "Choose a value based on \"STAT hash_power_level\" from a running instance\n",
+                        settings.hashpower_init);
+                    return 1;
+                }
+                break;
+            case NO_HASHEXPAND:
+                start_assoc_maint = false;
+                break;
+            case SLAB_REASSIGN:
+                settings.slab_reassign = true;
+                break;
+            case SLAB_AUTOMOVE:
+                if (subopts_value == NULL) {
+                    settings.slab_automove = 1;
+                    break;
+                }
+                settings.slab_automove = atoi(subopts_value);
+                if (settings.slab_automove < 0 || settings.slab_automove > 2) {
+                    fprintf(stderr, "slab_automove must be between 0 and 2\n");
+                    return 1;
+                }
+                break;
+            case SLAB_AUTOMOVE_RATIO:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing slab_automove_ratio argument\n");
+                    return 1;
+                }
+                settings.slab_automove_ratio = atof(subopts_value);
+                if (settings.slab_automove_ratio <= 0 || settings.slab_automove_ratio > 1) {
+                    fprintf(stderr, "slab_automove_ratio must be > 0 and < 1\n");
+                    return 1;
+                }
+                break;
+            case SLAB_AUTOMOVE_WINDOW:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing slab_automove_window argument\n");
+                    return 1;
+                }
+                settings.slab_automove_window = atoi(subopts_value);
+                if (settings.slab_automove_window < 3) {
+                    fprintf(stderr, "slab_automove_window must be > 2\n");
+                    return 1;
+                }
+                break;
+            case TAIL_REPAIR_TIME:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing numeric argument for tail_repair_time\n");
+                    return 1;
+                }
+                settings.tail_repair_time = atoi(subopts_value);
+                if (settings.tail_repair_time < 10) {
+                    fprintf(stderr, "Cannot set tail_repair_time to less than 10 seconds\n");
+                    return 1;
+                }
+                break;
+            case HASH_ALGORITHM:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing hash_algorithm argument\n");
+                    return 1;
+                };
+                if (strcmp(subopts_value, "jenkins") == 0) {
+                    hash_type = JENKINS_HASH;
+                } else if (strcmp(subopts_value, "murmur3") == 0) {
+                    hash_type = MURMUR3_HASH;
+                } else {
+                    fprintf(stderr, "Unknown hash_algorithm option (jenkins, murmur3)\n");
+                    return 1;
+                }
+                break;
+            case LRU_CRAWLER:
+                start_lru_crawler = true;
+                break;
+            case LRU_CRAWLER_SLEEP:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing lru_crawler_sleep value\n");
+                    return 1;
+                }
+                settings.lru_crawler_sleep = atoi(subopts_value);
+                if (settings.lru_crawler_sleep > 1000000 || settings.lru_crawler_sleep < 0) {
+                    fprintf(stderr, "LRU crawler sleep must be between 0 and 1 second\n");
+                    return 1;
+                }
+                break;
+            case LRU_CRAWLER_TOCRAWL:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing lru_crawler_tocrawl value\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &tocrawl)) {
+                    fprintf(stderr, "lru_crawler_tocrawl takes a numeric 32bit value\n");
+                    return 1;
+                }
+                settings.lru_crawler_tocrawl = tocrawl;
+                break;
+            case LRU_MAINTAINER:
+                start_lru_maintainer = true;
+                settings.lru_segmented = true;
+                break;
+            case HOT_LRU_PCT:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing hot_lru_pct argument\n");
+                    return 1;
+                }
+                settings.hot_lru_pct = atoi(subopts_value);
+                if (settings.hot_lru_pct < 1 || settings.hot_lru_pct >= 80) {
+                    fprintf(stderr, "hot_lru_pct must be > 1 and < 80\n");
+                    return 1;
+                }
+                break;
+            case WARM_LRU_PCT:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing warm_lru_pct argument\n");
+                    return 1;
+                }
+                settings.warm_lru_pct = atoi(subopts_value);
+                if (settings.warm_lru_pct < 1 || settings.warm_lru_pct >= 80) {
+                    fprintf(stderr, "warm_lru_pct must be > 1 and < 80\n");
+                    return 1;
+                }
+                break;
+            case HOT_MAX_FACTOR:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing hot_max_factor argument\n");
+                    return 1;
+                }
+                settings.hot_max_factor = atof(subopts_value);
+                if (settings.hot_max_factor <= 0) {
+                    fprintf(stderr, "hot_max_factor must be > 0\n");
+                    return 1;
+                }
+                break;
+            case WARM_MAX_FACTOR:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing warm_max_factor argument\n");
+                    return 1;
+                }
+                settings.warm_max_factor = atof(subopts_value);
+                if (settings.warm_max_factor <= 0) {
+                    fprintf(stderr, "warm_max_factor must be > 0\n");
+                    return 1;
+                }
+                break;
+            case TEMPORARY_TTL:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing temporary_ttl argument\n");
+                    return 1;
+                }
+                settings.temp_lru = true;
+                settings.temporary_ttl = atoi(subopts_value);
+                break;
+            case IDLE_TIMEOUT:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing numeric argument for idle_timeout\n");
+                    return 1;
+                }
+                settings.idle_timeout = atoi(subopts_value);
+                break;
+            case WATCHER_LOGBUF_SIZE:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing watcher_logbuf_size argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.logger_watcher_buf_size)) {
+                    fprintf(stderr, "could not parse argument to watcher_logbuf_size\n");
+                    return 1;
+                }
+                settings.logger_watcher_buf_size *= 1024; /* kilobytes */
+                break;
+            case WORKER_LOGBUF_SIZE:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing worker_logbuf_size argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.logger_buf_size)) {
+                    fprintf(stderr, "could not parse argument to worker_logbuf_size\n");
+                    return 1;
+                }
+                settings.logger_buf_size *= 1024; /* kilobytes */
+            case SLAB_SIZES:
+                slab_sizes_unparsed = strdup(subopts_value);
+                break;
+            case SLAB_CHUNK_MAX:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing slab_chunk_max argument\n");
+                }
+                if (!safe_strtol(subopts_value, &settings.slab_chunk_size_max)) {
+                    fprintf(stderr, "could not parse argument to slab_chunk_max\n");
+                }
+                slab_chunk_size_changed = true;
+                break;
+            case TRACK_SIZES:
+                item_stats_sizes_init();
+                break;
+            case NO_INLINE_ASCII_RESP:
+                break;
+            case INLINE_ASCII_RESP:
+                break;
+            case NO_CHUNKED_ITEMS:
+                settings.slab_chunk_size_max = settings.slab_page_size;
+                break;
+            case NO_SLAB_REASSIGN:
+                settings.slab_reassign = false;
+                break;
+            case NO_SLAB_AUTOMOVE:
+                settings.slab_automove = 0;
+                break;
+            case NO_MAXCONNS_FAST:
+                settings.maxconns_fast = false;
+                break;
+            case NO_LRU_CRAWLER:
+                settings.lru_crawler = false;
+                start_lru_crawler = false;
+                break;
+            case NO_LRU_MAINTAINER:
+                start_lru_maintainer = false;
+                settings.lru_segmented = false;
+                break;
+#ifdef TLS
+            case SSL_CERT:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ssl_chain_cert argument\n");
+                    return 1;
+                }
+                settings.ssl_chain_cert = strdup(subopts_value);
+                break;
+            case SSL_KEY:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ssl_key argument\n");
+                    return 1;
+                }
+                settings.ssl_key = strdup(subopts_value);
+                break;
+            case SSL_VERIFY_MODE:
+            {
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ssl_verify_mode argument\n");
+                    return 1;
+                }
+                int verify  = 0;
+                if (!safe_strtol(subopts_value, &verify)) {
+                    fprintf(stderr, "could not parse argument to ssl_verify_mode\n");
+                    return 1;
+                }
+                switch(verify) {
+                    case 0:
+                        settings.ssl_verify_mode = SSL_VERIFY_NONE;
+                        break;
+                    case 1:
+                        settings.ssl_verify_mode = SSL_VERIFY_PEER;
+                        break;
+                    case 2:
+                        settings.ssl_verify_mode = SSL_VERIFY_PEER |
+                                                    SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+                        break;
+                    case 3:
+                        settings.ssl_verify_mode = SSL_VERIFY_PEER |
+                                                    SSL_VERIFY_FAIL_IF_NO_PEER_CERT |
+                                                    SSL_VERIFY_CLIENT_ONCE;
+                        break;
+                    default:
+                        fprintf(stderr, "Invalid ssl_verify_mode. Use help to see valid options.\n");
+                        return 1;
+                }
+                break;
+            }
+            case SSL_KEYFORM:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ssl_keyformat argument\n");
+                    return 1;
+                }
+                if (!safe_strtol(subopts_value, &settings.ssl_keyformat)) {
+                    fprintf(stderr, "could not parse argument to ssl_keyformat\n");
+                    return 1;
+                }
+                break;
+            case SSL_CIPHERS:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ssl_ciphers argument\n");
+                    return 1;
+                }
+                settings.ssl_ciphers = strdup(subopts_value);
+                break;
+            case SSL_CA_CERT:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ssl_ca_cert argument\n");
+                    return 1;
+                }
+                settings.ssl_ca_cert = strdup(subopts_value);
+                break;
+            case SSL_WBUF_SIZE:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ssl_wbuf_size argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.ssl_wbuf_size)) {
+                    fprintf(stderr, "could not parse argument to ssl_wbuf_size\n");
+                    return 1;
+                }
+                settings.ssl_wbuf_size *= 1024; /* kilobytes */
+                break;
+            case SSL_SESSION_CACHE:
+                settings.ssl_session_cache = true;
+                break;
+#endif
+#ifdef EXTSTORE
+            case EXT_PAGE_SIZE:
+                if (storage_file) {
+                    fprintf(stderr, "Must specify ext_page_size before any ext_path arguments\n");
+                    return 1;
+                }
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_page_size argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &ext_cf.page_size)) {
+                    fprintf(stderr, "could not parse argument to ext_page_size\n");
+                    return 1;
+                }
+                ext_cf.page_size *= 1024 * 1024; /* megabytes */
+                break;
+            case EXT_WBUF_SIZE:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_wbuf_size argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &ext_cf.wbuf_size)) {
+                    fprintf(stderr, "could not parse argument to ext_wbuf_size\n");
+                    return 1;
+                }
+                ext_cf.wbuf_size *= 1024 * 1024; /* megabytes */
+                settings.ext_wbuf_size = ext_cf.wbuf_size;
+                break;
+            case EXT_THREADS:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_threads argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &ext_cf.io_threadcount)) {
+                    fprintf(stderr, "could not parse argument to ext_threads\n");
+                    return 1;
+                }
+                break;
+            case EXT_IO_DEPTH:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_io_depth argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &ext_cf.io_depth)) {
+                    fprintf(stderr, "could not parse argument to ext_io_depth\n");
+                    return 1;
+                }
+                break;
+            case EXT_ITEM_SIZE:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_item_size argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.ext_item_size)) {
+                    fprintf(stderr, "could not parse argument to ext_item_size\n");
+                    return 1;
+                }
+                break;
+            case EXT_ITEM_AGE:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_item_age argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.ext_item_age)) {
+                    fprintf(stderr, "could not parse argument to ext_item_age\n");
+                    return 1;
+                }
+                break;
+            case EXT_LOW_TTL:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_low_ttl argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.ext_low_ttl)) {
+                    fprintf(stderr, "could not parse argument to ext_low_ttl\n");
+                    return 1;
+                }
+                break;
+            case EXT_RECACHE_RATE:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_recache_rate argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.ext_recache_rate)) {
+                    fprintf(stderr, "could not parse argument to ext_recache_rate\n");
+                    return 1;
+                }
+                break;
+            case EXT_COMPACT_UNDER:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_compact_under argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.ext_compact_under)) {
+                    fprintf(stderr, "could not parse argument to ext_compact_under\n");
+                    return 1;
+                }
+                break;
+            case EXT_DROP_UNDER:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_drop_under argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.ext_drop_under)) {
+                    fprintf(stderr, "could not parse argument to ext_drop_under\n");
+                    return 1;
+                }
+                break;
+            case EXT_MAX_FRAG:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing ext_max_frag argument\n");
+                    return 1;
+                }
+                if (!safe_strtod(subopts_value, &settings.ext_max_frag)) {
+                    fprintf(stderr, "could not parse argument to ext_max_frag\n");
+                    return 1;
+                }
+                break;
+            case SLAB_AUTOMOVE_FREERATIO:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing slab_automove_freeratio argument\n");
+                    return 1;
+                }
+                if (!safe_strtod(subopts_value, &settings.slab_automove_freeratio)) {
+                    fprintf(stderr, "could not parse argument to slab_automove_freeratio\n");
+                    return 1;
+                }
+                break;
+            case EXT_DROP_UNREAD:
+                settings.ext_drop_unread = true;
+                break;
+            case EXT_PATH:
+                if (subopts_value) {
+                    struct extstore_conf_file *tmp = storage_conf_parse(subopts_value, ext_cf.page_size);
+                    if (tmp == NULL) {
+                        fprintf(stderr, "failed to parse ext_path argument\n");
+                        return 1;
+                    }
+                    if (storage_file != NULL) {
+                        tmp->next = storage_file;
+                    }
+                    storage_file = tmp;
+                } else {
+                    fprintf(stderr, "missing argument to ext_path, ie: ext_path=/d/file:5G\n");
+                    return 1;
+                }
+                break;
+#endif
+            case MODERN:
+                /* currently no new defaults */
+                break;
+            case NO_MODERN:
+                if (!slab_chunk_size_changed) {
+                    settings.slab_chunk_size_max = settings.slab_page_size;
+                }
+                settings.slab_reassign = false;
+                settings.slab_automove = 0;
+                settings.maxconns_fast = false;
+                settings.lru_segmented = false;
+                hash_type = JENKINS_HASH;
+                start_lru_crawler = false;
+                start_lru_maintainer = false;
+                break;
+            case NO_DROP_PRIVILEGES:
+                settings.drop_privileges = false;
+                break;
+            case DROP_PRIVILEGES:
+                settings.drop_privileges = true;
+                break;
+            case RESP_OBJ_MEM_LIMIT:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing resp_obj_mem_limit argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.resp_obj_mem_limit)) {
+                    fprintf(stderr, "could not parse argument to resp_obj_mem_limit\n");
+                    return 1;
+                }
+                settings.resp_obj_mem_limit *= 1024 * 1024; /* megabytes */
+                break;
+            case READ_BUF_MEM_LIMIT:
+                if (subopts_value == NULL) {
+                    fprintf(stderr, "Missing read_buf_mem_limit argument\n");
+                    return 1;
+                }
+                if (!safe_strtoul(subopts_value, &settings.read_buf_mem_limit)) {
+                    fprintf(stderr, "could not parse argument to read_buf_mem_limit\n");
+                    return 1;
+                }
+                settings.read_buf_mem_limit *= 1024 * 1024; /* megabytes */
+                break;
+#ifdef MEMCACHED_DEBUG
+            case RELAXED_PRIVILEGES:
+                settings.relaxed_privileges = true;
+                break;
+#endif
+            default:
+                printf("Illegal suboption \"%s\"\n", subopts_value);
+                return 1;
+            }
+
+            }
+            free(subopts_orig);
+            break;
+        default:
+            fprintf(stderr, "Illegal argument \"%c\"\n", c);
+            return 1;
+        }
+    }
+
+    if (settings.item_size_max < ITEM_SIZE_MAX_LOWER_LIMIT) {
+        fprintf(stderr, "Item max size cannot be less than 1024 bytes.\n");
+        exit(EX_USAGE);
+    }
+    if (settings.item_size_max > (settings.maxbytes / 2)) {
+        fprintf(stderr, "Cannot set item size limit higher than 1/2 of memory max.\n");
+        exit(EX_USAGE);
+    }
+    if (settings.item_size_max > (ITEM_SIZE_MAX_UPPER_LIMIT)) {
+        fprintf(stderr, "Cannot set item size limit higher than a gigabyte.\n");
+        exit(EX_USAGE);
+    }
+    if (settings.item_size_max > 1024 * 1024) {
+        if (!slab_chunk_size_changed) {
+            // Ideal new default is 16k, but needs stitching.
+            settings.slab_chunk_size_max = settings.slab_page_size / 2;
+        }
+    }
+
+    if (settings.slab_chunk_size_max > settings.item_size_max) {
+        fprintf(stderr, "slab_chunk_max (bytes: %d) cannot be larger than -I (item_size_max %d)\n",
+                settings.slab_chunk_size_max, settings.item_size_max);
+        exit(EX_USAGE);
+    }
+
+    if (settings.item_size_max % settings.slab_chunk_size_max != 0) {
+        fprintf(stderr, "-I (item_size_max: %d) must be evenly divisible by slab_chunk_max (bytes: %d)\n",
+                settings.item_size_max, settings.slab_chunk_size_max);
+        exit(EX_USAGE);
+    }
+
+    if (settings.slab_page_size % settings.slab_chunk_size_max != 0) {
+        fprintf(stderr, "slab_chunk_max (bytes: %d) must divide evenly into %d (slab_page_size)\n",
+                settings.slab_chunk_size_max, settings.slab_page_size);
+        exit(EX_USAGE);
+    }
+#ifdef EXTSTORE
+    if (storage_file) {
+        if (settings.item_size_max > ext_cf.wbuf_size) {
+            fprintf(stderr, "-I (item_size_max: %d) cannot be larger than ext_wbuf_size: %d\n",
+                settings.item_size_max, ext_cf.wbuf_size);
+            exit(EX_USAGE);
+        }
+
+        if (settings.udpport) {
+            fprintf(stderr, "Cannot use UDP with extstore enabled (-U 0 to disable)\n");
+            exit(EX_USAGE);
+        }
+    }
+#endif
+    // Reserve this for the new default. If factor size hasn't changed, use
+    // new default.
+    /*if (settings.slab_chunk_size_max == 16384 && settings.factor == 1.25) {
+        settings.factor = 1.08;
+    }*/
+
+    if (slab_sizes_unparsed != NULL) {
+        // want the unedited string for restart code.
+        char *temp = strdup(slab_sizes_unparsed);
+        if (_parse_slab_sizes(slab_sizes_unparsed, slab_sizes)) {
+            use_slab_sizes = true;
+            if (meta->slab_config) {
+                free(meta->slab_config);
+            }
+            meta->slab_config = temp;
+        } else {
+            exit(EX_USAGE);
+        }
+    } else if (!meta->slab_config) {
+        // using the default factor.
+        meta->slab_config = "1.25";
+    }
+
+    if (settings.hot_lru_pct + settings.warm_lru_pct > 80) {
+        fprintf(stderr, "hot_lru_pct + warm_lru_pct cannot be more than 80%% combined\n");
+        exit(EX_USAGE);
+    }
+
+    if (settings.temp_lru && !start_lru_maintainer) {
+        fprintf(stderr, "temporary_ttl requires lru_maintainer to be enabled\n");
+        exit(EX_USAGE);
+    }
+
+    if (hash_init(hash_type) != 0) {
+        fprintf(stderr, "Failed to initialize hash_algorithm!\n");
+        exit(EX_USAGE);
+    }
+
+    /*
+     * Use one workerthread to serve each UDP port if the user specified
+     * multiple ports
+     */
+    if (settings.inter != NULL && strchr(settings.inter, ',')) {
+        settings.num_threads_per_udp = 1;
+    } else {
+        settings.num_threads_per_udp = settings.num_threads;
+    }
+
+    if (settings.sasl) {
+        if (!protocol_specified) {
+            settings.binding_protocol = binary_prot;
+        } else {
+            if (settings.binding_protocol != binary_prot) {
+                fprintf(stderr, "ERROR: You cannot allow the ASCII protocol while using SASL.\n");
+                exit(EX_USAGE);
+            }
+        }
+
+        if (settings.udpport) {
+            fprintf(stderr, "ERROR: Cannot enable UDP while using binary SASL authentication.\n");
+            exit(EX_USAGE);
+        }
+    }
+
+    if (settings.auth_file) {
+        if (!protocol_specified) {
+            settings.binding_protocol = ascii_prot;
+        } else {
+            if (settings.binding_protocol != ascii_prot) {
+                fprintf(stderr, "ERROR: You cannot allow the BINARY protocol while using ascii authentication tokens.\n");
+                exit(EX_USAGE);
+            }
+        }
+    }
+
+    if (udp_specified && settings.udpport != 0 && !tcp_specified) {
+        settings.port = settings.udpport;
+    }
+
+
+#ifdef TLS
+    /*
+     * Setup SSL if enabled
+     */
+    if (settings.ssl_enabled) {
+        if (!settings.port) {
+            fprintf(stderr, "ERROR: You cannot enable SSL without a TCP port.\n");
+            exit(EX_USAGE);
+        }
+        // openssl init methods.
+        SSL_load_error_strings();
+        SSLeay_add_ssl_algorithms();
+        // Initiate the SSL context.
+        ssl_init();
+    }
+#endif
+
+    if (maxcore != 0) {
+        struct rlimit rlim_new;
+        /*
+         * First try raising to infinity; if that fails, try bringing
+         * the soft limit to the hard.
+         */
+        if (getrlimit(RLIMIT_CORE, &rlim) == 0) {
+            rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
+            if (setrlimit(RLIMIT_CORE, &rlim_new)!= 0) {
+                /* failed. try raising just to the old max */
+                rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max;
+                (void)setrlimit(RLIMIT_CORE, &rlim_new);
+            }
+        }
+        /*
+         * getrlimit again to see what we ended up with. Only fail if
+         * the soft limit ends up 0, because then no core files will be
+         * created at all.
+         */
+
+        if ((getrlimit(RLIMIT_CORE, &rlim) != 0) || rlim.rlim_cur == 0) {
+            fprintf(stderr, "failed to ensure corefile creation\n");
+            exit(EX_OSERR);
+        }
+    }
+
+    /*
+     * If needed, increase rlimits to allow as many connections
+     * as needed.
+     */
+
+    if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
+        fprintf(stderr, "failed to getrlimit number of files\n");
+        exit(EX_OSERR);
+    } else {
+        rlim.rlim_cur = settings.maxconns;
+        rlim.rlim_max = settings.maxconns;
+        if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
+            fprintf(stderr, "failed to set rlimit for open files. Try starting as root or requesting smaller maxconns value.\n");
+            exit(EX_OSERR);
+        }
+    }
+
+    /* lose root privileges if we have them */
+    if (getuid() == 0 || geteuid() == 0) {
+        if (username == 0 || *username == '\0') {
+            fprintf(stderr, "can't run as root without the -u switch\n");
+            exit(EX_USAGE);
+        }
+        if ((pw = getpwnam(username)) == 0) {
+            fprintf(stderr, "can't find the user %s to switch to\n", username);
+            exit(EX_NOUSER);
+        }
+        if (setgroups(0, NULL) < 0) {
+            /* setgroups may fail with EPERM, indicating we are already in a
+             * minimally-privileged state. In that case we continue. For all
+             * other failure codes we exit.
+             *
+             * Note that errno is stored here because fprintf may change it.
+             */
+            bool should_exit = errno != EPERM;
+            fprintf(stderr, "failed to drop supplementary groups: %s\n",
+                    strerror(errno));
+            if (should_exit) {
+                exit(EX_OSERR);
+            }
+        }
+        if (setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) {
+            fprintf(stderr, "failed to assume identity of user %s\n", username);
+            exit(EX_OSERR);
+        }
+    }
+
+    /* Initialize Sasl if -S was specified */
+    if (settings.sasl) {
+        init_sasl();
+    }
+
+    /* daemonize if requested */
+    /* if we want to ensure our ability to dump core, don't chdir to / */
+    if (do_daemonize) {
+        if (sigignore(SIGHUP) == -1) {
+            perror("Failed to ignore SIGHUP");
+        }
+        if (daemonize(maxcore, settings.verbose) == -1) {
+            fprintf(stderr, "failed to daemon() in order to daemonize\n");
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    /* lock paged memory if needed */
+    if (lock_memory) {
+#ifdef HAVE_MLOCKALL
+        int res = mlockall(MCL_CURRENT | MCL_FUTURE);
+        if (res != 0) {
+            fprintf(stderr, "warning: -k invalid, mlockall() failed: %s\n",
+                    strerror(errno));
+        }
+#else
+        fprintf(stderr, "warning: -k invalid, mlockall() not supported on this platform.  proceeding without.\n");
+#endif
+    }
+
+    /* initialize main thread libevent instance */
+#if defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER >= 0x02000101
+    /* If libevent version is larger/equal to 2.0.2-alpha, use newer version */
+    struct event_config *ev_config;
+    ev_config = event_config_new();
+    event_config_set_flag(ev_config, EVENT_BASE_FLAG_NOLOCK);
+    main_base = event_base_new_with_config(ev_config);
+    event_config_free(ev_config);
+#else
+    /* Otherwise, use older API */
+    main_base = event_init();
+#endif
+
+    /* Load initial auth file if required */
+    if (settings.auth_file) {
+        if (settings.udpport) {
+            fprintf(stderr, "Cannot use UDP with ascii authentication enabled (-U 0 to disable)\n");
+            exit(EX_USAGE);
+        }
+
+        switch (authfile_load(settings.auth_file)) {
+            case AUTHFILE_MISSING: // fall through.
+            case AUTHFILE_OPENFAIL:
+                vperror("Could not open authfile [%s] for reading", settings.auth_file);
+                exit(EXIT_FAILURE);
+                break;
+            case AUTHFILE_OOM:
+                fprintf(stderr, "Out of memory reading password file: %s", settings.auth_file);
+                exit(EXIT_FAILURE);
+                break;
+            case AUTHFILE_MALFORMED:
+                fprintf(stderr, "Authfile [%s] has a malformed entry. Should be 'user:password'", settings.auth_file);
+                exit(EXIT_FAILURE);
+                break;
+            case AUTHFILE_OK:
+                break;
+        }
+    }
+
+    /* initialize other stuff */
+    stats_init();
+    logger_init();
+    conn_init();
+    bool reuse_mem = false;
+    void *mem_base = NULL;
+    bool prefill = false;
+    if (memory_file != NULL) {
+        preallocate = true;
+        // Easier to manage memory if we prefill the global pool when reusing.
+        prefill = true;
+        restart_register("main", _mc_meta_load_cb, _mc_meta_save_cb, meta);
+        reuse_mem = restart_mmap_open(settings.maxbytes,
+                        memory_file,
+                        &mem_base);
+        // The "save" callback gets called when we're closing out the mmap,
+        // but we don't know what the mmap_base is until after we call open.
+        // So we pass the struct above but have to fill it in here so the
+        // data's available during the save routine.
+        meta->mmap_base = mem_base;
+        // Also, the callbacks for load() run before _open returns, so we
+        // should have the old base in 'meta' as of here.
+    }
+    // Initialize the hash table _after_ checking restart metadata.
+    // We override the hash table start argument with what was live
+    // previously, to avoid filling a huge set of items into a tiny hash
+    // table.
+    assoc_init(settings.hashpower_init);
+#ifdef EXTSTORE
+    if (storage_file && reuse_mem) {
+        fprintf(stderr, "[restart] memory restart with extstore not presently supported.\n");
+        reuse_mem = false;
+    }
+#endif
+    slabs_init(settings.maxbytes, settings.factor, preallocate,
+            use_slab_sizes ? slab_sizes : NULL, mem_base, reuse_mem);
+#ifdef EXTSTORE
+    if (storage_file) {
+        enum extstore_res eres;
+        if (settings.ext_compact_under == 0) {
+            // If changing the default fraction (4), change the help text as well.
+            settings.ext_compact_under = storage_file->page_count / 4;
+            /* Only rescues non-COLD items if below this threshold */
+            settings.ext_drop_under = storage_file->page_count / 4;
+        }
+        crc32c_init();
+        /* Init free chunks to zero. */
+        for (int x = 0; x < MAX_NUMBER_OF_SLAB_CLASSES; x++) {
+            settings.ext_free_memchunks[x] = 0;
+        }
+        storage = extstore_init(storage_file, &ext_cf, &eres);
+        if (storage == NULL) {
+            fprintf(stderr, "Failed to initialize external storage: %s\n",
+                    extstore_err(eres));
+            if (eres == EXTSTORE_INIT_OPEN_FAIL) {
+                perror("extstore open");
+            }
+            exit(EXIT_FAILURE);
+        }
+        ext_storage = storage;
+        /* page mover algorithm for extstore needs memory prefilled */
+        prefill = true;
+    }
+#endif
+
+    if (settings.drop_privileges) {
+        setup_privilege_violations_handler();
+    }
+
+    if (prefill)
+        slabs_prefill_global();
+    /* In restartable mode and we've decided to issue a fixup on memory */
+    if (memory_file != NULL && reuse_mem) {
+        mc_ptr_t old_base = meta->old_base;
+        assert(old_base == meta->old_base);
+
+        // should've pulled in process_started from meta file.
+        process_started = meta->process_started;
+        // TODO: must be a more canonical way of serializing/deserializing
+        // pointers? passing through uint64_t should work, and we're not
+        // annotating the pointer with anything, but it's still slightly
+        // insane.
+        restart_fixup((void *)old_base);
+    }
+    /*
+     * ignore SIGPIPE signals; we can use errno == EPIPE if we
+     * need that information
+     */
+    if (sigignore(SIGPIPE) == -1) {
+        perror("failed to ignore SIGPIPE; sigaction");
+        exit(EX_OSERR);
+    }
+    /* start up worker threads if MT mode */
+#ifdef EXTSTORE
+    slabs_set_storage(storage);
+    memcached_thread_init(settings.num_threads, storage);
+    init_lru_crawler(storage);
+#else
+    memcached_thread_init(settings.num_threads, NULL);
+    init_lru_crawler(NULL);
+#endif
+
+    if (start_assoc_maint && start_assoc_maintenance_thread() == -1) {
+        exit(EXIT_FAILURE);
+    }
+    if (start_lru_crawler && start_item_crawler_thread() != 0) {
+        fprintf(stderr, "Failed to enable LRU crawler thread\n");
+        exit(EXIT_FAILURE);
+    }
+#ifdef EXTSTORE
+    if (storage && start_storage_compact_thread(storage) != 0) {
+        fprintf(stderr, "Failed to start storage compaction thread\n");
+        exit(EXIT_FAILURE);
+    }
+    if (storage && start_storage_write_thread(storage) != 0) {
+        fprintf(stderr, "Failed to start storage writer thread\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if (start_lru_maintainer && start_lru_maintainer_thread(storage) != 0) {
+#else
+    if (start_lru_maintainer && start_lru_maintainer_thread(NULL) != 0) {
+#endif
+        fprintf(stderr, "Failed to enable LRU maintainer thread\n");
+        free(meta);
+        return 1;
+    }
+
+    if (settings.slab_reassign &&
+        start_slab_maintenance_thread() == -1) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (settings.idle_timeout && start_conn_timeout_thread() == -1) {
+        exit(EXIT_FAILURE);
+    }
+
+    /* initialise clock event */
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+    {
+        struct timespec ts;
+        if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+            monotonic = true;
+            monotonic_start = ts.tv_sec;
+            // Monotonic clock needs special handling for restarts.
+            // We get a start time at an arbitrary place, so we need to
+            // restore the original time delta, which is always "now" - _start
+            if (reuse_mem) {
+                // the running timespan at stop time + the time we think we
+                // were stopped.
+                monotonic_start -= meta->current_time + meta->time_delta;
+            } else {
+                monotonic_start -= ITEM_UPDATE_INTERVAL + 2;
+            }
+        }
+    }
+#endif
+    clock_handler(0, 0, 0);
+
+    /* create unix mode sockets after dropping privileges */
+    if (settings.socketpath != NULL) {
+        errno = 0;
+        if (server_socket_unix(settings.socketpath,settings.access)) {
+            vperror("failed to listen on UNIX socket: %s", settings.socketpath);
+            exit(EX_OSERR);
+        }
+    }
+
+    /* create the listening socket, bind it, and init */
+    if (settings.socketpath == NULL) {
+        const char *portnumber_filename = getenv("MEMCACHED_PORT_FILENAME");
+        char *temp_portnumber_filename = NULL;
+        size_t len;
+        FILE *portnumber_file = NULL;
+
+        if (portnumber_filename != NULL) {
+            len = strlen(portnumber_filename)+4+1;
+            temp_portnumber_filename = malloc(len);
+            snprintf(temp_portnumber_filename,
+                     len,
+                     "%s.lck", portnumber_filename);
+
+            portnumber_file = fopen(temp_portnumber_filename, "a");
+            if (portnumber_file == NULL) {
+                fprintf(stderr, "Failed to open \"%s\": %s\n",
+                        temp_portnumber_filename, strerror(errno));
+            }
+        }
+
+        errno = 0;
+        if (settings.port && server_sockets(settings.port, tcp_transport,
+                                           portnumber_file)) {
+            vperror("failed to listen on TCP port %d", settings.port);
+            exit(EX_OSERR);
+        }
+
+        /*
+         * initialization order: first create the listening sockets
+         * (may need root on low ports), then drop root if needed,
+         * then daemonize if needed, then init libevent (in some cases
+         * descriptors created by libevent wouldn't survive forking).
+         */
+
+        /* create the UDP listening socket and bind it */
+        errno = 0;
+        if (settings.udpport && server_sockets(settings.udpport, udp_transport,
+                                              portnumber_file)) {
+            vperror("failed to listen on UDP port %d", settings.udpport);
+            exit(EX_OSERR);
+        }
+
+        if (portnumber_file) {
+            fclose(portnumber_file);
+            rename(temp_portnumber_filename, portnumber_filename);
+        }
+        if (temp_portnumber_filename)
+            free(temp_portnumber_filename);
+    }
+
+    /* Give the sockets a moment to open. I know this is dumb, but the error
+     * is only an advisory.
+     */
+    usleep(1000);
+    if (stats_state.curr_conns + stats_state.reserved_fds >= settings.maxconns - 1) {
+        fprintf(stderr, "Maxconns setting is too low, use -c to increase.\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if (pid_file != NULL) {
+        save_pid(pid_file);
+    }
+
+    /* Drop privileges no longer needed */
+    if (settings.drop_privileges) {
+        drop_privileges();
+    }
+
+    /* Initialize the uriencode lookup table. */
+    uriencode_init();
+
+    /* enter the event loop */
+    // we run memcached in in-process mode, no need to listen on connections or do clock ticks
+    //while (!stop_main_loop) {
+    //    if (event_base_loop(main_base, EVLOOP_ONCE) != 0) {
+    //        retval = EXIT_FAILURE;
+    //        break;
+    //    }
+    //}
+    return EXIT_SUCCESS;
+}
+
+int memcached_event_loop(bool * stop_main_loop){
+    int retval = EXIT_SUCCESS;
+    while (!(*stop_main_loop)) {
+        if (event_base_loop(main_base, EVLOOP_ONCE) != 0) {
+            retval = EXIT_FAILURE;
+            break;
+        }
+    }
+    return retval;
+}
+
+int stop_memcached(){
+    fprintf(stderr, "Gracefully stopping\n");
+    stop_threads();
+    if (memory_file != NULL) {
+        restart_mmap_close();
+    }
+
+    /* remove the PID file if we're a daemon */
+    //always false in library mode
+    //if (do_daemonize)
+    //    remove_pidfile(pid_file);
+    /* Clean up strdup() call for bind() address */
+    if (settings.inter)
+      free(settings.inter);
+
+    /* cleanup base */
+    event_base_free(main_base);
+
+    free(meta);
+
+    return EXIT_SUCCESS;
+}
diff --git a/cachelib/external/memcached/memcached.h b/cachelib/external/memcached/memcached.h
new file mode 100644
index 0000000000..e4f7bf2f17
--- /dev/null
+++ b/cachelib/external/memcached/memcached.h
@@ -0,0 +1,883 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+/** \file
+ * The main memcached header holding commonly used data
+ * structures and function prototypes.
+ */
+
+#include "config.h"
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <event.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <assert.h>
+#include <grp.h>
+#include <signal.h>
+/* need this to get IOV_MAX on some platforms. */
+#ifndef __need_IOV_MAX
+#define __need_IOV_MAX
+#endif
+#include <limits.h>
+/* FreeBSD 4.x doesn't have IOV_MAX exposed. */
+#ifndef IOV_MAX
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__GNU__)
+# define IOV_MAX 1024
+/* GNU/Hurd don't set MAXPATHLEN
+ * http://www.gnu.org/software/hurd/hurd/porting/guidelines.html#PATH_MAX_tt_MAX_PATH_tt_MAXPATHL */
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 4096
+#endif
+#endif
+#endif
+
+#include "itoa_ljust.h"
+#include "protocol_binary.h"
+#include "cache.h"
+#include "logger.h"
+
+#ifdef EXTSTORE
+#include "extstore.h"
+#include "crc32c.h"
+#endif
+
+#include "sasl_defs.h"
+#ifdef TLS
+#include <openssl/ssl.h>
+#endif
+
+/** Maximum length of a key. */
+#define KEY_MAX_LENGTH 250
+
+/** Maximum length of a uri encoded key. */
+#define KEY_MAX_URI_ENCODED_LENGTH (KEY_MAX_LENGTH  * 3 + 1)
+
+/** Size of an incr buf. */
+#define INCR_MAX_STORAGE_LEN 24
+
+#define WRITE_BUFFER_SIZE 1024
+#define READ_BUFFER_SIZE 16384
+#define READ_BUFFER_CACHED 0
+#define UDP_READ_BUFFER_SIZE 65536
+#define UDP_MAX_PAYLOAD_SIZE 1400
+#define UDP_HEADER_SIZE 8
+#define MAX_SENDBUF_SIZE (256 * 1024 * 1024)
+
+/* Binary protocol stuff */
+#define BIN_MAX_EXTLEN 20 // length of the _incr command is currently the longest.
+
+/* Initial power multiplier for the hash table */
+#define HASHPOWER_DEFAULT 16
+#define HASHPOWER_MAX 32
+
+/*
+ * We only reposition items in the LRU queue if they haven't been repositioned
+ * in this many seconds. That saves us from churning on frequently-accessed
+ * items.
+ */
+#define ITEM_UPDATE_INTERVAL 60
+
+/*
+ * Valid range of the maximum size of an item, in bytes.
+ */
+#define ITEM_SIZE_MAX_LOWER_LIMIT 1024
+#define ITEM_SIZE_MAX_UPPER_LIMIT 1024 * 1024 * 1024
+
+
+/* unistd.h is here */
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+/* Slab sizing definitions. */
+#define POWER_SMALLEST 1
+#define POWER_LARGEST  256 /* actual cap is 255 */
+#define SLAB_GLOBAL_PAGE_POOL 0 /* magic slab class for storing pages for reassignment */
+#define CHUNK_ALIGN_BYTES 8
+/* slab class max is a 6-bit number, -1. */
+#define MAX_NUMBER_OF_SLAB_CLASSES (63 + 1)
+
+/** How long an object can reasonably be assumed to be locked before
+    harvesting it on a low memory condition. Default: disabled. */
+#define TAIL_REPAIR_TIME_DEFAULT 0
+
+/* warning: don't use these macros with a function, as it evals its arg twice */
+#define ITEM_get_cas(i) (((i)->it_flags & ITEM_CAS) ? \
+        (i)->data->cas : (uint64_t)0)
+
+#define ITEM_set_cas(i,v) { \
+    if ((i)->it_flags & ITEM_CAS) { \
+        (i)->data->cas = v; \
+    } \
+}
+
+#define ITEM_key(item) (((char*)&((item)->data)) \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+
+#define ITEM_suffix(item) ((char*) &((item)->data) + (item)->nkey + 1 \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+
+#define ITEM_data(item) ((char*) &((item)->data) + (item)->nkey + 1 \
+         + (((item)->it_flags & ITEM_CFLAGS) ? sizeof(uint32_t) : 0) \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+
+#define ITEM_ntotal(item) (sizeof(struct _stritem) + (item)->nkey + 1 \
+         + (item)->nbytes \
+         + (((item)->it_flags & ITEM_CFLAGS) ? sizeof(uint32_t) : 0) \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+
+#define ITEM_clsid(item) ((item)->slabs_clsid & ~(3<<6))
+#define ITEM_lruid(item) ((item)->slabs_clsid & (3<<6))
+
+#define STAT_KEY_LEN 128
+#define STAT_VAL_LEN 128
+
+/** Append a simple stat with a stat name, value format and value */
+#define APPEND_STAT(name, fmt, val) \
+    append_stat(name, add_stats, c, fmt, val);
+
+/** Append an indexed stat with a stat name (with format), value format
+    and value */
+#define APPEND_NUM_FMT_STAT(name_fmt, num, name, fmt, val)          \
+    klen = snprintf(key_str, STAT_KEY_LEN, name_fmt, num, name);    \
+    vlen = snprintf(val_str, STAT_VAL_LEN, fmt, val);               \
+    add_stats(key_str, klen, val_str, vlen, c);
+
+/** Common APPEND_NUM_FMT_STAT format. */
+#define APPEND_NUM_STAT(num, name, fmt, val) \
+    APPEND_NUM_FMT_STAT("%d:%s", num, name, fmt, val)
+
+/** Item client flag conversion */
+#define FLAGS_CONV(it, flag) { \
+    if ((it)->it_flags & ITEM_CFLAGS) { \
+        flag = *((uint32_t *)ITEM_suffix((it))); \
+    } else { \
+        flag = 0; \
+    } \
+}
+
+#define FLAGS_SIZE(item) (((item)->it_flags & ITEM_CFLAGS) ? sizeof(uint32_t) : 0)
+
+/**
+ * Callback for any function producing stats.
+ *
+ * @param key the stat's key
+ * @param klen length of the key
+ * @param val the stat's value in an ascii form (e.g. text form of a number)
+ * @param vlen length of the value
+ * @parm cookie magic callback cookie
+ */
+typedef void (*ADD_STAT)(const char *key, const uint16_t klen,
+                         const char *val, const uint32_t vlen,
+                         const void *cookie);
+
+/*
+ * NOTE: If you modify this table you _MUST_ update the function state_text
+ */
+/**
+ * Possible states of a connection.
+ */
+enum conn_states {
+    conn_listening,  /**< the socket which listens for connections */
+    conn_new_cmd,    /**< Prepare connection for next command */
+    conn_waiting,    /**< waiting for a readable socket */
+    conn_read,       /**< reading in a command line */
+    conn_parse_cmd,  /**< try to parse a command from the input buffer */
+    conn_write,      /**< writing out a simple response */
+    conn_nread,      /**< reading in a fixed number of bytes */
+    conn_swallow,    /**< swallowing unnecessary bytes w/o storing */
+    conn_closing,    /**< closing this connection */
+    conn_mwrite,     /**< writing out many items sequentially */
+    conn_closed,     /**< connection is closed */
+    conn_watch,      /**< held by the logger thread as a watcher */
+    conn_max_state   /**< Max state value (used for assertion) */
+};
+
+enum bin_substates {
+    bin_no_state,
+    bin_reading_set_header,
+    bin_reading_cas_header,
+    bin_read_set_value,
+    bin_reading_get_key,
+    bin_reading_stat,
+    bin_reading_del_header,
+    bin_reading_incr_header,
+    bin_read_flush_exptime,
+    bin_reading_sasl_auth,
+    bin_reading_sasl_auth_data,
+    bin_reading_touch_key,
+};
+
+enum protocol {
+    ascii_prot = 3, /* arbitrary value. */
+    binary_prot,
+    negotiating_prot /* Discovering the protocol */
+};
+
+enum network_transport {
+    local_transport, /* Unix sockets*/
+    tcp_transport,
+    udp_transport
+};
+
+enum pause_thread_types {
+    PAUSE_WORKER_THREADS = 0,
+    PAUSE_ALL_THREADS,
+    RESUME_ALL_THREADS,
+    RESUME_WORKER_THREADS
+};
+
+#define IS_TCP(x) (x == tcp_transport)
+#define IS_UDP(x) (x == udp_transport)
+
+#define NREAD_ADD 1
+#define NREAD_SET 2
+#define NREAD_REPLACE 3
+#define NREAD_APPEND 4
+#define NREAD_PREPEND 5
+#define NREAD_CAS 6
+
+enum store_item_type {
+    NOT_STORED=0, STORED, EXISTS, NOT_FOUND, TOO_LARGE, NO_MEMORY
+};
+
+enum delta_result_type {
+    OK, NON_NUMERIC, EOM, DELTA_ITEM_NOT_FOUND, DELTA_ITEM_CAS_MISMATCH
+};
+
+/** Time relative to server start. Smaller than time_t on 64-bit systems. */
+// TODO: Move to sub-header. needed in logger.h
+//typedef unsigned int rel_time_t;
+
+/** Use X macros to avoid iterating over the stats fields during reset and
+ * aggregation. No longer have to add new stats in 3+ places.
+ */
+
+#define SLAB_STATS_FIELDS \
+    X(set_cmds) \
+    X(get_hits) \
+    X(touch_hits) \
+    X(delete_hits) \
+    X(cas_hits) \
+    X(cas_badval) \
+    X(incr_hits) \
+    X(decr_hits)
+
+/** Stats stored per slab (and per thread). */
+struct slab_stats {
+#define X(name) uint64_t    name;
+    SLAB_STATS_FIELDS
+#undef X
+};
+
+#define THREAD_STATS_FIELDS \
+    X(get_cmds) \
+    X(get_misses) \
+    X(get_expired) \
+    X(get_flushed) \
+    X(touch_cmds) \
+    X(touch_misses) \
+    X(delete_misses) \
+    X(incr_misses) \
+    X(decr_misses) \
+    X(cas_misses) \
+    X(meta_cmds) \
+    X(bytes_read) \
+    X(bytes_written) \
+    X(flush_cmds) \
+    X(conn_yields) /* # of yields for connections (-R option)*/ \
+    X(auth_cmds) \
+    X(auth_errors) \
+    X(idle_kicks) /* idle connections killed */ \
+    X(response_obj_oom) \
+    X(read_buf_oom)
+
+#ifdef EXTSTORE
+#define EXTSTORE_THREAD_STATS_FIELDS \
+    X(get_extstore) \
+    X(get_aborted_extstore) \
+    X(get_oom_extstore) \
+    X(recache_from_extstore) \
+    X(miss_from_extstore) \
+    X(badcrc_from_extstore)
+#endif
+
+/**
+ * Stats stored per-thread.
+ */
+struct thread_stats {
+    pthread_mutex_t   mutex;
+#define X(name) uint64_t    name;
+    THREAD_STATS_FIELDS
+#ifdef EXTSTORE
+    EXTSTORE_THREAD_STATS_FIELDS
+#endif
+#undef X
+    struct slab_stats slab_stats[MAX_NUMBER_OF_SLAB_CLASSES];
+    uint64_t lru_hits[POWER_LARGEST];
+    uint64_t response_obj_bytes;
+    uint64_t response_obj_total;
+    uint64_t response_obj_free;
+    uint64_t read_buf_bytes;
+    uint64_t read_buf_bytes_free;
+};
+
+/**
+ * Global stats. Only resettable stats should go into this structure.
+ */
+struct stats {
+    uint64_t      total_items;
+    uint64_t      total_conns;
+    uint64_t      rejected_conns;
+    uint64_t      malloc_fails;
+    uint64_t      listen_disabled_num;
+    uint64_t      slabs_moved;       /* times slabs were moved around */
+    uint64_t      slab_reassign_rescues; /* items rescued during slab move */
+    uint64_t      slab_reassign_evictions_nomem; /* valid items lost during slab move */
+    uint64_t      slab_reassign_inline_reclaim; /* valid items lost during slab move */
+    uint64_t      slab_reassign_chunk_rescues; /* chunked-item chunks recovered */
+    uint64_t      slab_reassign_busy_items; /* valid temporarily unmovable */
+    uint64_t      slab_reassign_busy_deletes; /* refcounted items killed */
+    uint64_t      lru_crawler_starts; /* Number of item crawlers kicked off */
+    uint64_t      lru_maintainer_juggles; /* number of LRU bg pokes */
+    uint64_t      time_in_listen_disabled_us;  /* elapsed time in microseconds while server unable to process new connections */
+    uint64_t      log_worker_dropped; /* logs dropped by worker threads */
+    uint64_t      log_worker_written; /* logs written by worker threads */
+    uint64_t      log_watcher_skipped; /* logs watchers missed */
+    uint64_t      log_watcher_sent; /* logs sent to watcher buffers */
+#ifdef EXTSTORE
+    uint64_t      extstore_compact_lost; /* items lost because they were locked */
+    uint64_t      extstore_compact_rescues; /* items re-written during compaction */
+    uint64_t      extstore_compact_skipped; /* unhit items skipped during compaction */
+#endif
+#ifdef TLS
+    uint64_t      ssl_handshake_errors; /* TLS failures at accept/handshake time */
+    uint64_t      ssl_new_sessions; /* successfully negotiated new (non-reused) TLS sessions */
+#endif
+    struct timeval maxconns_entered;  /* last time maxconns entered */
+};
+
+/**
+ * Global "state" stats. Reflects state that shouldn't be wiped ever.
+ * Ordered for some cache line locality for commonly updated counters.
+ */
+struct stats_state {
+    uint64_t      curr_items;
+    uint64_t      curr_bytes;
+    uint64_t      curr_conns;
+    uint64_t      hash_bytes;       /* size used for hash tables */
+    unsigned int  conn_structs;
+    unsigned int  reserved_fds;
+    unsigned int  hash_power_level; /* Better hope it's not over 9000 */
+    bool          hash_is_expanding; /* If the hash table is being expanded */
+    bool          accepting_conns;  /* whether we are currently accepting */
+    bool          slab_reassign_running; /* slab reassign in progress */
+    bool          lru_crawler_running; /* crawl in progress */
+};
+
+#define MAX_VERBOSITY_LEVEL 2
+
+/* When adding a setting, be sure to update process_stat_settings */
+/**
+ * Globally accessible settings as derived from the commandline.
+ */
+struct settings {
+    size_t maxbytes;
+    int maxconns;
+    int port;
+    int udpport;
+    char *inter;
+    int verbose;
+    rel_time_t oldest_live; /* ignore existing items older than this */
+    uint64_t oldest_cas; /* ignore existing items with CAS values lower than this */
+    int evict_to_free;
+    char *socketpath;   /* path to unix socket if using local socket */
+    char *auth_file;    /* path to user authentication file */
+    int access;  /* access mask (a la chmod) for unix domain socket */
+    double factor;          /* chunk size growth factor */
+    int chunk_size;
+    int num_threads;        /* number of worker (without dispatcher) libevent threads to run */
+    int num_threads_per_udp; /* number of worker threads serving each udp socket */
+    char prefix_delimiter;  /* character that marks a key prefix (for stats) */
+    int detail_enabled;     /* nonzero if we're collecting detailed stats */
+    int reqs_per_event;     /* Maximum number of io to process on each
+                               io-event. */
+    bool use_cas;
+    enum protocol binding_protocol;
+    int backlog;
+    int item_size_max;        /* Maximum item size */
+    int slab_chunk_size_max;  /* Upper end for chunks within slab pages. */
+    int slab_page_size;     /* Slab's page units. */
+    volatile sig_atomic_t sig_hup;  /* a HUP signal was received but not yet handled */
+    bool sasl;              /* SASL on/off */
+    bool maxconns_fast;     /* Whether or not to early close connections */
+    bool lru_crawler;        /* Whether or not to enable the autocrawler thread */
+    bool lru_maintainer_thread; /* LRU maintainer background thread */
+    bool lru_segmented;     /* Use split or flat LRU's */
+    bool slab_reassign;     /* Whether or not slab reassignment is allowed */
+    int slab_automove;     /* Whether or not to automatically move slabs */
+    double slab_automove_ratio; /* youngest must be within pct of oldest */
+    unsigned int slab_automove_window; /* window mover for algorithm */
+    int hashpower_init;     /* Starting hash power level */
+    bool shutdown_command; /* allow shutdown command */
+    int tail_repair_time;   /* LRU tail refcount leak repair time */
+    bool flush_enabled;     /* flush_all enabled */
+    bool dump_enabled;      /* whether cachedump/metadump commands work */
+    char *hash_algorithm;     /* Hash algorithm in use */
+    int lru_crawler_sleep;  /* Microsecond sleep between items */
+    uint32_t lru_crawler_tocrawl; /* Number of items to crawl per run */
+    int hot_lru_pct; /* percentage of slab space for HOT_LRU */
+    int warm_lru_pct; /* percentage of slab space for WARM_LRU */
+    double hot_max_factor; /* HOT tail age relative to COLD tail */
+    double warm_max_factor; /* WARM tail age relative to COLD tail */
+    int crawls_persleep; /* Number of LRU crawls to run before sleeping */
+    bool temp_lru; /* TTL < temporary_ttl uses TEMP_LRU */
+    uint32_t temporary_ttl; /* temporary LRU threshold */
+    int idle_timeout;       /* Number of seconds to let connections idle */
+    unsigned int logger_watcher_buf_size; /* size of logger's per-watcher buffer */
+    unsigned int logger_buf_size; /* size of per-thread logger buffer */
+    unsigned int resp_obj_mem_limit; /* total megabytes allowable for resp objects */
+    unsigned int read_buf_mem_limit; /* total megabytes allowable for read buffers */
+    bool drop_privileges;   /* Whether or not to drop unnecessary process privileges */
+    bool watch_enabled; /* allows watch commands to be dropped */
+    bool relaxed_privileges;   /* Relax process restrictions when running testapp */
+#ifdef EXTSTORE
+    unsigned int ext_io_threadcount; /* number of IO threads to run. */
+    unsigned int ext_page_size; /* size in megabytes of storage pages. */
+    unsigned int ext_item_size; /* minimum size of items to store externally */
+    unsigned int ext_item_age; /* max age of tail item before storing ext. */
+    unsigned int ext_low_ttl; /* remaining TTL below this uses own pages */
+    unsigned int ext_recache_rate; /* counter++ % recache_rate == 0 > recache */
+    unsigned int ext_wbuf_size; /* read only note for the engine */
+    unsigned int ext_compact_under; /* when fewer than this many pages, compact */
+    unsigned int ext_drop_under; /* when fewer than this many pages, drop COLD items */
+    double ext_max_frag; /* ideal maximum page fragmentation */
+    double slab_automove_freeratio; /* % of memory to hold free as buffer */
+    bool ext_drop_unread; /* skip unread items during compaction */
+    /* per-slab-class free chunk limit */
+    unsigned int ext_free_memchunks[MAX_NUMBER_OF_SLAB_CLASSES];
+#endif
+#ifdef TLS
+    bool ssl_enabled; /* indicates whether SSL is enabled */
+    SSL_CTX *ssl_ctx; /* holds the SSL server context which has the server certificate */
+    char *ssl_chain_cert; /* path to the server SSL chain certificate */
+    char *ssl_key; /* path to the server key */
+    int ssl_verify_mode; /* client certificate verify mode */
+    int ssl_keyformat; /* key format , defult is PEM */
+    char *ssl_ciphers; /* list of SSL ciphers */
+    char *ssl_ca_cert; /* certificate with CAs. */
+    rel_time_t ssl_last_cert_refresh_time; /* time of the last server certificate refresh */
+    unsigned int ssl_wbuf_size; /* size of the write buffer used by ssl_sendmsg method */
+    bool ssl_session_cache; /* enable SSL server session caching */
+#endif
+};
+
+extern struct stats stats;
+extern struct stats_state stats_state;
+extern time_t process_started;
+extern struct settings settings;
+
+#define ITEM_LINKED 1
+#define ITEM_CAS 2
+
+/* temp */
+#define ITEM_SLABBED 4
+
+/* Item was fetched at least once in its lifetime */
+#define ITEM_FETCHED 8
+/* Appended on fetch, removed on LRU shuffling */
+#define ITEM_ACTIVE 16
+/* If an item's storage are chained chunks. */
+#define ITEM_CHUNKED 32
+#define ITEM_CHUNK 64
+/* ITEM_data bulk is external to item */
+#define ITEM_HDR 128
+/* additional 4 bytes for item client flags */
+#define ITEM_CFLAGS 256
+/* item has sent out a token already */
+#define ITEM_TOKEN_SENT 512
+/* reserved, in case tokens should be a 2-bit count in future */
+#define ITEM_TOKEN_RESERVED 1024
+/* if item has been marked as a stale value */
+#define ITEM_STALE 2048
+
+/**
+ * Structure for storing items within memcached.
+ */
+typedef struct _stritem {
+    /* Protected by LRU locks */
+    struct _stritem *next;
+    struct _stritem *prev;
+    /* Rest are protected by an item lock */
+    struct _stritem *h_next;    /* hash chain next */
+    rel_time_t      time;       /* least recent access */
+    rel_time_t      exptime;    /* expire time */
+    int             nbytes;     /* size of data */
+    unsigned short  refcount;
+    uint16_t        it_flags;   /* ITEM_* above */
+    uint8_t         slabs_clsid;/* which slab class we're in */
+    uint8_t         nkey;       /* key length, w/terminating null and padding */
+    /* this odd type prevents type-punning issues when we do
+     * the little shuffle to save space when not using CAS. */
+    union {
+        uint64_t cas;
+        char end;
+    } data[];
+    /* if it_flags & ITEM_CAS we have 8 bytes CAS */
+    /* then null-terminated key */
+    /* then " flags length\r\n" (no terminating null) */
+    /* then data with terminating \r\n (no terminating null; it's binary!) */
+} item;
+
+// TODO: If we eventually want user loaded modules, we can't use an enum :(
+enum crawler_run_type {
+    CRAWLER_AUTOEXPIRE=0, CRAWLER_EXPIRED, CRAWLER_METADUMP
+};
+
+typedef struct {
+    struct _stritem *next;
+    struct _stritem *prev;
+    struct _stritem *h_next;    /* hash chain next */
+    rel_time_t      time;       /* least recent access */
+    rel_time_t      exptime;    /* expire time */
+    int             nbytes;     /* size of data */
+    unsigned short  refcount;
+    uint16_t        it_flags;   /* ITEM_* above */
+    uint8_t         slabs_clsid;/* which slab class we're in */
+    uint8_t         nkey;       /* key length, w/terminating null and padding */
+    uint32_t        remaining;  /* Max keys to crawl per slab per invocation */
+    uint64_t        reclaimed;  /* items reclaimed during this crawl. */
+    uint64_t        unfetched;  /* items reclaimed unfetched during this crawl. */
+    uint64_t        checked;    /* items examined during this crawl. */
+} crawler;
+
+/* Header when an item is actually a chunk of another item. */
+typedef struct _strchunk {
+    struct _strchunk *next;     /* points within its own chain. */
+    struct _strchunk *prev;     /* can potentially point to the head. */
+    struct _stritem  *head;     /* always points to the owner chunk */
+    int              size;      /* available chunk space in bytes */
+    int              used;      /* chunk space used */
+    int              nbytes;    /* used. */
+    unsigned short   refcount;  /* used? */
+    uint16_t         it_flags;  /* ITEM_* above. */
+    uint8_t          slabs_clsid; /* Same as above. */
+    uint8_t          orig_clsid; /* For obj hdr chunks slabs_clsid is fake. */
+    char data[];
+} item_chunk;
+
+#ifdef NEED_ALIGN
+static inline char *ITEM_schunk(item *it) {
+    int offset = it->nkey + 1
+        + ((it->it_flags & ITEM_CFLAGS) ? sizeof(uint32_t) : 0)
+        + ((it->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0);
+    int remain = offset % 8;
+    if (remain != 0) {
+        offset += 8 - remain;
+    }
+    return ((char *) &(it->data)) + offset;
+}
+#else
+#define ITEM_schunk(item) ((char*) &((item)->data) + (item)->nkey + 1 \
+         + (((item)->it_flags & ITEM_CFLAGS) ? sizeof(uint32_t) : 0) \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+#endif
+
+#ifdef EXTSTORE
+typedef struct {
+    unsigned int page_version; /* from IO header */
+    unsigned int offset; /* from IO header */
+    unsigned short page_id; /* from IO header */
+} item_hdr;
+#endif
+typedef struct {
+    pthread_t thread_id;        /* unique ID of this thread */
+    struct event_base *base;    /* libevent handle this thread uses */
+    struct event notify_event;  /* listen event for notify pipe */
+    int notify_receive_fd;      /* receiving end of notify pipe */
+    int notify_send_fd;         /* sending end of notify pipe */
+    struct thread_stats stats;  /* Stats generated by this thread */
+    struct conn_queue *new_conn_queue; /* queue of new connections to handle */
+    cache_t *resp_cache;        /* response objects */
+    cache_t *rbuf_cache;        /* static-sized read buffers */
+#ifdef EXTSTORE
+    cache_t *io_cache;          /* IO objects */
+    void *storage;              /* data object for storage system */
+#endif
+    logger *l;                  /* logger buffer */
+    void *lru_bump_buf;         /* async LRU bump buffer */
+#ifdef TLS
+    char   *ssl_wbuf;
+#endif
+
+} LIBEVENT_THREAD;
+
+/**
+ * Response objects
+ */
+#define MC_RESP_IOVCOUNT 4
+typedef struct _mc_resp {
+    struct _mc_resp *next; // choo choo.
+    int wbytes; // bytes to write out of wbuf: might be able to nuke this.
+    int tosend; // total bytes to send for this response
+    void *write_and_free; /** free this memory after finishing writing */
+
+    item *item; /* item associated with this response object, with reference held */
+    struct iovec iov[MC_RESP_IOVCOUNT]; /* built-in iovecs to simplify network code */
+    int chunked_total; /* total amount of chunked item data to send. */
+    uint8_t iovcnt;
+    uint8_t chunked_data_iov; /* this iov is a pointer to chunked data header */
+
+    /* instruct transmit to skip this response object. used by storage engines
+     * to asynchronously kill an object that was queued to write
+     */
+    bool skip;
+    bool free; // double free detection.
+    // UDP bits. Copied in from the client.
+    uint16_t    request_id; /* Incoming UDP request ID, if this is a UDP "connection" */
+    uint16_t    udp_sequence; /* packet counter when transmitting result */
+    uint16_t    udp_total; /* total number of packets in sequence */
+    struct sockaddr_in6 request_addr; /* udp: Who sent this request */
+    socklen_t request_addr_size;
+
+    char wbuf[WRITE_BUFFER_SIZE];
+} mc_resp;
+
+typedef struct conn conn;
+#ifdef EXTSTORE
+typedef struct _io_wrap {
+    obj_io io;
+    struct _io_wrap *next;
+    conn *c;
+    item *hdr_it;             /* original header item. */
+    mc_resp *resp;            /* associated response object */
+    unsigned int iovec_data;  /* specific index of data iovec */
+    bool noreply;             /* whether the response had noreply set */
+    bool miss;                /* signal a miss to unlink hdr_it */
+    bool badcrc;              /* signal a crc failure */
+    bool active;              /* tells if IO was dispatched or not */
+} io_wrap;
+#endif
+/**
+ * The structure representing a connection into memcached.
+ */
+struct conn {
+    sasl_conn_t *sasl_conn;
+    int    sfd;
+    bool sasl_started;
+    bool authenticated;
+    bool set_stale;
+    bool mset_res; /** uses mset format for return code */
+    bool close_after_write; /** flush write then move to close connection */
+    bool rbuf_malloced; /** read buffer was malloc'ed for ascii mget, needs free() */
+#ifdef TLS
+    SSL    *ssl;
+    char   *ssl_wbuf;
+    bool ssl_enabled;
+#endif
+    enum conn_states  state;
+    enum bin_substates substate;
+    rel_time_t last_cmd_time;
+    struct event event;
+    short  ev_flags;
+    short  which;   /** which events were just triggered */
+
+    char   *rbuf;   /** buffer to read commands into */
+    char   *rcurr;  /** but if we parsed some already, this is where we stopped */
+    int    rsize;   /** total allocated size of rbuf */
+    int    rbytes;  /** how much data, starting from rcur, do we have unparsed */
+
+    mc_resp *resp; // tail response.
+    mc_resp *resp_head; // first response in current stack.
+    char   *ritem;  /** when we read in an item's value, it goes here */
+    int    rlbytes;
+
+    /**
+     * item is used to hold an item structure created after reading the command
+     * line of set/add/replace commands, but before we finished reading the actual
+     * data. The data is read into ITEM_data(item) to avoid extra copying.
+     */
+
+    void   *item;     /* for commands set/add/replace  */
+
+    /* data for the swallow state */
+    int    sbytes;    /* how many bytes to swallow */
+
+#ifdef EXTSTORE
+    int io_wrapleft;
+    unsigned int recache_counter;
+    io_wrap *io_wraplist; /* linked list of io_wraps */
+    bool io_queued; /* FIXME: debugging flag */
+#endif
+    enum protocol protocol;   /* which protocol this connection speaks */
+    enum network_transport transport; /* what transport is used by this connection */
+
+    /* data for UDP clients */
+    int    request_id; /* Incoming UDP request ID, if this is a UDP "connection" */
+    struct sockaddr_in6 request_addr; /* udp: Who sent the most recent request */
+    socklen_t request_addr_size;
+
+    bool   noreply;   /* True if the reply should not be sent. */
+    /* current stats command */
+    struct {
+        char *buffer;
+        size_t size;
+        size_t offset;
+    } stats;
+
+    /* Binary protocol stuff */
+    /* This is where the binary header goes */
+    protocol_binary_request_header binary_header;
+    uint64_t cas; /* the cas to return */
+    short cmd; /* current command being processed */
+    int opaque;
+    int keylen;
+    conn   *next;     /* Used for generating a list of conn structures */
+    LIBEVENT_THREAD *thread; /* Pointer to the thread object serving this connection */
+    int (*try_read_command)(conn *c); /* pointer for top level input parser */
+    ssize_t (*read)(conn  *c, void *buf, size_t count);
+    ssize_t (*sendmsg)(conn *c, struct msghdr *msg, int flags);
+    ssize_t (*write)(conn *c, void *buf, size_t count);
+};
+
+/* array of conn structures, indexed by file descriptor */
+extern conn **conns;
+
+/* current time of day (updated periodically) */
+extern volatile rel_time_t current_time;
+
+/* TODO: Move to slabs.h? */
+extern volatile int slab_rebalance_signal;
+
+struct slab_rebalance {
+    void *slab_start;
+    void *slab_end;
+    void *slab_pos;
+    int s_clsid;
+    int d_clsid;
+    uint32_t busy_items;
+    uint32_t rescues;
+    uint32_t evictions_nomem;
+    uint32_t inline_reclaim;
+    uint32_t chunk_rescues;
+    uint32_t busy_deletes;
+    uint32_t busy_loops;
+    uint8_t done;
+    uint8_t *completed;
+};
+
+extern struct slab_rebalance slab_rebal;
+#ifdef EXTSTORE
+extern void *ext_storage;
+#endif
+/*
+ * Functions
+ */
+void do_accept_new_conns(const bool do_accept);
+enum delta_result_type do_add_delta(conn *c, const char *key,
+                                    const size_t nkey, const bool incr,
+                                    const int64_t delta, char *buf,
+                                    uint64_t *cas, const uint32_t hv,
+                                    item **it_ret);
+enum store_item_type do_store_item(item *item, int comm, conn* c, const uint32_t hv);
+conn *conn_new(const int sfd, const enum conn_states init_state, const int event_flags, const int read_buffer_size,
+    enum network_transport transport, struct event_base *base, void *ssl);
+
+void conn_worker_readd(conn *c);
+extern int daemonize(int nochdir, int noclose);
+
+#define mutex_lock(x) pthread_mutex_lock(x)
+#define mutex_unlock(x) pthread_mutex_unlock(x)
+
+#include "stats_prefix.h"
+#include "slabs.h"
+#include "assoc.h"
+#include "items.h"
+#include "crawler.h"
+#include "trace.h"
+#include "hash.h"
+#include "util.h"
+
+/*
+ * Functions such as the libevent-related calls that need to do cross-thread
+ * communication in multithreaded mode (rather than actually doing the work
+ * in the current thread) are called via "dispatch_" frontends, which are
+ * also #define-d to directly call the underlying code in singlethreaded mode.
+ */
+void memcached_thread_init(int nthreads, void *arg);
+void redispatch_conn(conn *c);
+void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags, int read_buffer_size,
+    enum network_transport transport, void *ssl);
+void sidethread_conn_close(conn *c);
+
+/* Lock wrappers for cache functions that are called from main loop. */
+enum delta_result_type add_delta(conn *c, const char *key,
+                                 const size_t nkey, bool incr,
+                                 const int64_t delta, char *buf,
+                                 uint64_t *cas);
+void accept_new_conns(const bool do_accept);
+void  conn_close_idle(conn *c);
+void  conn_close_all(void);
+item *item_alloc(char *key, size_t nkey, int flags, rel_time_t exptime, int nbytes);
+#define DO_UPDATE true
+#define DONT_UPDATE false
+item *item_get(const char *key, const size_t nkey, conn *c, const bool do_update);
+item *item_get_locked(const char *key, const size_t nkey, conn *c, const bool do_update, uint32_t *hv);
+item *item_touch(const char *key, const size_t nkey, uint32_t exptime, conn *c);
+int   item_link(item *it);
+void  item_remove(item *it);
+int   item_replace(item *it, item *new_it, const uint32_t hv);
+void  item_unlink(item *it);
+
+void item_lock(uint32_t hv);
+void *item_trylock(uint32_t hv);
+void item_trylock_unlock(void *arg);
+void item_unlock(uint32_t hv);
+void pause_threads(enum pause_thread_types type);
+void stop_threads(void);
+int stop_conn_timeout_thread(void);
+#define refcount_incr(it) ++(it->refcount)
+#define refcount_decr(it) --(it->refcount)
+void STATS_LOCK(void);
+void STATS_UNLOCK(void);
+#define THR_STATS_LOCK(c) pthread_mutex_lock(&c->thread->stats.mutex)
+#define THR_STATS_UNLOCK(c) pthread_mutex_unlock(&c->thread->stats.mutex)
+void threadlocal_stats_reset(void);
+void threadlocal_stats_aggregate(struct thread_stats *stats);
+void slab_stats_aggregate(struct thread_stats *stats, struct slab_stats *out);
+
+/* Stat processing functions */
+void append_stat(const char *name, ADD_STAT add_stats, conn *c,
+                 const char *fmt, ...);
+
+int init_memcached(int argc, char ** argv, size_t cache_size_mb, uint32_t nthreads);
+int stop_memcached();
+
+
+enum store_item_type store_item(item *item, int comm, conn *c);
+
+#if HAVE_DROP_PRIVILEGES
+extern void setup_privilege_violations_handler(void);
+extern void drop_privileges(void);
+#else
+#define setup_privilege_violations_handler()
+#define drop_privileges()
+#endif
+
+#if HAVE_DROP_WORKER_PRIVILEGES
+extern void drop_worker_privileges(void);
+#else
+#define drop_worker_privileges()
+#endif
+
+/* If supported, give compiler hints for branch prediction. */
+#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+#define __builtin_expect(x, expected_value) (x)
+#endif
+
+#define likely(x)       __builtin_expect((x),1)
+#define unlikely(x)     __builtin_expect((x),0)
diff --git a/cachelib/external/memcached/murmur3_hash.c b/cachelib/external/memcached/murmur3_hash.c
new file mode 100644
index 0000000000..9c3edf3b75
--- /dev/null
+++ b/cachelib/external/memcached/murmur3_hash.c
@@ -0,0 +1,124 @@
+//-----------------------------------------------------------------------------
+// MurmurHash3 was written by Austin Appleby, and is placed in the public
+// domain. The author hereby disclaims copyright to this source code.
+
+// Note - The x86 and x64 versions do _not_ produce the same results, as the
+// algorithms are optimized for their respective platforms. You can still
+// compile and run any of them on any platform, but your performance with the
+// non-native version will be less than optimal.
+
+#include "murmur3_hash.h"
+
+//-----------------------------------------------------------------------------
+// Platform-specific functions and macros
+
+// Microsoft Visual Studio
+
+#if defined(_MSC_VER)
+
+#define FORCE_INLINE    __forceinline
+
+#include <stdlib.h>
+
+#define ROTL32(x,y)    _rotl(x,y)
+
+#define BIG_CONSTANT(x) (x)
+
+// Other compilers
+
+#else    // defined(_MSC_VER)
+
+#define    FORCE_INLINE inline __attribute__((always_inline))
+
+static inline uint32_t rotl32 ( uint32_t x, int8_t r )
+{
+  return (x << r) | (x >> (32 - r));
+}
+
+#define    ROTL32(x,y)    rotl32(x,y)
+
+#define BIG_CONSTANT(x) (x##LLU)
+
+#endif // !defined(_MSC_VER)
+
+//-----------------------------------------------------------------------------
+// Block read - if your platform needs to do endian-swapping or can only
+// handle aligned reads, do the conversion here
+
+static FORCE_INLINE uint32_t getblock32 ( const uint32_t * p, int i )
+{
+  return p[i];
+}
+
+//-----------------------------------------------------------------------------
+// Finalization mix - force all bits of a hash block to avalanche
+
+static FORCE_INLINE uint32_t fmix32 ( uint32_t h )
+{
+  h ^= h >> 16;
+  h *= 0x85ebca6b;
+  h ^= h >> 13;
+  h *= 0xc2b2ae35;
+  h ^= h >> 16;
+
+  return h;
+}
+
+//-----------------------------------------------------------------------------
+
+/* Definition modified slightly from the public domain interface (no seed +
+ * return value */
+uint32_t MurmurHash3_x86_32 ( const void * key, size_t length)
+{
+  const uint8_t * data = (const uint8_t*)key;
+  const int nblocks = length / 4;
+
+  uint32_t h1 = 0;
+
+  uint32_t c1 = 0xcc9e2d51;
+  uint32_t c2 = 0x1b873593;
+
+  //----------
+  // body
+
+  const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
+
+  for(int i = -nblocks; i; i++)
+  {
+    uint32_t k1 = getblock32(blocks,i);
+
+    k1 *= c1;
+    k1 = ROTL32(k1,15);
+    k1 *= c2;
+
+    h1 ^= k1;
+    h1 = ROTL32(h1,13);
+    h1 = h1*5+0xe6546b64;
+  }
+
+  //----------
+  // tail
+
+  const uint8_t * tail = (const uint8_t*)(data + nblocks*4);
+
+  uint32_t k1 = 0;
+
+  switch(length & 3)
+  {
+  case 3: k1 ^= tail[2] << 16;
+  case 2: k1 ^= tail[1] << 8;
+  case 1: k1 ^= tail[0];
+          k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
+  };
+
+  //----------
+  // finalization
+
+  h1 ^= length;
+
+  h1 = fmix32(h1);
+
+  //*(uint32_t*)out = h1;
+  return h1;
+}
+
diff --git a/cachelib/external/memcached/murmur3_hash.h b/cachelib/external/memcached/murmur3_hash.h
new file mode 100644
index 0000000000..e2820e1934
--- /dev/null
+++ b/cachelib/external/memcached/murmur3_hash.h
@@ -0,0 +1,19 @@
+//-----------------------------------------------------------------------------
+// MurmurHash3 was written by Austin Appleby, and is placed in the public
+// domain. The author hereby disclaims copyright to this source code.
+
+#ifndef MURMURHASH3_H
+#define MURMURHASH3_H
+
+//-----------------------------------------------------------------------------
+// Platform-specific functions and macros
+#include <stdint.h>
+#include <stddef.h>
+
+//-----------------------------------------------------------------------------
+
+uint32_t MurmurHash3_x86_32(const void *key, size_t length);
+
+//-----------------------------------------------------------------------------
+
+#endif // MURMURHASH3_H
diff --git a/cachelib/external/memcached/openbsd_priv.c b/cachelib/external/memcached/openbsd_priv.c
new file mode 100644
index 0000000000..b72f167e57
--- /dev/null
+++ b/cachelib/external/memcached/openbsd_priv.c
@@ -0,0 +1,32 @@
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "memcached.h"
+
+/*
+ * this section of code will drop all (OpenBSD) privileges including
+ * those normally granted to all userland process (basic privileges). The
+ * effect of this is that after running this code, the process will not able
+ * to fork(), exec(), etc.  See pledge(2) for more information.
+ */
+void drop_privileges() {
+    extern char *__progname;
+
+    if (settings.socketpath != NULL) {
+       if (pledge("stdio unix", NULL) == -1) {
+          fprintf(stderr, "%s: pledge: %s\n", __progname, strerror(errno));
+          exit(EXIT_FAILURE);
+       }
+    } else {
+       if (pledge("stdio inet", NULL) == -1) {
+          fprintf(stderr, "%s: pledge: %s\n", __progname, strerror(errno));
+          exit(EXIT_FAILURE);
+       }
+     }
+}
+
+void setup_privilege_violations_handler(void) {
+   // not needed
+}
diff --git a/cachelib/external/memcached/protocol_binary.h b/cachelib/external/memcached/protocol_binary.h
new file mode 100644
index 0000000000..5f3b77831d
--- /dev/null
+++ b/cachelib/external/memcached/protocol_binary.h
@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) <2008>, Sun Microsystems, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the  nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY SUN MICROSYSTEMS, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL SUN MICROSYSTEMS, INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Summary: Constants used by to implement the binary protocol.
+ *
+ * Copy: See Copyright for the status of this software.
+ *
+ * Author: Trond Norbye <trond.norbye@sun.com>
+ */
+
+#ifndef PROTOCOL_BINARY_H
+#define PROTOCOL_BINARY_H
+
+/**
+ * This file contains definitions of the constants and packet formats
+ * defined in the binary specification. Please note that you _MUST_ remember
+ * to convert each multibyte field to / from network byte order to / from
+ * host order.
+ */
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+    /**
+     * Definition of the legal "magic" values used in a packet.
+     * See section 3.1 Magic byte
+     */
+    typedef enum {
+        PROTOCOL_BINARY_REQ = 0x80,
+        PROTOCOL_BINARY_RES = 0x81
+    } protocol_binary_magic;
+
+    /**
+     * Definition of the valid response status numbers.
+     * See section 3.2 Response Status
+     */
+    typedef enum {
+        PROTOCOL_BINARY_RESPONSE_SUCCESS = 0x00,
+        PROTOCOL_BINARY_RESPONSE_KEY_ENOENT = 0x01,
+        PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS = 0x02,
+        PROTOCOL_BINARY_RESPONSE_E2BIG = 0x03,
+        PROTOCOL_BINARY_RESPONSE_EINVAL = 0x04,
+        PROTOCOL_BINARY_RESPONSE_NOT_STORED = 0x05,
+        PROTOCOL_BINARY_RESPONSE_DELTA_BADVAL = 0x06,
+        PROTOCOL_BINARY_RESPONSE_AUTH_ERROR = 0x20,
+        PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE = 0x21,
+        PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND = 0x81,
+        PROTOCOL_BINARY_RESPONSE_ENOMEM = 0x82
+    } protocol_binary_response_status;
+
+    /**
+     * Definition of the different command opcodes.
+     * See section 3.3 Command Opcodes
+     */
+    typedef enum {
+        PROTOCOL_BINARY_CMD_GET = 0x00,
+        PROTOCOL_BINARY_CMD_SET = 0x01,
+        PROTOCOL_BINARY_CMD_ADD = 0x02,
+        PROTOCOL_BINARY_CMD_REPLACE = 0x03,
+        PROTOCOL_BINARY_CMD_DELETE = 0x04,
+        PROTOCOL_BINARY_CMD_INCREMENT = 0x05,
+        PROTOCOL_BINARY_CMD_DECREMENT = 0x06,
+        PROTOCOL_BINARY_CMD_QUIT = 0x07,
+        PROTOCOL_BINARY_CMD_FLUSH = 0x08,
+        PROTOCOL_BINARY_CMD_GETQ = 0x09,
+        PROTOCOL_BINARY_CMD_NOOP = 0x0a,
+        PROTOCOL_BINARY_CMD_VERSION = 0x0b,
+        PROTOCOL_BINARY_CMD_GETK = 0x0c,
+        PROTOCOL_BINARY_CMD_GETKQ = 0x0d,
+        PROTOCOL_BINARY_CMD_APPEND = 0x0e,
+        PROTOCOL_BINARY_CMD_PREPEND = 0x0f,
+        PROTOCOL_BINARY_CMD_STAT = 0x10,
+        PROTOCOL_BINARY_CMD_SETQ = 0x11,
+        PROTOCOL_BINARY_CMD_ADDQ = 0x12,
+        PROTOCOL_BINARY_CMD_REPLACEQ = 0x13,
+        PROTOCOL_BINARY_CMD_DELETEQ = 0x14,
+        PROTOCOL_BINARY_CMD_INCREMENTQ = 0x15,
+        PROTOCOL_BINARY_CMD_DECREMENTQ = 0x16,
+        PROTOCOL_BINARY_CMD_QUITQ = 0x17,
+        PROTOCOL_BINARY_CMD_FLUSHQ = 0x18,
+        PROTOCOL_BINARY_CMD_APPENDQ = 0x19,
+        PROTOCOL_BINARY_CMD_PREPENDQ = 0x1a,
+        PROTOCOL_BINARY_CMD_TOUCH = 0x1c,
+        PROTOCOL_BINARY_CMD_GAT = 0x1d,
+        PROTOCOL_BINARY_CMD_GATQ = 0x1e,
+        PROTOCOL_BINARY_CMD_GATK = 0x23,
+        PROTOCOL_BINARY_CMD_GATKQ = 0x24,
+
+        PROTOCOL_BINARY_CMD_SASL_LIST_MECHS = 0x20,
+        PROTOCOL_BINARY_CMD_SASL_AUTH = 0x21,
+        PROTOCOL_BINARY_CMD_SASL_STEP = 0x22,
+
+        /* These commands are used for range operations and exist within
+         * this header for use in other projects.  Range operations are
+         * not expected to be implemented in the memcached server itself.
+         */
+        PROTOCOL_BINARY_CMD_RGET      = 0x30,
+        PROTOCOL_BINARY_CMD_RSET      = 0x31,
+        PROTOCOL_BINARY_CMD_RSETQ     = 0x32,
+        PROTOCOL_BINARY_CMD_RAPPEND   = 0x33,
+        PROTOCOL_BINARY_CMD_RAPPENDQ  = 0x34,
+        PROTOCOL_BINARY_CMD_RPREPEND  = 0x35,
+        PROTOCOL_BINARY_CMD_RPREPENDQ = 0x36,
+        PROTOCOL_BINARY_CMD_RDELETE   = 0x37,
+        PROTOCOL_BINARY_CMD_RDELETEQ  = 0x38,
+        PROTOCOL_BINARY_CMD_RINCR     = 0x39,
+        PROTOCOL_BINARY_CMD_RINCRQ    = 0x3a,
+        PROTOCOL_BINARY_CMD_RDECR     = 0x3b,
+        PROTOCOL_BINARY_CMD_RDECRQ    = 0x3c
+        /* End Range operations */
+
+    } protocol_binary_command;
+
+    /**
+     * Definition of the data types in the packet
+     * See section 3.4 Data Types
+     */
+    typedef enum {
+        PROTOCOL_BINARY_RAW_BYTES = 0x00
+    } protocol_binary_datatypes;
+
+    /**
+     * Definition of the header structure for a request packet.
+     * See section 2
+     */
+    typedef union {
+        struct {
+            uint8_t magic;
+            uint8_t opcode;
+            uint16_t keylen;
+            uint8_t extlen;
+            uint8_t datatype;
+            uint16_t reserved;
+            uint32_t bodylen;
+            uint32_t opaque;
+            uint64_t cas;
+        } request;
+        uint8_t bytes[24];
+    } protocol_binary_request_header;
+
+    /**
+     * Definition of the header structure for a response packet.
+     * See section 2
+     */
+    typedef union {
+        struct {
+            uint8_t magic;
+            uint8_t opcode;
+            uint16_t keylen;
+            uint8_t extlen;
+            uint8_t datatype;
+            uint16_t status;
+            uint32_t bodylen;
+            uint32_t opaque;
+            uint64_t cas;
+        } response;
+        uint8_t bytes[24];
+    } protocol_binary_response_header;
+
+    /**
+     * Definition of a request-packet containing no extras
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header)];
+    } protocol_binary_request_no_extras;
+
+    /**
+     * Definition of a response-packet containing no extras
+     */
+    typedef union {
+        struct {
+            protocol_binary_response_header header;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_response_header)];
+    } protocol_binary_response_no_extras;
+
+    /**
+     * Definition of the packet used by the get, getq, getk and getkq command.
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_get;
+    typedef protocol_binary_request_no_extras protocol_binary_request_getq;
+    typedef protocol_binary_request_no_extras protocol_binary_request_getk;
+    typedef protocol_binary_request_no_extras protocol_binary_request_getkq;
+
+    /**
+     * Definition of the packet returned from a successful get, getq, getk and
+     * getkq.
+     * See section 4
+     */
+    typedef union {
+        struct {
+            protocol_binary_response_header header;
+            struct {
+                uint32_t flags;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_response_header) + 4];
+    } protocol_binary_response_get;
+
+    typedef protocol_binary_response_get protocol_binary_response_getq;
+    typedef protocol_binary_response_get protocol_binary_response_getk;
+    typedef protocol_binary_response_get protocol_binary_response_getkq;
+
+    /**
+     * Definition of the packet used by the delete command
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_delete;
+
+    /**
+     * Definition of the packet returned by the delete command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_delete;
+
+    /**
+     * Definition of the packet used by the flush command
+     * See section 4
+     * Please note that the expiration field is optional, so remember to see
+     * check the header.bodysize to see if it is present.
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
+    } protocol_binary_request_flush;
+
+    /**
+     * Definition of the packet returned by the flush command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_flush;
+
+    /**
+     * Definition of the packet used by set, add and replace
+     * See section 4
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint32_t flags;
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 8];
+    } protocol_binary_request_set;
+    typedef protocol_binary_request_set protocol_binary_request_add;
+    typedef protocol_binary_request_set protocol_binary_request_replace;
+
+    /**
+     * Definition of the packet returned by set, add and replace
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_set;
+    typedef protocol_binary_response_no_extras protocol_binary_response_add;
+    typedef protocol_binary_response_no_extras protocol_binary_response_replace;
+
+    /**
+     * Definition of the noop packet
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_noop;
+
+    /**
+     * Definition of the packet returned by the noop command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_noop;
+
+    /**
+     * Definition of the structure used by the increment and decrement
+     * command.
+     * See section 4
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint64_t delta;
+                uint64_t initial;
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 20];
+    } protocol_binary_request_incr;
+    typedef protocol_binary_request_incr protocol_binary_request_decr;
+
+    /**
+     * Definition of the response from an incr or decr command
+     * command.
+     * See section 4
+     */
+    typedef union {
+        struct {
+            protocol_binary_response_header header;
+            struct {
+                uint64_t value;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_response_header) + 8];
+    } protocol_binary_response_incr;
+    typedef protocol_binary_response_incr protocol_binary_response_decr;
+
+    /**
+     * Definition of the quit
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_quit;
+
+    /**
+     * Definition of the packet returned by the quit command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_quit;
+
+    /**
+     * Definition of the packet used by append and prepend command
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_append;
+    typedef protocol_binary_request_no_extras protocol_binary_request_prepend;
+
+    /**
+     * Definition of the packet returned from a successful append or prepend
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_append;
+    typedef protocol_binary_response_no_extras protocol_binary_response_prepend;
+
+    /**
+     * Definition of the packet used by the version command
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_version;
+
+    /**
+     * Definition of the packet returned from a successful version command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_version;
+
+
+    /**
+     * Definition of the packet used by the stats command.
+     * See section 4
+     */
+    typedef protocol_binary_request_no_extras protocol_binary_request_stats;
+
+    /**
+     * Definition of the packet returned from a successful stats command
+     * See section 4
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_stats;
+
+    /**
+     * Definition of the packet used by the touch command.
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
+    } protocol_binary_request_touch;
+
+    /**
+     * Definition of the packet returned from the touch command
+     */
+    typedef protocol_binary_response_no_extras protocol_binary_response_touch;
+
+    /**
+     * Definition of the packet used by the GAT(Q) command.
+     */
+    typedef union {
+        struct {
+            protocol_binary_request_header header;
+            struct {
+                uint32_t expiration;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
+    } protocol_binary_request_gat;
+
+    typedef protocol_binary_request_gat protocol_binary_request_gatq;
+    typedef protocol_binary_request_gat protocol_binary_request_gatk;
+    typedef protocol_binary_request_gat protocol_binary_request_gatkq;
+
+    /**
+     * Definition of the packet returned from the GAT(Q)
+     */
+    typedef protocol_binary_response_get protocol_binary_response_gat;
+    typedef protocol_binary_response_get protocol_binary_response_gatq;
+    typedef protocol_binary_response_get protocol_binary_response_gatk;
+    typedef protocol_binary_response_get protocol_binary_response_gatkq;
+
+    /**
+     * Definition of a request for a range operation.
+     * See http://code.google.com/p/memcached/wiki/RangeOps
+     *
+     * These types are used for range operations and exist within
+     * this header for use in other projects.  Range operations are
+     * not expected to be implemented in the memcached server itself.
+     */
+    typedef union {
+        struct {
+            protocol_binary_response_header header;
+            struct {
+                uint16_t size;
+                uint8_t  reserved;
+                uint8_t  flags;
+                uint32_t max_results;
+            } body;
+        } message;
+        uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
+    } protocol_binary_request_rangeop;
+
+    typedef protocol_binary_request_rangeop protocol_binary_request_rget;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rset;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rsetq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rappend;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rappendq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rprepend;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rprependq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rdelete;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rdeleteq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rincr;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rincrq;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rdecr;
+    typedef protocol_binary_request_rangeop protocol_binary_request_rdecrq;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* PROTOCOL_BINARY_H */
diff --git a/cachelib/external/memcached/restart.c b/cachelib/external/memcached/restart.c
new file mode 100644
index 0000000000..9a83d3aa9c
--- /dev/null
+++ b/cachelib/external/memcached/restart.c
@@ -0,0 +1,443 @@
+#include "memcached.h"
+
+#include "restart.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+typedef struct _restart_data_cb restart_data_cb;
+
+struct _restart_data_cb {
+    void *data; // user supplied opaque data.
+    struct _restart_data_cb *next; // callbacks are ordered stack
+    restart_check_cb ccb;
+    restart_save_cb scb;
+    char tag[RESTART_TAG_MAXLEN];
+};
+
+// TODO: struct to hand back to caller.
+static int mmap_fd = 0;
+static void *mmap_base = NULL;
+static size_t slabmem_limit = 0;
+char *memory_file = NULL;
+
+static restart_data_cb *cb_stack = NULL;
+
+// Allows submodules and engines to have independent check and save metadata
+// routines for the restart code.
+void restart_register(const char *tag, restart_check_cb ccb, restart_save_cb scb, void *data) {
+    restart_data_cb *cb = calloc(1, sizeof(restart_data_cb));
+    if (cb == NULL) {
+        fprintf(stderr, "[restart] failed to allocate callback register\n");
+        abort();
+    }
+
+    // Handle first time call initialization inline so we don't need separate
+    // API call.
+    if (cb_stack == NULL) {
+        cb_stack = cb;
+    } else {
+        // Ensure we fire the callbacks in registration order.
+        // Someday I'll get a queue.h overhaul.
+        restart_data_cb *finder = cb_stack;
+        while (finder->next != NULL) {
+            finder = finder->next;
+        }
+        finder->next = cb;
+    }
+
+    safe_strcpy(cb->tag, tag, RESTART_TAG_MAXLEN);
+    cb->data = data;
+    cb->ccb = *ccb;
+    cb->scb = *scb;
+}
+
+typedef struct {
+    FILE *f;
+    restart_data_cb *cb;
+    char *line;
+    bool done;
+} restart_cb_ctx;
+
+// TODO: error string from cb?
+// - look for final line with checksum
+// - checksum entire file (up until final line)
+// - seek to start
+
+static int restart_check(const char *file) {
+    // metadata is kept in a separate file.
+    size_t flen = strlen(file);
+    const char *ext = ".meta";
+    char *metafile = calloc(1, flen + strlen(ext) + 1);
+    if (metafile == NULL) {
+        // probably in a really bad position if we hit here, so don't start.
+        fprintf(stderr, "[restart] failed to allocate memory for restart check\n");
+        abort();
+    }
+    memcpy(metafile, file, flen);
+    memcpy(metafile+flen, ext, strlen(ext));
+
+    FILE *f = fopen(metafile, "r");
+    if (f == NULL) {
+        fprintf(stderr, "[restart] no metadata save file, starting with a clean cache\n");
+        free(metafile);
+        return -1;
+    }
+
+    restart_cb_ctx ctx;
+
+    ctx.f = f;
+    ctx.cb = NULL;
+    ctx.line = NULL;
+    ctx.done = false;
+    if (restart_get_kv(&ctx, NULL, NULL) != RESTART_DONE) {
+        // First line must be a tag, so read it in and set up the proper
+        // callback here.
+        fprintf(stderr, "[restart] corrupt metadata file\n");
+        // TODO: this should probably just return -1 and skip the reuse.
+        abort();
+    }
+    if (ctx.cb == NULL) {
+        fprintf(stderr, "[restart] Failed to read a tag from metadata file\n");
+        abort();
+    }
+
+    // loop call the callback, check result code.
+    bool failed = false;
+    while (!ctx.done) {
+        restart_data_cb *cb = ctx.cb;
+        if (cb->ccb(cb->tag, &ctx, cb->data) != 0) {
+            failed = true;
+            break;
+        }
+    }
+
+    if (ctx.line)
+        free(ctx.line);
+
+    fclose(f);
+
+    unlink(metafile);
+    free(metafile);
+
+    if (failed) {
+        fprintf(stderr, "[restart] failed to valiate metadata, starting with a clean cache\n");
+        return -1;
+    } else {
+        return 0;
+    }
+}
+
+// This function advances the file read while being called directly from the
+// callback.
+// The control inversion here (callback calling in which might change the next
+// callback) allows the callbacks to set up proper loops or sequences for
+// reading data back, avoiding an event model.
+enum restart_get_kv_ret restart_get_kv(void *ctx, char **key, char **val) {
+    char *line = NULL;
+    size_t len = 0;
+    restart_data_cb *cb = NULL;
+    restart_cb_ctx *c = (restart_cb_ctx *) ctx;
+    // free previous line.
+    // we could just pass it into getline, but it can randomly realloc so we'd
+    // have to re-assign it into the structure anyway.
+    if (c->line != NULL) {
+        free(c->line);
+        c->line = NULL;
+    }
+
+    if (getline(&line, &len, c->f) != -1) {
+        // First char is an indicator:
+        // T for TAG, changing the callback we use.
+        // K for key/value, to ship to the active callback.
+        char *p = line;
+        while (*p != '\n') {
+            p++;
+        }
+        *p = '\0';
+
+        if (line[0] == 'T') {
+            cb = cb_stack;
+            while (cb != NULL) {
+                // NOTE: len is allocated size, not line len. need to chomp \n
+                if (strcmp(cb->tag, line+1) == 0) {
+                    break;
+                }
+                cb = cb->next;
+            }
+            if (cb == NULL) {
+                fprintf(stderr, "[restart] internal handler for metadata tag not found: %s:\n", line+1);
+                return RESTART_NOTAG;
+            }
+            c->cb = cb;
+        } else if (line[0] == 'K') {
+            char *p = line+1; // start just ahead of the token.
+            // tokenize the string and return the pointers?
+            if (key != NULL) {
+                *key = p;
+            }
+
+            // turn key into a normal NULL terminated string.
+            while (*p != ' ' && (p - line < len)) {
+                p++;
+            }
+            *p = '\0';
+            p++;
+
+            // value _should_ run until where the newline was, which is \0 now
+            if (val != NULL) {
+                *val = p;
+            }
+            c->line = line;
+
+            return RESTART_OK;
+        } else {
+            // FIXME: proper error chain.
+            fprintf(stderr, "[restart] invalid metadata line:\n\n%s\n", line);
+            free(line);
+            return RESTART_BADLINE;
+        }
+    } else {
+        // EOF or error in read.
+        c->done = true;
+    }
+
+    return RESTART_DONE;
+}
+
+// TODO:
+// - rolling checksum along with the writes.
+// - write final line + checksum + byte count or w/e.
+
+static int restart_save(const char *file) {
+    // metadata is kept in a separate file.
+    // FIXME: function.
+    size_t flen = strlen(file);
+    const char *ext = ".meta";
+    size_t extlen = strlen(ext);
+    char *metafile = calloc(1, flen + extlen + 1);
+    if (metafile == NULL) {
+        fprintf(stderr, "[restart] failed to allocate memory during metadata save\n");
+        return -1;
+    }
+    memcpy(metafile, file, flen);
+    memcpy(metafile+flen, ext, extlen);
+
+    // restrictive permissions for the metadata file.
+    // TODO: also for the mmap file eh? :P
+    mode_t oldmask = umask(~(S_IRUSR | S_IWUSR));
+    FILE *f = fopen(metafile, "w");
+    umask(oldmask);
+    if (f == NULL) {
+        // FIXME: correct error handling.
+        free(metafile);
+        perror("failed to write metadata file");
+        return -1;
+    }
+
+    restart_data_cb *cb = cb_stack;
+    restart_cb_ctx ctx;
+    ctx.f = f;
+    while (cb != NULL) {
+        // Plugins/engines in the metadata file are separated by tag lines.
+        fprintf(f, "T%s\n", cb->tag);
+        if (cb->scb(cb->tag, &ctx, cb->data) != 0) {
+            fclose(f);
+            free(metafile);
+            return -1;
+        }
+
+        cb = cb->next;
+    }
+
+    fclose(f);
+    free(metafile);
+
+    return 0;
+}
+
+// Keys and values must not contain spaces or newlines.
+// Could offer an interface that uriencodes values for the caller, however
+// nothing currently would use it, so add when necessary.
+#define SET_VAL_MAX 4096
+void restart_set_kv(void *ctx, const char *key, const char *fmt, ...) {
+    va_list ap;
+    restart_cb_ctx *c = (restart_cb_ctx *) ctx;
+    char valbuf[SET_VAL_MAX];
+
+    va_start(ap, fmt);
+    int vlen = vsnprintf(valbuf, SET_VAL_MAX-1, fmt, ap);
+    va_end(ap);
+    // This is heavy handed. We need to protect against corrupt data as much
+    // as possible. The buffer is large and these values are currently small,
+    // it will take a significant mistake to land here.
+    if (vlen >= SET_VAL_MAX) {
+        fprintf(stderr, "[restart] fatal error while saving metadata state, value too long for: %s %s",
+                key, valbuf);
+        abort();
+    }
+
+    fprintf(c->f, "K%s %s\n", key, valbuf);
+    // TODO: update crc32c
+}
+
+static long _find_pagesize(void) {
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+    return sysconf(_SC_PAGESIZE);
+#else
+    // A good guess.
+    return 4096;
+#endif
+}
+
+bool restart_mmap_open(const size_t limit, const char *file, void **mem_base) {
+    bool reuse_mmap = true;
+
+    long pagesize = _find_pagesize();
+    memory_file = strdup(file);
+    mmap_fd = open(file, O_RDWR|O_CREAT, S_IRWXU);
+    if (mmap_fd == -1) {
+        perror("failed to open file for mmap");
+        abort();
+    }
+    if (ftruncate(mmap_fd, limit) != 0) {
+        perror("ftruncate failed");
+        abort();
+    }
+    /* Allocate everything in a big chunk with malloc */
+    if (limit % pagesize) {
+        // This is a sanity check; shouldn't ever be possible since we
+        // increase memory by whole megabytes.
+        fprintf(stderr, "[restart] memory limit not divisible evenly by pagesize (please report bug)\n");
+        abort();
+    }
+    mmap_base = mmap(NULL, limit, PROT_READ|PROT_WRITE, MAP_SHARED, mmap_fd, 0);
+    if (mmap_base == MAP_FAILED) {
+        perror("failed to mmap, aborting");
+        abort();
+    }
+    // Set the limit before calling check_mmap, so we can find the meta page..
+    slabmem_limit = limit;
+    if (restart_check(file) != 0) {
+        reuse_mmap = false;
+    }
+    *mem_base = mmap_base;
+
+    return reuse_mmap;
+}
+
+/* Gracefully stop/close the shared memory segment */
+void restart_mmap_close(void) {
+    msync(mmap_base, slabmem_limit, MS_SYNC);
+
+    if (restart_save(memory_file) != 0) {
+        fprintf(stderr, "[restart] failed to save metadata");
+    }
+
+    if (munmap(mmap_base, slabmem_limit) != 0) {
+        perror("[restart] failed to munmap shared memory");
+    } else if (close(mmap_fd) != 0) {
+        perror("[restart] failed to close shared memory fd");
+    }
+
+    free(memory_file);
+}
+
+// given memory base, quickly walk memory and do pointer fixup.
+// do this once on startup to avoid having to do pointer fixup on every
+// reference from hash table or LRU.
+unsigned int restart_fixup(void *orig_addr) {
+    struct timeval tv;
+    uint64_t checked = 0;
+    const unsigned int page_size = settings.slab_page_size;
+    unsigned int page_remain = page_size;
+
+    gettimeofday(&tv, NULL);
+    if (settings.verbose > 0) {
+        fprintf(stderr, "[restart] original memory base: [%p] new base: [%p]\n", orig_addr, mmap_base);
+        fprintf(stderr, "[restart] recovery start [%d.%d]\n", (int)tv.tv_sec, (int)tv.tv_usec);
+    }
+
+    // since chunks don't align with pages, we have to also track page size.
+    while (checked < slabmem_limit) {
+        //fprintf(stderr, "checked: %lu\n", checked);
+        item *it = (item *)((char *)mmap_base + checked);
+
+        int size = slabs_fixup((char *)mmap_base + checked,
+                checked % settings.slab_page_size);
+        //fprintf(stderr, "id: %d, size: %d\n", it->slabs_clsid, size);
+        // slabber gobbled an entire page, skip and move on.
+        if (size == -1) {
+            assert(page_remain % page_size == 0);
+            assert(page_remain == page_size);
+            checked += page_remain;
+            page_remain = page_size;
+            continue;
+        }
+
+        if (it->it_flags & ITEM_LINKED) {
+            // fixup next/prev links while on LRU.
+            if (it->next) {
+                it->next = (item *)((mc_ptr_t)it->next - (mc_ptr_t)orig_addr);
+                it->next = (item *)((mc_ptr_t)it->next + (mc_ptr_t)mmap_base);
+            }
+            if (it->prev) {
+                it->prev = (item *)((mc_ptr_t)it->prev - (mc_ptr_t)orig_addr);
+                it->prev = (item *)((mc_ptr_t)it->prev + (mc_ptr_t)mmap_base);
+            }
+
+            //fprintf(stderr, "item was linked\n");
+            do_item_link_fixup(it);
+        }
+
+        if (it->it_flags & (ITEM_CHUNKED|ITEM_CHUNK)) {
+            item_chunk *ch;
+            if (it->it_flags & ITEM_CHUNKED) {
+                ch = (item_chunk *) ITEM_schunk(it);
+                // Sigh. Chunked items are a hack; the clsid is the clsid of
+                // the full object (always the largest slab class) rather than
+                // the actual chunk.
+                // I bet this is fixable :(
+                size = slabs_size(ch->orig_clsid);
+                //fprintf(stderr, "fixing chunked item header [%d]\n", size);
+            } else {
+                //fprintf(stderr, "fixing item chunk [%d]\n", size);
+                ch = (item_chunk *) it;
+            }
+            if (ch->next) {
+                ch->next = (item_chunk *)((mc_ptr_t)ch->next - (mc_ptr_t)orig_addr);
+                ch->next = (item_chunk *)((mc_ptr_t)ch->next + (mc_ptr_t)mmap_base);
+            }
+            if (ch->prev) {
+                ch->prev = (item_chunk *)((mc_ptr_t)ch->prev - (mc_ptr_t)orig_addr);
+                ch->prev = (item_chunk *)((mc_ptr_t)ch->prev + (mc_ptr_t)mmap_base);
+            }
+            if (ch->head) {
+                ch->head = (item *)((mc_ptr_t)ch->head - (mc_ptr_t)orig_addr);
+                ch->head = (item *)((mc_ptr_t)ch->head + (mc_ptr_t)mmap_base);
+            }
+        }
+
+        // next chunk
+        checked += size;
+        page_remain -= size;
+        if (size > page_remain) {
+            //fprintf(stderr, "doot %d\n", page_remain);
+            checked += page_remain;
+            page_remain = settings.slab_page_size;
+        }
+        //assert(checked != 3145728);
+    }
+
+    if (settings.verbose > 0) {
+        gettimeofday(&tv, NULL);
+        fprintf(stderr, "[restart] recovery end [%d.%d]\n", (int)tv.tv_sec, (int)tv.tv_usec);
+    }
+
+    return 0;
+}
diff --git a/cachelib/external/memcached/restart.h b/cachelib/external/memcached/restart.h
new file mode 100644
index 0000000000..e35eaf238f
--- /dev/null
+++ b/cachelib/external/memcached/restart.h
@@ -0,0 +1,28 @@
+#ifndef RESTART_H
+#define RESTART_H
+
+#define RESTART_TAG_MAXLEN 255
+
+// Track the pointer size for restart fiddling.
+#if SIZEOF_VOID_P == 8
+    typedef uint64_t mc_ptr_t;
+#else
+    typedef uint32_t mc_ptr_t;
+#endif
+
+enum restart_get_kv_ret {
+    RESTART_OK=0, RESTART_NOTAG, RESTART_BADLINE, RESTART_DONE
+};
+
+typedef int (*restart_check_cb)(const char *tag, void *ctx, void *data);
+typedef int (*restart_save_cb)(const char *tag, void *ctx, void *data);
+void restart_register(const char *tag, restart_check_cb ccb, restart_save_cb scb, void *data);
+
+void restart_set_kv(void *ctx, const char *key, const char *fmt, ...);
+enum restart_get_kv_ret restart_get_kv(void *ctx, char **key, char **val);
+
+bool restart_mmap_open(const size_t limit, const char *file, void **mem_base);
+void restart_mmap_close(void);
+unsigned int restart_fixup(void *old_base);
+
+#endif
diff --git a/cachelib/external/memcached/sasl_defs.c b/cachelib/external/memcached/sasl_defs.c
new file mode 100644
index 0000000000..e73a5707ac
--- /dev/null
+++ b/cachelib/external/memcached/sasl_defs.c
@@ -0,0 +1,223 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "memcached.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sasl/saslplug.h>
+
+char my_sasl_hostname[1025];
+
+#if defined(HAVE_SASL_CB_GETCONF) || defined(HAVE_SASL_CB_GETCONFPATH)
+/* The locations we may search for a SASL config file if the user didn't
+ * specify one in the environment variable SASL_CONF_PATH
+ */
+const char * const locations[] = {
+    "/etc/sasl/memcached.conf",
+    "/etc/sasl2/memcached.conf",
+    NULL
+};
+
+/* If the element of locations is file, locations_dir_path stores the
+ * directory path of these elements */
+const char *const locations_dir_path[] = {
+    "/etc/sasl",
+    "/etc/sasl2",
+    NULL
+};
+
+/* If the element of locations is directory, locations_file_path stores
+ * the actual configure file which used by sasl, when GETCONFPATH is
+ * enabled */
+const char *const locations_file_path[] = {
+    "/etc/sasl/memcached.conf/memcached.conf",
+    "/etc/sasl2/memcached.conf/memcached.conf",
+    NULL
+};
+#endif
+
+#ifndef HAVE_SASL_CALLBACK_FT
+typedef int (*sasl_callback_ft)(void);
+#endif
+
+#ifdef ENABLE_SASL_PWDB
+#define MAX_ENTRY_LEN 256
+
+static const char *memcached_sasl_pwdb;
+
+static int sasl_server_userdb_checkpass(sasl_conn_t *conn,
+                                        void *context,
+                                        const char *user,
+                                        const char *pass,
+                                        unsigned passlen,
+                                        struct propctx *propctx)
+{
+    size_t unmlen = strlen(user);
+    if ((passlen + unmlen) > (MAX_ENTRY_LEN - 4)) {
+        fprintf(stderr,
+                "WARNING: Failed to authenticate <%s> due to too long password (%d)\n",
+                user, passlen);
+        return SASL_NOAUTHZ;
+    }
+
+    FILE *pwfile = fopen(memcached_sasl_pwdb, "r");
+    if (pwfile == NULL) {
+        if (settings.verbose) {
+            vperror("WARNING: Failed to open sasl database <%s>",
+                    memcached_sasl_pwdb);
+        }
+        return SASL_NOAUTHZ;
+    }
+
+    char buffer[MAX_ENTRY_LEN];
+    bool ok = false;
+
+    while ((fgets(buffer, sizeof(buffer), pwfile)) != NULL) {
+        if (memcmp(user, buffer, unmlen) == 0 && buffer[unmlen] == ':') {
+            /* This is the correct user */
+            ++unmlen;
+            if (memcmp(pass, buffer + unmlen, passlen) == 0 &&
+                (buffer[unmlen + passlen] == ':' || /* Additional tokens */
+                 buffer[unmlen + passlen] == '\n' || /* end of line */
+                 buffer[unmlen + passlen] == '\r'|| /* dos format? */
+                 buffer[unmlen + passlen] == '\0')) { /* line truncated */
+                ok = true;
+            }
+
+            break;
+        }
+    }
+    (void)fclose(pwfile);
+    if (ok) {
+        return SASL_OK;
+    }
+
+    if (settings.verbose) {
+        fprintf(stderr, "INFO: User <%s> failed to authenticate\n", user);
+    }
+
+    return SASL_NOAUTHZ;
+}
+#endif
+
+#if defined(HAVE_SASL_CB_GETCONF) || defined(HAVE_SASL_CB_GETCONFPATH)
+static int sasl_getconf(void *context, const char **path)
+{
+    *path = getenv("SASL_CONF_PATH");
+
+    if (*path == NULL) {
+#if defined(HAVE_SASL_CB_GETCONF)
+        for (int i = 0; locations[i] != NULL; ++i) {
+            if (access(locations[i], F_OK) == 0) {
+                *path = locations[i];
+                break;
+            }
+        }
+#elif defined(HAVE_SASL_CB_GETCONFPATH)
+        for (int i = 0; locations[i] != NULL; ++i) {
+            if (access(locations_file_path[i], F_OK) == 0) {
+                *path = locations[i];
+                break;
+            } else if (access(locations[i], F_OK) == 0) {
+                *path = locations_dir_path[i];
+                break;
+            }
+        }
+#endif
+    }
+
+    if (settings.verbose) {
+        if (*path != NULL) {
+            fprintf(stderr, "Reading configuration from: <%s>\n", *path);
+        } else {
+            fprintf(stderr, "Failed to locate a config path\n");
+        }
+
+    }
+
+    return (*path != NULL) ? SASL_OK : SASL_FAIL;
+}
+#endif
+
+static int sasl_log(void *context, int level, const char *message)
+{
+    bool log = true;
+
+    switch (level) {
+    case SASL_LOG_NONE:
+        log = false;
+        break;
+    case SASL_LOG_PASS:
+    case SASL_LOG_TRACE:
+    case SASL_LOG_DEBUG:
+    case SASL_LOG_NOTE:
+        if (settings.verbose < 2) {
+            log = false;
+        }
+        break;
+    case SASL_LOG_WARN:
+    case SASL_LOG_FAIL:
+        if (settings.verbose < 1) {
+            log = false;
+        }
+        break;
+    default:
+        /* This is an error */
+        ;
+    }
+
+    if (log) {
+        fprintf(stderr, "SASL (severity %d): %s\n", level, message);
+    }
+
+    return SASL_OK;
+}
+
+static sasl_callback_t sasl_callbacks[] = {
+#ifdef ENABLE_SASL_PWDB
+   { SASL_CB_SERVER_USERDB_CHECKPASS, (sasl_callback_ft)sasl_server_userdb_checkpass, NULL },
+#endif
+
+   { SASL_CB_LOG, (sasl_callback_ft)sasl_log, NULL },
+
+#ifdef HAVE_SASL_CB_GETCONF
+   { SASL_CB_GETCONF, sasl_getconf, NULL },
+#else
+#ifdef HAVE_SASL_CB_GETCONFPATH
+   { SASL_CB_GETCONFPATH, (sasl_callback_ft)sasl_getconf, NULL },
+#endif
+#endif
+
+   { SASL_CB_LIST_END, NULL, NULL }
+};
+
+void init_sasl(void) {
+#ifdef ENABLE_SASL_PWDB
+    memcached_sasl_pwdb = getenv("MEMCACHED_SASL_PWDB");
+    if (memcached_sasl_pwdb == NULL) {
+       if (settings.verbose) {
+          fprintf(stderr,
+                  "INFO: MEMCACHED_SASL_PWDB not specified. "
+                  "Internal passwd database disabled\n");
+       }
+       sasl_callbacks[0].id = SASL_CB_LIST_END;
+       sasl_callbacks[0].proc = NULL;
+    }
+#endif
+
+    memset(my_sasl_hostname, 0, sizeof(my_sasl_hostname));
+    if (gethostname(my_sasl_hostname, sizeof(my_sasl_hostname)-1) == -1) {
+        if (settings.verbose) {
+            fprintf(stderr, "Error discovering hostname for SASL\n");
+        }
+        my_sasl_hostname[0] = '\0';
+    }
+
+    if (sasl_server_init(sasl_callbacks, "memcached") != SASL_OK) {
+        fprintf(stderr, "Error initializing sasl.\n");
+        exit(EXIT_FAILURE);
+    } else {
+        if (settings.verbose) {
+            fprintf(stderr, "Initialized SASL.\n");
+        }
+    }
+}
diff --git a/cachelib/external/memcached/sasl_defs.h b/cachelib/external/memcached/sasl_defs.h
new file mode 100644
index 0000000000..f36c694432
--- /dev/null
+++ b/cachelib/external/memcached/sasl_defs.h
@@ -0,0 +1,31 @@
+#ifndef SASL_DEFS_H
+#define SASL_DEFS_H 1
+
+// Longest one I could find was ``9798-U-RSA-SHA1-ENC''
+#define MAX_SASL_MECH_LEN 32
+
+#if defined(HAVE_SASL_SASL_H) && defined(ENABLE_SASL)
+
+#include <sasl/sasl.h>
+void init_sasl(void);
+
+extern char my_sasl_hostname[1025];
+
+#else /* End of SASL support */
+
+typedef void* sasl_conn_t;
+
+#define init_sasl() {}
+#define sasl_dispose(x) {}
+#define sasl_server_new(a, b, c, d, e, f, g, h) 1
+#define sasl_listmech(a, b, c, d, e, f, g, h) 1
+#define sasl_server_start(a, b, c, d, e, f) 1
+#define sasl_server_step(a, b, c, d, e) 1
+#define sasl_getprop(a, b, c) {}
+
+#define SASL_OK 0
+#define SASL_CONTINUE -1
+
+#endif /* sasl compat */
+
+#endif /* SASL_DEFS_H */
diff --git a/cachelib/external/memcached/sizes.c b/cachelib/external/memcached/sizes.c
new file mode 100644
index 0000000000..4fb5388171
--- /dev/null
+++ b/cachelib/external/memcached/sizes.c
@@ -0,0 +1,32 @@
+#include <stdio.h>
+
+#include "memcached.h"
+
+static void display(const char *name, size_t size) {
+    printf("%s\t%d\n", name, (int)size);
+}
+
+int main(int argc, char **argv) {
+
+    display("Slab Stats", sizeof(struct slab_stats));
+    display("Thread stats",
+            sizeof(struct thread_stats)
+            - (200 * sizeof(struct slab_stats)));
+    display("Global stats", sizeof(struct stats));
+    display("Settings", sizeof(struct settings));
+    display("Item (no cas)", sizeof(item));
+    display("Item (cas)", sizeof(item) + sizeof(uint64_t));
+#ifdef EXTSTORE
+    display("extstore header", sizeof(item_hdr));
+#endif
+    display("Libevent thread",
+            sizeof(LIBEVENT_THREAD) - sizeof(struct thread_stats));
+    display("Connection", sizeof(conn));
+
+    printf("----------------------------------------\n");
+
+    display("libevent thread cumulative", sizeof(LIBEVENT_THREAD));
+    display("Thread stats cumulative\t", sizeof(struct thread_stats));
+
+    return 0;
+}
diff --git a/cachelib/external/memcached/slab_automove.c b/cachelib/external/memcached/slab_automove.c
new file mode 100644
index 0000000000..25a6bdb137
--- /dev/null
+++ b/cachelib/external/memcached/slab_automove.c
@@ -0,0 +1,152 @@
+/*  Copyright 2017 Facebook.
+ *
+ *  Use and distribution licensed under the BSD license.  See
+ *  the LICENSE file for full text.
+ */
+
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "memcached.h"
+#include "slab_automove.h"
+#include <stdlib.h>
+#include <string.h>
+
+#define MIN_PAGES_FOR_SOURCE 2
+#define MIN_PAGES_FOR_RECLAIM 2.5
+
+struct window_data {
+    uint64_t age;
+    uint64_t dirty;
+    uint64_t evicted;
+};
+
+typedef struct {
+    struct window_data *window_data;
+    uint32_t window_size;
+    uint32_t window_cur;
+    double max_age_ratio;
+    item_stats_automove iam_before[MAX_NUMBER_OF_SLAB_CLASSES];
+    item_stats_automove iam_after[MAX_NUMBER_OF_SLAB_CLASSES];
+    slab_stats_automove sam_before[MAX_NUMBER_OF_SLAB_CLASSES];
+    slab_stats_automove sam_after[MAX_NUMBER_OF_SLAB_CLASSES];
+} slab_automove;
+
+void *slab_automove_init(struct settings *settings) {
+    uint32_t window_size = settings->slab_automove_window;
+    double max_age_ratio = settings->slab_automove_ratio;
+    slab_automove *a = calloc(1, sizeof(slab_automove));
+    if (a == NULL)
+        return NULL;
+    a->window_data = calloc(window_size * MAX_NUMBER_OF_SLAB_CLASSES, sizeof(struct window_data));
+    a->window_size = window_size;
+    a->max_age_ratio = max_age_ratio;
+    if (a->window_data == NULL) {
+        free(a);
+        return NULL;
+    }
+
+    // do a dry run to fill the before structs
+    fill_item_stats_automove(a->iam_before);
+    fill_slab_stats_automove(a->sam_before);
+
+    return (void *)a;
+}
+
+void slab_automove_free(void *arg) {
+    slab_automove *a = (slab_automove *)arg;
+    free(a->window_data);
+    free(a);
+}
+
+static void window_sum(struct window_data *wd, struct window_data *w, uint32_t size) {
+    int x;
+    for (x = 0; x < size; x++) {
+        struct window_data *d = &wd[x];
+        w->age += d->age;
+        w->dirty += d->dirty;
+        w->evicted += d->evicted;
+    }
+}
+
+// TODO: if oldest is dirty, find next oldest.
+// still need to base ratio off of absolute age
+void slab_automove_run(void *arg, int *src, int *dst) {
+    slab_automove *a = (slab_automove *)arg;
+    int n;
+    struct window_data w_sum;
+    int oldest = -1;
+    uint64_t oldest_age = 0;
+    int youngest = -1;
+    uint64_t youngest_age = ~0;
+    bool youngest_evicting = false;
+    *src = -1;
+    *dst = -1;
+
+    // fill after structs
+    fill_item_stats_automove(a->iam_after);
+    fill_slab_stats_automove(a->sam_after);
+    a->window_cur++;
+
+    // iterate slabs
+    for (n = POWER_SMALLEST; n < MAX_NUMBER_OF_SLAB_CLASSES; n++) {
+        int w_offset = n * a->window_size;
+        struct window_data *wd = &a->window_data[w_offset + (a->window_cur % a->window_size)];
+        memset(wd, 0, sizeof(struct window_data));
+        // summarize the window-up-to-now.
+        memset(&w_sum, 0, sizeof(struct window_data));
+        window_sum(&a->window_data[w_offset], &w_sum, a->window_size);
+
+        // if page delta, or evicted delta, mark window dirty
+        // (or outofmemory)
+        if (a->iam_after[n].evicted - a->iam_before[n].evicted > 0 ||
+            a->iam_after[n].outofmemory - a->iam_before[n].outofmemory > 0) {
+            wd->evicted = 1;
+            wd->dirty = 1;
+        }
+        if (a->sam_after[n].total_pages - a->sam_before[n].total_pages > 0) {
+            wd->dirty = 1;
+        }
+
+        // set age into window
+        wd->age = a->iam_after[n].age;
+
+        // grab age as average of window total
+        uint64_t age = w_sum.age / a->window_size;
+
+        // if > N free chunks and not dirty, make decision.
+        if (a->sam_after[n].free_chunks > a->sam_after[n].chunks_per_page * MIN_PAGES_FOR_RECLAIM) {
+            if (w_sum.dirty == 0) {
+                *src = n;
+                *dst = 0;
+                break;
+            }
+        }
+
+        // if oldest and have enough pages, is oldest
+        if (age > oldest_age && a->sam_after[n].total_pages > MIN_PAGES_FOR_SOURCE) {
+            oldest = n;
+            oldest_age = age;
+        }
+
+        // grab evicted count from window
+        // if > half the window and youngest, mark as youngest
+        if (age < youngest_age && w_sum.evicted > a->window_size / 2) {
+            youngest = n;
+            youngest_age = age;
+            youngest_evicting = wd->evicted ? true : false;
+        }
+    }
+
+    memcpy(a->iam_before, a->iam_after,
+            sizeof(item_stats_automove) * MAX_NUMBER_OF_SLAB_CLASSES);
+    memcpy(a->sam_before, a->sam_after,
+            sizeof(slab_stats_automove) * MAX_NUMBER_OF_SLAB_CLASSES);
+    // if we have a youngest and oldest, and oldest is outside the ratio,
+    // also, only make decisions if window has filled once.
+    if (youngest != -1 && oldest != -1 && a->window_cur > a->window_size) {
+        if (youngest_age < ((double)oldest_age * a->max_age_ratio) && youngest_evicting) {
+            *src = oldest;
+            *dst = youngest;
+        }
+    }
+    return;
+}
diff --git a/cachelib/external/memcached/slab_automove.h b/cachelib/external/memcached/slab_automove.h
new file mode 100644
index 0000000000..226d68dc93
--- /dev/null
+++ b/cachelib/external/memcached/slab_automove.h
@@ -0,0 +1,19 @@
+#ifndef SLAB_AUTOMOVE_H
+#define SLAB_AUTOMOVE_H
+
+/* default automove functions */
+void *slab_automove_init(struct settings *settings);
+void slab_automove_free(void *arg);
+void slab_automove_run(void *arg, int *src, int *dst);
+
+typedef void *(*slab_automove_init_func)(struct settings *settings);
+typedef void (*slab_automove_free_func)(void *arg);
+typedef void (*slab_automove_run_func)(void *arg, int *src, int *dst);
+
+typedef struct {
+    slab_automove_init_func init;
+    slab_automove_free_func free;
+    slab_automove_run_func run;
+} slab_automove_reg_t;
+
+#endif
diff --git a/cachelib/external/memcached/slab_automove_extstore.c b/cachelib/external/memcached/slab_automove_extstore.c
new file mode 100644
index 0000000000..38312793d3
--- /dev/null
+++ b/cachelib/external/memcached/slab_automove_extstore.c
@@ -0,0 +1,298 @@
+/*  Copyright 2017 Facebook.
+ *
+ *  Use and distribution licensed under the BSD license.  See
+ *  the LICENSE file for full text.
+ */
+
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "memcached.h"
+#include "slab_automove_extstore.h"
+#include <stdlib.h>
+#include <string.h>
+
+#define MIN_PAGES_FOR_SOURCE 2
+#define MIN_PAGES_FOR_RECLAIM 2.5
+#define MIN_PAGES_FREE 1.5
+#define MEMCHECK_PERIOD 60
+
+struct window_data {
+    uint64_t age;
+    uint64_t dirty;
+    uint64_t evicted;
+    unsigned int excess_free;
+    unsigned int relaxed;
+};
+
+struct window_global {
+    uint32_t pool_low;
+    uint32_t pool_high;
+};
+
+typedef struct {
+    struct window_data *window_data;
+    struct window_global *window_global;
+    struct settings *settings;
+    uint32_t window_size;
+    uint32_t window_cur;
+    uint32_t item_size;
+    rel_time_t last_memcheck_run;
+    double max_age_ratio;
+    double free_ratio;
+    bool pool_filled_once;
+    unsigned int free_mem[MAX_NUMBER_OF_SLAB_CLASSES];
+    item_stats_automove iam_before[MAX_NUMBER_OF_SLAB_CLASSES];
+    item_stats_automove iam_after[MAX_NUMBER_OF_SLAB_CLASSES];
+    slab_stats_automove sam_before[MAX_NUMBER_OF_SLAB_CLASSES];
+    slab_stats_automove sam_after[MAX_NUMBER_OF_SLAB_CLASSES];
+} slab_automove;
+
+void *slab_automove_extstore_init(struct settings *settings) {
+    uint32_t window_size = settings->slab_automove_window;
+    double max_age_ratio = settings->slab_automove_ratio;
+    slab_automove *a = calloc(1, sizeof(slab_automove));
+    if (a == NULL)
+        return NULL;
+    a->window_data = calloc(window_size * MAX_NUMBER_OF_SLAB_CLASSES, sizeof(struct window_data));
+    a->window_global = calloc(window_size, sizeof(struct window_global));
+    a->window_size = window_size;
+    a->max_age_ratio = max_age_ratio;
+    a->free_ratio = settings->slab_automove_freeratio;
+    a->item_size = settings->ext_item_size;
+    a->last_memcheck_run = 0;
+    a->settings = settings;
+    a->pool_filled_once = false;
+    if (a->window_data == NULL || a->window_global == NULL) {
+        if (a->window_data)
+            free(a->window_data);
+        if (a->window_global)
+            free(a->window_global);
+        free(a);
+        return NULL;
+    }
+
+    // do a dry run to fill the before structs
+    fill_item_stats_automove(a->iam_before);
+    fill_slab_stats_automove(a->sam_before);
+
+    return (void *)a;
+}
+
+void slab_automove_extstore_free(void *arg) {
+    slab_automove *a = (slab_automove *)arg;
+    free(a->window_data);
+    free(a->window_global);
+    free(a);
+}
+
+static void window_sum(struct window_data *wd, struct window_data *w,
+        uint32_t size) {
+    for (int x = 0; x < size; x++) {
+        struct window_data *d = &wd[x];
+        w->age += d->age;
+        w->dirty += d->dirty;
+        w->evicted += d->evicted;
+        w->excess_free += d->excess_free;
+        w->relaxed += d->relaxed;
+    }
+}
+
+/* This could potentially merge with above */
+static void window_global_sum(struct window_global *wg,
+        struct window_global *w, uint32_t size) {
+    for (int x = 0; x < size; x++) {
+        struct window_global *d = &wg[x];
+        w->pool_high += d->pool_high;
+        w->pool_low += d->pool_low;
+    }
+}
+
+static void global_pool_check(slab_automove *a) {
+    bool mem_limit_reached;
+    uint32_t free = a->free_mem[0];
+    struct window_global *wg = &a->window_global[a->window_cur % a->window_size];
+    unsigned int count = global_page_pool_size(&mem_limit_reached);
+    memset(wg, 0, sizeof(struct window_global));
+    if (!mem_limit_reached)
+        return;
+    if (count < free / 2) {
+        wg->pool_low = 1;
+        a->pool_filled_once = true;
+    } else if (count > free) {
+        wg->pool_high = 1;
+    } else {
+        a->pool_filled_once = true;
+    }
+}
+
+/* A percentage of memory is configured to be held "free" as buffers for the
+ * external storage system.
+ * % of global memory is desired in the global page pool
+ * each slab class has a % of free chunks desired based on how much memory is
+ * currently in the class. This allows time for extstore to flush data when
+ * spikes or waves of set data arrive.
+ * The global page pool reserve acts as a secondary buffer for any slab class,
+ * which helps absorb shifts in which class is active.
+ */
+static void memcheck(slab_automove *a) {
+    unsigned int total_pages = 0;
+    if (current_time < a->last_memcheck_run + MEMCHECK_PERIOD)
+        return;
+    a->last_memcheck_run = current_time;
+    for (int n = 1; n < MAX_NUMBER_OF_SLAB_CLASSES; n++) {
+        slab_stats_automove *sam = &a->sam_after[n];
+        total_pages += sam->total_pages;
+        unsigned int hold_free = (sam->total_pages * sam->chunks_per_page)
+            * a->free_ratio;
+        if (sam->chunks_per_page * MIN_PAGES_FREE > hold_free)
+            hold_free = sam->chunks_per_page * MIN_PAGES_FREE;
+        a->free_mem[n] = hold_free;
+        if (a->settings->ext_free_memchunks[n] != hold_free && a->pool_filled_once) {
+            a->settings->ext_free_memchunks[n] = hold_free;
+        }
+    }
+    // remember to add what remains in global pool.
+    total_pages += a->sam_after[0].total_pages;
+    a->free_mem[0] = total_pages * a->free_ratio;
+}
+
+static struct window_data *get_window_data(slab_automove *a, int class) {
+    int w_offset = class * a->window_size;
+    return &a->window_data[w_offset + (a->window_cur % a->window_size)];
+}
+
+void slab_automove_extstore_run(void *arg, int *src, int *dst) {
+    slab_automove *a = (slab_automove *)arg;
+    int n;
+    struct window_data w_sum;
+    int oldest = -1;
+    uint64_t oldest_age = 0;
+    int youngest = -1;
+    uint64_t youngest_age = ~0;
+    bool too_free = false;
+    *src = -1;
+    *dst = -1;
+
+    global_pool_check(a);
+    struct window_global wg_sum;
+    memset(&wg_sum, 0, sizeof(struct window_global));
+    window_global_sum(a->window_global, &wg_sum, a->window_size);
+    // fill after structs
+    fill_item_stats_automove(a->iam_after);
+    fill_slab_stats_automove(a->sam_after);
+    a->window_cur++;
+
+    memcheck(a);
+
+    // iterate slabs
+    for (n = POWER_SMALLEST; n < MAX_NUMBER_OF_SLAB_CLASSES; n++) {
+        bool small_slab = a->sam_before[n].chunk_size < a->item_size
+            ? true : false;
+        bool free_enough = false;
+        struct window_data *wd = get_window_data(a, n);
+        // summarize the window-up-to-now.
+        memset(&w_sum, 0, sizeof(struct window_data));
+        int w_offset = n * a->window_size;
+        window_sum(&a->window_data[w_offset], &w_sum, a->window_size);
+        memset(wd, 0, sizeof(struct window_data));
+
+        // if page delta, oom, or evicted delta, mark window dirty
+        // classes marked dirty cannot donate memory back to global pool.
+        if (a->iam_after[n].evicted - a->iam_before[n].evicted > 0 ||
+            a->iam_after[n].outofmemory - a->iam_before[n].outofmemory > 0) {
+            wd->evicted = 1;
+            wd->dirty = 1;
+        }
+        if (a->sam_after[n].total_pages - a->sam_before[n].total_pages > 0) {
+            wd->dirty = 1;
+        }
+        // Mark excess free if we're over the free mem limit for too long.
+        // "free_enough" means it is either wobbling, recently received a new
+        // page of memory, or the crawler is freeing memory.
+        if (a->sam_after[n].free_chunks > a->free_mem[n]) {
+            free_enough = true;
+        }
+        // double the free requirements means we may have memory we can
+        // reclaim to global, if it stays this way for the whole window.
+        if (a->sam_after[n].free_chunks > (a->free_mem[n] * 2) && a->free_mem[n] > 0) {
+            wd->excess_free = 1;
+        }
+
+        // set age into window
+        wd->age = a->iam_after[n].age;
+
+        // grab age as average of window total
+        uint64_t age = w_sum.age / a->window_size;
+
+        // if > N free chunks and not dirty, reclaim memory
+        // small slab classes aren't age balanced and rely more on global
+        // pool. reclaim them more aggressively.
+        if (a->sam_after[n].free_chunks > a->sam_after[n].chunks_per_page * MIN_PAGES_FOR_RECLAIM
+                && w_sum.dirty == 0) {
+            if (small_slab) {
+                *src = n;
+                *dst = 0;
+                too_free = true;
+            } else if (!small_slab && w_sum.excess_free >= a->window_size) {
+                // If large slab and free chunks haven't decreased for a full
+                // window, reclaim pages.
+                *src = n;
+                *dst = 0;
+                too_free = true;
+            }
+        }
+
+        if (!small_slab) {
+            // if oldest and have enough pages, is oldest
+            if (age > oldest_age
+                    && a->sam_after[n].total_pages > MIN_PAGES_FOR_SOURCE) {
+                oldest = n;
+                oldest_age = age;
+            }
+
+            // don't count as youngest if it hasn't been using new chunks.
+            // (if it was relaxed recently, and is currently "free enough")
+            if (age < youngest_age && a->sam_after[n].total_pages != 0
+                    && w_sum.excess_free < a->window_size
+                    && !(w_sum.relaxed && free_enough)) {
+                youngest = n;
+                youngest_age = age;
+            }
+        }
+    }
+
+    memcpy(a->iam_before, a->iam_after,
+            sizeof(item_stats_automove) * MAX_NUMBER_OF_SLAB_CLASSES);
+    memcpy(a->sam_before, a->sam_after,
+            sizeof(slab_stats_automove) * MAX_NUMBER_OF_SLAB_CLASSES);
+    // only make decisions if window has filled once.
+    if (a->window_cur < a->window_size)
+        return;
+
+    if (wg_sum.pool_high >= a->window_size && !wg_sum.pool_low && youngest != -1) {
+        if (a->sam_after[youngest].free_chunks <= a->free_mem[youngest]) {
+            *src = 0;
+            *dst = youngest;
+        }
+        struct window_data *wd = get_window_data(a, youngest);
+        // "relaxing" here and below allows us to skip classes which will
+        // never grow or are growing slowly, more quickly finding other
+        // classes which violate the age ratio.
+        wd->relaxed = 1;
+    } else if (!too_free && wg_sum.pool_low && oldest != -1) {
+        *src = oldest;
+        *dst = 0;
+    } else if (!too_free && youngest != -1 && oldest != -1 && youngest != oldest) {
+        // if we have a youngest and oldest, and oldest is outside the ratio.
+        if (youngest_age < ((double)oldest_age * a->max_age_ratio)) {
+            struct window_data *wd = get_window_data(a, youngest);
+            wd->relaxed = 1;
+            // only actually assign more memory if it's absorbed what it has
+            if (a->sam_after[youngest].free_chunks <= a->free_mem[youngest]) {
+                *src = 0;
+                *dst = youngest;
+
+            }
+        }
+    }
+    return;
+}
diff --git a/cachelib/external/memcached/slab_automove_extstore.h b/cachelib/external/memcached/slab_automove_extstore.h
new file mode 100644
index 0000000000..8b437b1e8a
--- /dev/null
+++ b/cachelib/external/memcached/slab_automove_extstore.h
@@ -0,0 +1,8 @@
+#ifndef SLAB_AUTOMOVE_EXTSTORE_H
+#define SLAB_AUTOMOVE_EXTSTORE_H
+
+void *slab_automove_extstore_init(struct settings *settings);
+void slab_automove_extstore_free(void *arg);
+void slab_automove_extstore_run(void *arg, int *src, int *dst);
+
+#endif
diff --git a/cachelib/external/memcached/slabs.c b/cachelib/external/memcached/slabs.c
new file mode 100644
index 0000000000..ca8a8f250a
--- /dev/null
+++ b/cachelib/external/memcached/slabs.c
@@ -0,0 +1,1336 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Slabs memory allocation, based on powers-of-N. Slabs are up to 1MB in size
+ * and are divided into chunks. The chunk sizes start off at the size of the
+ * "item" structure plus space for a small key and value. They increase by
+ * a multiplier factor from there, up to half the maximum slab size. The last
+ * slab size is always 1MB, since that's the maximum item size allowed by the
+ * memcached protocol.
+ */
+#include "memcached.h"
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <assert.h>
+#include <pthread.h>
+
+//#define DEBUG_SLAB_MOVER
+/* powers-of-N allocation structures */
+
+typedef struct {
+    unsigned int size;      /* sizes of items */
+    unsigned int perslab;   /* how many items per slab */
+
+    void *slots;           /* list of item ptrs */
+    unsigned int sl_curr;   /* total free items in list */
+
+    unsigned int slabs;     /* how many slabs were allocated for this class */
+
+    void **slab_list;       /* array of slab pointers */
+    unsigned int list_size; /* size of prev array */
+} slabclass_t;
+
+static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];
+static size_t mem_limit = 0;
+static size_t mem_malloced = 0;
+/* If the memory limit has been hit once. Used as a hint to decide when to
+ * early-wake the LRU maintenance thread */
+static bool mem_limit_reached = false;
+static int power_largest;
+
+static void *mem_base = NULL;
+static void *mem_current = NULL;
+static size_t mem_avail = 0;
+#ifdef EXTSTORE
+static void *storage  = NULL;
+#endif
+/**
+ * Access to the slab allocator is protected by this lock
+ */
+static pthread_mutex_t slabs_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t slabs_rebalance_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/*
+ * Forward Declarations
+ */
+static int grow_slab_list (const unsigned int id);
+static int do_slabs_newslab(const unsigned int id);
+static void *memory_allocate(size_t size);
+static void do_slabs_free(void *ptr, const size_t size, unsigned int id);
+
+/* Preallocate as many slab pages as possible (called from slabs_init)
+   on start-up, so users don't get confused out-of-memory errors when
+   they do have free (in-slab) space, but no space to make new slabs.
+   if maxslabs is 18 (POWER_LARGEST - POWER_SMALLEST + 1), then all
+   slab types can be made.  if max memory is less than 18 MB, only the
+   smaller ones will be made.  */
+static void slabs_preallocate (const unsigned int maxslabs);
+#ifdef EXTSTORE
+void slabs_set_storage(void *arg) {
+    storage = arg;
+}
+#endif
+/*
+ * Figures out which slab class (chunk size) is required to store an item of
+ * a given size.
+ *
+ * Given object size, return id to use when allocating/freeing memory for object
+ * 0 means error: can't store such a large object
+ */
+
+unsigned int slabs_clsid(const size_t size) {
+    int res = POWER_SMALLEST;
+
+    if (size == 0 || size > settings.item_size_max)
+        return 0;
+    while (size > slabclass[res].size)
+        if (res++ == power_largest)     /* won't fit in the biggest slab */
+            return power_largest;
+    return res;
+}
+
+unsigned int slabs_size(const int clsid) {
+    return slabclass[clsid].size;
+}
+
+// TODO: could this work with the restartable memory?
+// Docs say hugepages only work with private shm allocs.
+/* Function split out for better error path handling */
+static void * alloc_large_chunk(const size_t limit)
+{
+    void *ptr = NULL;
+#if defined(__linux__) && defined(MADV_HUGEPAGE)
+    size_t pagesize = 0;
+    FILE *fp;
+    int ret;
+
+    /* Get the size of huge pages */
+    fp = fopen("/proc/meminfo", "r");
+    if (fp != NULL) {
+        char buf[64];
+
+        while ((fgets(buf, sizeof(buf), fp)))
+            if (!strncmp(buf, "Hugepagesize:", 13)) {
+                ret = sscanf(buf + 13, "%zu\n", &pagesize);
+
+                /* meminfo huge page size is in KiBs */
+                pagesize <<= 10;
+            }
+        fclose(fp);
+    }
+
+    if (!pagesize) {
+        fprintf(stderr, "Failed to get supported huge page size\n");
+        return NULL;
+    }
+
+    if (settings.verbose > 1)
+        fprintf(stderr, "huge page size: %zu\n", pagesize);
+
+    /* This works because glibc simply uses mmap when the alignment is
+     * above a certain limit. */
+    ret = posix_memalign(&ptr, pagesize, limit);
+    if (ret != 0) {
+        fprintf(stderr, "Failed to get aligned memory chunk: %d\n", ret);
+        return NULL;
+    }
+
+    ret = madvise(ptr, limit, MADV_HUGEPAGE);
+    if (ret < 0) {
+        fprintf(stderr, "Failed to set transparent hugepage hint: %d\n", ret);
+        free(ptr);
+        ptr = NULL;
+    }
+#elif defined(__FreeBSD__)
+    size_t align = (sizeof(size_t) * 8 - (__builtin_clzl(4095)));
+    ptr = mmap(NULL, limit, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON | MAP_ALIGNED(align) | MAP_ALIGNED_SUPER, -1, 0);
+    if (ptr == MAP_FAILED) {
+        fprintf(stderr, "Failed to set super pages\n");
+        ptr = NULL;
+    }
+#else
+    ptr = malloc(limit);
+#endif
+    return ptr;
+}
+
+unsigned int slabs_fixup(char *chunk, const int border) {
+    slabclass_t *p;
+    item *it = (item *)chunk;
+    int id = ITEM_clsid(it);
+
+    // memory isn't used yet. shunt to global pool.
+    // (which must be 0)
+    if (id == 0) {
+        //assert(border == 0);
+        p = &slabclass[0];
+        grow_slab_list(0);
+        p->slab_list[p->slabs++] = (char*)chunk;
+        return -1;
+    }
+    p = &slabclass[id];
+
+    // if we're on a page border, add the slab to slab class
+    if (border == 0) {
+        grow_slab_list(id);
+        p->slab_list[p->slabs++] = chunk;
+    }
+
+    // increase free count if ITEM_SLABBED
+    if (it->it_flags == ITEM_SLABBED) {
+        // if ITEM_SLABBED re-stack on freelist.
+        // don't have to run pointer fixups.
+        it->prev = 0;
+        it->next = p->slots;
+        if (it->next) it->next->prev = it;
+        p->slots = it;
+
+        p->sl_curr++;
+        //fprintf(stderr, "replacing into freelist\n");
+    }
+
+    return p->size;
+}
+
+/**
+ * Determines the chunk sizes and initializes the slab class descriptors
+ * accordingly.
+ */
+void slabs_init(const size_t limit, const double factor, const bool prealloc, const uint32_t *slab_sizes, void *mem_base_external, bool reuse_mem) {
+    int i = POWER_SMALLEST - 1;
+    unsigned int size = sizeof(item) + settings.chunk_size;
+
+    /* Some platforms use runtime transparent hugepages. If for any reason
+     * the initial allocation fails, the required settings do not persist
+     * for remaining allocations. As such it makes little sense to do slab
+     * preallocation. */
+    bool __attribute__ ((unused)) do_slab_prealloc = false;
+
+    mem_limit = limit;
+
+    if (prealloc && mem_base_external == NULL) {
+        mem_base = alloc_large_chunk(mem_limit);
+        if (mem_base) {
+            do_slab_prealloc = true;
+            mem_current = mem_base;
+            mem_avail = mem_limit;
+        } else {
+            fprintf(stderr, "Warning: Failed to allocate requested memory in"
+                    " one large chunk.\nWill allocate in smaller chunks\n");
+        }
+    } else if (prealloc && mem_base_external != NULL) {
+        // Can't (yet) mix hugepages with mmap allocations, so separate the
+        // logic from above. Reusable memory also force-preallocates memory
+        // pages into the global pool, which requires turning mem_* variables.
+        do_slab_prealloc = true;
+        mem_base = mem_base_external;
+        // _current shouldn't be used in this case, but we set it to where it
+        // should be anyway.
+        if (reuse_mem) {
+            mem_current = ((char*)mem_base) + mem_limit;
+            mem_avail = 0;
+        } else {
+            mem_current = mem_base;
+            mem_avail = mem_limit;
+        }
+    }
+
+    memset(slabclass, 0, sizeof(slabclass));
+
+    while (++i < MAX_NUMBER_OF_SLAB_CLASSES-1) {
+        if (slab_sizes != NULL) {
+            if (slab_sizes[i-1] == 0)
+                break;
+            size = slab_sizes[i-1];
+        } else if (size >= settings.slab_chunk_size_max / factor) {
+            break;
+        }
+        /* Make sure items are always n-byte aligned */
+        if (size % CHUNK_ALIGN_BYTES)
+            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
+
+        slabclass[i].size = size;
+        slabclass[i].perslab = settings.slab_page_size / slabclass[i].size;
+        if (slab_sizes == NULL)
+            size *= factor;
+        if (settings.verbose > 1) {
+            fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
+                    i, slabclass[i].size, slabclass[i].perslab);
+        }
+    }
+
+    power_largest = i;
+    slabclass[power_largest].size = settings.slab_chunk_size_max;
+    slabclass[power_largest].perslab = settings.slab_page_size / settings.slab_chunk_size_max;
+    if (settings.verbose > 1) {
+        fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
+                i, slabclass[i].size, slabclass[i].perslab);
+    }
+
+    /* for the test suite:  faking of how much we've already malloc'd */
+    {
+        char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
+        if (t_initial_malloc) {
+            mem_malloced = (size_t)atol(t_initial_malloc);
+        }
+
+    }
+
+    if (do_slab_prealloc) {
+        if (!reuse_mem) {
+            slabs_preallocate(power_largest);
+        }
+    }
+}
+
+void slabs_prefill_global(void) {
+    void *ptr;
+    slabclass_t *p = &slabclass[0];
+    int len = settings.slab_page_size;
+
+    while (mem_malloced < mem_limit
+            && (ptr = memory_allocate(len)) != NULL) {
+        grow_slab_list(0);
+        // Ensure the front header is zero'd to avoid confusing restart code.
+        // It's probably good enough to cast it and just zero slabs_clsid, but
+        // this is extra paranoid.
+        memset(ptr, 0, sizeof(item));
+        p->slab_list[p->slabs++] = ptr;
+    }
+    mem_limit_reached = true;
+}
+
+static void slabs_preallocate (const unsigned int maxslabs) {
+    int i;
+    unsigned int prealloc = 0;
+
+    /* pre-allocate a 1MB slab in every size class so people don't get
+       confused by non-intuitive "SERVER_ERROR out of memory"
+       messages.  this is the most common question on the mailing
+       list.  if you really don't want this, you can rebuild without
+       these three lines.  */
+
+    for (i = POWER_SMALLEST; i < MAX_NUMBER_OF_SLAB_CLASSES; i++) {
+        if (++prealloc > maxslabs)
+            break;
+        if (do_slabs_newslab(i) == 0) {
+            fprintf(stderr, "Error while preallocating slab memory!\n"
+                "If using -L or other prealloc options, max memory must be "
+                "at least %d megabytes.\n", power_largest);
+            exit(1);
+        }
+    }
+}
+
+static int grow_slab_list (const unsigned int id) {
+    slabclass_t *p = &slabclass[id];
+    if (p->slabs == p->list_size) {
+        size_t new_size =  (p->list_size != 0) ? p->list_size * 2 : 16;
+        void *new_list = realloc(p->slab_list, new_size * sizeof(void *));
+        if (new_list == 0) return 0;
+        p->list_size = new_size;
+        p->slab_list = new_list;
+    }
+    return 1;
+}
+
+static void split_slab_page_into_freelist(char *ptr, const unsigned int id) {
+    slabclass_t *p = &slabclass[id];
+    int x;
+    for (x = 0; x < p->perslab; x++) {
+        do_slabs_free(ptr, 0, id);
+        ptr += p->size;
+    }
+}
+
+/* Fast FIFO queue */
+static void *get_page_from_global_pool(void) {
+    slabclass_t *p = &slabclass[SLAB_GLOBAL_PAGE_POOL];
+    if (p->slabs < 1) {
+        return NULL;
+    }
+    char *ret = p->slab_list[p->slabs - 1];
+    p->slabs--;
+    return ret;
+}
+
+static int do_slabs_newslab(const unsigned int id) {
+    slabclass_t *p = &slabclass[id];
+    slabclass_t *g = &slabclass[SLAB_GLOBAL_PAGE_POOL];
+    int len = (settings.slab_reassign || settings.slab_chunk_size_max != settings.slab_page_size)
+        ? settings.slab_page_size
+        : p->size * p->perslab;
+    char *ptr;
+
+    if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0
+         && g->slabs == 0)) {
+        mem_limit_reached = true;
+        MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
+        return 0;
+    }
+
+    if ((grow_slab_list(id) == 0) ||
+        (((ptr = get_page_from_global_pool()) == NULL) &&
+        ((ptr = memory_allocate((size_t)len)) == 0))) {
+
+        MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
+        return 0;
+    }
+
+    // Always wipe the memory at this stage: in restart mode the mmap memory
+    // could be unused, yet still full of data. Better for usability if we're
+    // wiping memory as it's being pulled out of the global pool instead of
+    // blocking startup all at once.
+    memset(ptr, 0, (size_t)len);
+    split_slab_page_into_freelist(ptr, id);
+
+    p->slab_list[p->slabs++] = ptr;
+    MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);
+
+    return 1;
+}
+
+/*@null@*/
+static void *do_slabs_alloc(const size_t size, unsigned int id,
+        unsigned int flags) {
+    slabclass_t *p;
+    void *ret = NULL;
+    item *it = NULL;
+
+    if (id < POWER_SMALLEST || id > power_largest) {
+        MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);
+        return NULL;
+    }
+    p = &slabclass[id];
+    assert(p->sl_curr == 0 || (((item *)p->slots)->it_flags & ITEM_SLABBED));
+
+    assert(size <= p->size);
+    /* fail unless we have space at the end of a recently allocated page,
+       we have something on our freelist, or we could allocate a new page */
+    if (p->sl_curr == 0 && flags != SLABS_ALLOC_NO_NEWPAGE) {
+        do_slabs_newslab(id);
+    }
+
+    if (p->sl_curr != 0) {
+        /* return off our freelist */
+        it = (item *)p->slots;
+        p->slots = it->next;
+        if (it->next) it->next->prev = 0;
+        /* Kill flag and initialize refcount here for lock safety in slab
+         * mover's freeness detection. */
+        it->it_flags &= ~ITEM_SLABBED;
+        it->refcount = 1;
+        p->sl_curr--;
+        ret = (void *)it;
+    } else {
+        ret = NULL;
+    }
+
+    if (ret) {
+        MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);
+    } else {
+        MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
+    }
+
+    return ret;
+}
+
+static void do_slabs_free_chunked(item *it, const size_t size) {
+    item_chunk *chunk = (item_chunk *) ITEM_schunk(it);
+    slabclass_t *p;
+
+    it->it_flags = ITEM_SLABBED;
+    // FIXME: refresh on how this works?
+    //it->slabs_clsid = 0;
+    it->prev = 0;
+    // header object's original classid is stored in chunk.
+    p = &slabclass[chunk->orig_clsid];
+    if (chunk->next) {
+        chunk = chunk->next;
+        chunk->prev = 0;
+    } else {
+        // header with no attached chunk
+        chunk = NULL;
+    }
+
+    // return the header object.
+    // TODO: This is in three places, here and in do_slabs_free().
+    it->prev = 0;
+    it->next = p->slots;
+    if (it->next) it->next->prev = it;
+    p->slots = it;
+    p->sl_curr++;
+
+    item_chunk *next_chunk;
+    while (chunk) {
+        assert(chunk->it_flags == ITEM_CHUNK);
+        chunk->it_flags = ITEM_SLABBED;
+        p = &slabclass[chunk->slabs_clsid];
+        next_chunk = chunk->next;
+
+        chunk->prev = 0;
+        chunk->next = p->slots;
+        if (chunk->next) chunk->next->prev = chunk;
+        p->slots = chunk;
+        p->sl_curr++;
+
+        chunk = next_chunk;
+    }
+
+    return;
+}
+
+
+static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
+    slabclass_t *p;
+    item *it;
+
+    assert(id >= POWER_SMALLEST && id <= power_largest);
+    if (id < POWER_SMALLEST || id > power_largest)
+        return;
+
+    MEMCACHED_SLABS_FREE(size, id, ptr);
+    p = &slabclass[id];
+
+    it = (item *)ptr;
+    if ((it->it_flags & ITEM_CHUNKED) == 0) {
+        it->it_flags = ITEM_SLABBED;
+        it->slabs_clsid = id;
+        it->prev = 0;
+        it->next = p->slots;
+        if (it->next) it->next->prev = it;
+        p->slots = it;
+
+        p->sl_curr++;
+    } else {
+        do_slabs_free_chunked(it, size);
+    }
+    return;
+}
+
+/* With refactoring of the various stats code the automover won't need a
+ * custom function here.
+ */
+void fill_slab_stats_automove(slab_stats_automove *am) {
+    int n;
+    pthread_mutex_lock(&slabs_lock);
+    for (n = 0; n < MAX_NUMBER_OF_SLAB_CLASSES; n++) {
+        slabclass_t *p = &slabclass[n];
+        slab_stats_automove *cur = &am[n];
+        cur->chunks_per_page = p->perslab;
+        cur->free_chunks = p->sl_curr;
+        cur->total_pages = p->slabs;
+        cur->chunk_size = p->size;
+    }
+    pthread_mutex_unlock(&slabs_lock);
+}
+
+/* TODO: slabs_available_chunks should grow up to encompass this.
+ * mem_flag is redundant with the other function.
+ */
+unsigned int global_page_pool_size(bool *mem_flag) {
+    unsigned int ret = 0;
+    pthread_mutex_lock(&slabs_lock);
+    if (mem_flag != NULL)
+        *mem_flag = mem_malloced >= mem_limit ? true : false;
+    ret = slabclass[SLAB_GLOBAL_PAGE_POOL].slabs;
+    pthread_mutex_unlock(&slabs_lock);
+    return ret;
+}
+
+/*@null@*/
+static void do_slabs_stats(ADD_STAT add_stats, void *c) {
+    int i, total;
+    /* Get the per-thread stats which contain some interesting aggregates */
+    struct thread_stats thread_stats;
+    threadlocal_stats_aggregate(&thread_stats);
+
+    total = 0;
+    for(i = POWER_SMALLEST; i <= power_largest; i++) {
+        slabclass_t *p = &slabclass[i];
+        if (p->slabs != 0) {
+            uint32_t perslab, slabs;
+            slabs = p->slabs;
+            perslab = p->perslab;
+
+            char key_str[STAT_KEY_LEN];
+            char val_str[STAT_VAL_LEN];
+            int klen = 0, vlen = 0;
+
+            APPEND_NUM_STAT(i, "chunk_size", "%u", p->size);
+            APPEND_NUM_STAT(i, "chunks_per_page", "%u", perslab);
+            APPEND_NUM_STAT(i, "total_pages", "%u", slabs);
+            APPEND_NUM_STAT(i, "total_chunks", "%u", slabs * perslab);
+            APPEND_NUM_STAT(i, "used_chunks", "%u",
+                            slabs*perslab - p->sl_curr);
+            APPEND_NUM_STAT(i, "free_chunks", "%u", p->sl_curr);
+            /* Stat is dead, but displaying zero instead of removing it. */
+            APPEND_NUM_STAT(i, "free_chunks_end", "%u", 0);
+            APPEND_NUM_STAT(i, "get_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].get_hits);
+            APPEND_NUM_STAT(i, "cmd_set", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].set_cmds);
+            APPEND_NUM_STAT(i, "delete_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].delete_hits);
+            APPEND_NUM_STAT(i, "incr_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].incr_hits);
+            APPEND_NUM_STAT(i, "decr_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].decr_hits);
+            APPEND_NUM_STAT(i, "cas_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].cas_hits);
+            APPEND_NUM_STAT(i, "cas_badval", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].cas_badval);
+            APPEND_NUM_STAT(i, "touch_hits", "%llu",
+                    (unsigned long long)thread_stats.slab_stats[i].touch_hits);
+            total++;
+        }
+    }
+
+    /* add overall slab stats and append terminator */
+
+    APPEND_STAT("active_slabs", "%d", total);
+    APPEND_STAT("total_malloced", "%llu", (unsigned long long)mem_malloced);
+    add_stats(NULL, 0, NULL, 0, c);
+}
+
+static void *memory_allocate(size_t size) {
+    void *ret;
+
+    if (mem_base == NULL) {
+        /* We are not using a preallocated large memory chunk */
+        ret = malloc(size);
+    } else {
+        ret = mem_current;
+
+        if (size > mem_avail) {
+            return NULL;
+        }
+
+        /* mem_current pointer _must_ be aligned!!! */
+        if (size % CHUNK_ALIGN_BYTES) {
+            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
+        }
+
+        mem_current = ((char*)mem_current) + size;
+        if (size < mem_avail) {
+            mem_avail -= size;
+        } else {
+            mem_avail = 0;
+        }
+    }
+    mem_malloced += size;
+
+    return ret;
+}
+
+/* Must only be used if all pages are item_size_max */
+static void memory_release() {
+    void *p = NULL;
+    if (mem_base != NULL)
+        return;
+
+    if (!settings.slab_reassign)
+        return;
+
+    while (mem_malloced > mem_limit &&
+            (p = get_page_from_global_pool()) != NULL) {
+        free(p);
+        mem_malloced -= settings.slab_page_size;
+    }
+}
+
+void *slabs_alloc(size_t size, unsigned int id,
+        unsigned int flags) {
+    void *ret;
+
+    pthread_mutex_lock(&slabs_lock);
+    ret = do_slabs_alloc(size, id, flags);
+    pthread_mutex_unlock(&slabs_lock);
+    return ret;
+}
+
+void slabs_free(void *ptr, size_t size, unsigned int id) {
+    pthread_mutex_lock(&slabs_lock);
+    do_slabs_free(ptr, size, id);
+    pthread_mutex_unlock(&slabs_lock);
+}
+
+void slabs_stats(ADD_STAT add_stats, void *c) {
+    pthread_mutex_lock(&slabs_lock);
+    do_slabs_stats(add_stats, c);
+    pthread_mutex_unlock(&slabs_lock);
+}
+
+static bool do_slabs_adjust_mem_limit(size_t new_mem_limit) {
+    /* Cannot adjust memory limit at runtime if prealloc'ed */
+    if (mem_base != NULL)
+        return false;
+    settings.maxbytes = new_mem_limit;
+    mem_limit = new_mem_limit;
+    mem_limit_reached = false; /* Will reset on next alloc */
+    memory_release(); /* free what might already be in the global pool */
+    return true;
+}
+
+bool slabs_adjust_mem_limit(size_t new_mem_limit) {
+    bool ret;
+    pthread_mutex_lock(&slabs_lock);
+    ret = do_slabs_adjust_mem_limit(new_mem_limit);
+    pthread_mutex_unlock(&slabs_lock);
+    return ret;
+}
+
+unsigned int slabs_available_chunks(const unsigned int id, bool *mem_flag,
+        unsigned int *chunks_perslab) {
+    unsigned int ret;
+    slabclass_t *p;
+
+    pthread_mutex_lock(&slabs_lock);
+    p = &slabclass[id];
+    ret = p->sl_curr;
+    if (mem_flag != NULL)
+        *mem_flag = mem_malloced >= mem_limit ? true : false;
+    if (chunks_perslab != NULL)
+        *chunks_perslab = p->perslab;
+    pthread_mutex_unlock(&slabs_lock);
+    return ret;
+}
+
+/* The slabber system could avoid needing to understand much, if anything,
+ * about items if callbacks were strategically used. Due to how the slab mover
+ * works, certain flag bits can only be adjusted while holding the slabs lock.
+ * Using these functions, isolate sections of code needing this and turn them
+ * into callbacks when an interface becomes more obvious.
+ */
+void slabs_mlock(void) {
+    pthread_mutex_lock(&slabs_lock);
+}
+
+void slabs_munlock(void) {
+    pthread_mutex_unlock(&slabs_lock);
+}
+
+static pthread_cond_t slab_rebalance_cond = PTHREAD_COND_INITIALIZER;
+static volatile int do_run_slab_rebalance_thread = 1;
+
+static int slab_rebalance_start(void) {
+    slabclass_t *s_cls;
+    int no_go = 0;
+
+    pthread_mutex_lock(&slabs_lock);
+
+    if (slab_rebal.s_clsid < SLAB_GLOBAL_PAGE_POOL ||
+        slab_rebal.s_clsid > power_largest  ||
+        slab_rebal.d_clsid < SLAB_GLOBAL_PAGE_POOL ||
+        slab_rebal.d_clsid > power_largest  ||
+        slab_rebal.s_clsid == slab_rebal.d_clsid)
+        no_go = -2;
+
+    s_cls = &slabclass[slab_rebal.s_clsid];
+
+    if (!grow_slab_list(slab_rebal.d_clsid)) {
+        no_go = -1;
+    }
+
+    if (s_cls->slabs < 2)
+        no_go = -3;
+
+    if (no_go != 0) {
+        pthread_mutex_unlock(&slabs_lock);
+        return no_go; /* Should use a wrapper function... */
+    }
+
+    /* Always kill the first available slab page as it is most likely to
+     * contain the oldest items
+     */
+    slab_rebal.slab_start = s_cls->slab_list[0];
+    slab_rebal.slab_end   = (char *)slab_rebal.slab_start +
+        (s_cls->size * s_cls->perslab);
+    slab_rebal.slab_pos   = slab_rebal.slab_start;
+    slab_rebal.done       = 0;
+    // Don't need to do chunk move work if page is in global pool.
+    if (slab_rebal.s_clsid == SLAB_GLOBAL_PAGE_POOL) {
+        slab_rebal.done = 1;
+    }
+
+    // Bit-vector to keep track of completed chunks
+    slab_rebal.completed = (uint8_t*)calloc(s_cls->perslab,sizeof(uint8_t));
+
+    slab_rebalance_signal = 2;
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "Started a slab rebalance\n");
+    }
+
+    pthread_mutex_unlock(&slabs_lock);
+
+    STATS_LOCK();
+    stats_state.slab_reassign_running = true;
+    STATS_UNLOCK();
+
+    return 0;
+}
+
+/* CALLED WITH slabs_lock HELD */
+static void *slab_rebalance_alloc(const size_t size, unsigned int id) {
+    slabclass_t *s_cls;
+    s_cls = &slabclass[slab_rebal.s_clsid];
+    int x;
+    item *new_it = NULL;
+
+    for (x = 0; x < s_cls->perslab; x++) {
+        new_it = do_slabs_alloc(size, id, SLABS_ALLOC_NO_NEWPAGE);
+        /* check that memory isn't within the range to clear */
+        if (new_it == NULL) {
+            break;
+        }
+        if ((void *)new_it >= slab_rebal.slab_start
+            && (void *)new_it < slab_rebal.slab_end) {
+            /* Pulled something we intend to free. Mark it as freed since
+             * we've already done the work of unlinking it from the freelist.
+             */
+            new_it->refcount = 0;
+            new_it->it_flags = ITEM_SLABBED|ITEM_FETCHED;
+#ifdef DEBUG_SLAB_MOVER
+            memcpy(ITEM_key(new_it), "deadbeef", 8);
+#endif
+            new_it = NULL;
+            slab_rebal.inline_reclaim++;
+        } else {
+            break;
+        }
+    }
+    return new_it;
+}
+
+/* CALLED WITH slabs_lock HELD */
+/* detaches item/chunk from freelist. */
+static void slab_rebalance_cut_free(slabclass_t *s_cls, item *it) {
+    /* Ensure this was on the freelist and nothing else. */
+    assert(it->it_flags == ITEM_SLABBED);
+    if (s_cls->slots == it) {
+        s_cls->slots = it->next;
+    }
+    if (it->next) it->next->prev = it->prev;
+    if (it->prev) it->prev->next = it->next;
+    s_cls->sl_curr--;
+}
+
+enum move_status {
+    MOVE_PASS=0, MOVE_FROM_SLAB, MOVE_FROM_LRU, MOVE_BUSY, MOVE_LOCKED
+};
+
+#define SLAB_MOVE_MAX_LOOPS 1000
+
+/* refcount == 0 is safe since nobody can incr while item_lock is held.
+ * refcount != 0 is impossible since flags/etc can be modified in other
+ * threads. instead, note we found a busy one and bail. logic in do_item_get
+ * will prevent busy items from continuing to be busy
+ * NOTE: This is checking it_flags outside of an item lock. I believe this
+ * works since it_flags is 8 bits, and we're only ever comparing a single bit
+ * regardless. ITEM_SLABBED bit will always be correct since we're holding the
+ * lock which modifies that bit. ITEM_LINKED won't exist if we're between an
+ * item having ITEM_SLABBED removed, and the key hasn't been added to the item
+ * yet. The memory barrier from the slabs lock should order the key write and the
+ * flags to the item?
+ * If ITEM_LINKED did exist and was just removed, but we still see it, that's
+ * still safe since it will have a valid key, which we then lock, and then
+ * recheck everything.
+ * This may not be safe on all platforms; If not, slabs_alloc() will need to
+ * seed the item key while holding slabs_lock.
+ */
+static int slab_rebalance_move(void) {
+    slabclass_t *s_cls;
+    int was_busy = 0;
+    int refcount = 0;
+    uint32_t hv;
+    void *hold_lock;
+    enum move_status status = MOVE_PASS;
+
+    s_cls = &slabclass[slab_rebal.s_clsid];
+    // the offset to check if completed or not
+    int offset = ((char*)slab_rebal.slab_pos-(char*)slab_rebal.slab_start)/(s_cls->size);
+
+    // skip acquiring the slabs lock for items we've already fully processed.
+    if (slab_rebal.completed[offset] == 0) {
+        pthread_mutex_lock(&slabs_lock);
+        hv = 0;
+        hold_lock = NULL;
+        item *it = slab_rebal.slab_pos;
+
+        item_chunk *ch = NULL;
+        status = MOVE_PASS;
+
+        if (it->it_flags & ITEM_CHUNK) {
+            /* This chunk is a chained part of a larger item. */
+            ch = (item_chunk *) it;
+            /* Instead, we use the head chunk to find the item and effectively
+             * lock the entire structure. If a chunk has ITEM_CHUNK flag, its
+             * head cannot be slabbed, so the normal routine is safe. */
+            it = ch->head;
+            assert(it->it_flags & ITEM_CHUNKED);
+        }
+
+        /* ITEM_FETCHED when ITEM_SLABBED is overloaded to mean we've cleared
+         * the chunk for move. Only these two flags should exist.
+         */
+        if (it->it_flags != (ITEM_SLABBED|ITEM_FETCHED)) {
+            /* ITEM_SLABBED can only be added/removed under the slabs_lock */
+            if (it->it_flags & ITEM_SLABBED) {
+                assert(ch == NULL);
+                slab_rebalance_cut_free(s_cls, it);
+                status = MOVE_FROM_SLAB;
+            } else if ((it->it_flags & ITEM_LINKED) != 0) {
+                /* If it doesn't have ITEM_SLABBED, the item could be in any
+                 * state on its way to being freed or written to. If no
+                 * ITEM_SLABBED, but it's had ITEM_LINKED, it must be active
+                 * and have the key written to it already.
+                 */
+                hv = hash(ITEM_key(it), it->nkey);
+                if ((hold_lock = item_trylock(hv)) == NULL) {
+                    status = MOVE_LOCKED;
+                } else {
+                    bool is_linked = (it->it_flags & ITEM_LINKED);
+                    refcount = refcount_incr(it);
+                    if (refcount == 2) { /* item is linked but not busy */
+                        /* Double check ITEM_LINKED flag here, since we're
+                         * past a memory barrier from the mutex. */
+                        if (is_linked) {
+                            status = MOVE_FROM_LRU;
+                        } else {
+                            /* refcount == 1 + !ITEM_LINKED means the item is being
+                             * uploaded to, or was just unlinked but hasn't been freed
+                             * yet. Let it bleed off on its own and try again later */
+                            status = MOVE_BUSY;
+                        }
+                    } else if (refcount > 2 && is_linked) {
+                        // TODO: Mark items for delete/rescue and process
+                        // outside of the main loop.
+                        if (slab_rebal.busy_loops > SLAB_MOVE_MAX_LOOPS) {
+                            slab_rebal.busy_deletes++;
+                            // Only safe to hold slabs lock because refcount
+                            // can't drop to 0 until we release item lock.
+                            STORAGE_delete(storage, it);
+                            pthread_mutex_unlock(&slabs_lock);
+                            do_item_unlink(it, hv);
+                            pthread_mutex_lock(&slabs_lock);
+                        }
+                        status = MOVE_BUSY;
+                    } else {
+                        if (settings.verbose > 2) {
+                            fprintf(stderr, "Slab reassign hit a busy item: refcount: %d (%d -> %d)\n",
+                                it->refcount, slab_rebal.s_clsid, slab_rebal.d_clsid);
+                        }
+                        status = MOVE_BUSY;
+                    }
+                    /* Item lock must be held while modifying refcount */
+                    if (status == MOVE_BUSY) {
+                        refcount_decr(it);
+                        item_trylock_unlock(hold_lock);
+                    }
+                }
+            } else {
+                /* See above comment. No ITEM_SLABBED or ITEM_LINKED. Mark
+                 * busy and wait for item to complete its upload. */
+                status = MOVE_BUSY;
+            }
+        }
+
+        int save_item = 0;
+        item *new_it = NULL;
+        size_t ntotal = 0;
+        switch (status) {
+            case MOVE_FROM_LRU:
+                /* Lock order is LRU locks -> slabs_lock. unlink uses LRU lock.
+                 * We only need to hold the slabs_lock while initially looking
+                 * at an item, and at this point we have an exclusive refcount
+                 * (2) + the item is locked. Drop slabs lock, drop item to
+                 * refcount 1 (just our own, then fall through and wipe it
+                 */
+                /* Check if expired or flushed */
+                ntotal = ITEM_ntotal(it);
+#ifdef EXTSTORE
+                if (it->it_flags & ITEM_HDR) {
+                    ntotal = (ntotal - it->nbytes) + sizeof(item_hdr);
+                }
+#endif
+                /* REQUIRES slabs_lock: CHECK FOR cls->sl_curr > 0 */
+                if (ch == NULL && (it->it_flags & ITEM_CHUNKED)) {
+                    /* Chunked should be identical to non-chunked, except we need
+                     * to swap out ntotal for the head-chunk-total. */
+                    ntotal = s_cls->size;
+                }
+                if ((it->exptime != 0 && it->exptime < current_time)
+                    || item_is_flushed(it)) {
+                    /* Expired, don't save. */
+                    save_item = 0;
+                } else if (ch == NULL &&
+                        (new_it = slab_rebalance_alloc(ntotal, slab_rebal.s_clsid)) == NULL) {
+                    /* Not a chunk of an item, and nomem. */
+                    save_item = 0;
+                    slab_rebal.evictions_nomem++;
+                } else if (ch != NULL &&
+                        (new_it = slab_rebalance_alloc(s_cls->size, slab_rebal.s_clsid)) == NULL) {
+                    /* Is a chunk of an item, and nomem. */
+                    save_item = 0;
+                    slab_rebal.evictions_nomem++;
+                } else {
+                    /* Was whatever it was, and we have memory for it. */
+                    save_item = 1;
+                }
+                pthread_mutex_unlock(&slabs_lock);
+                if (save_item) {
+                    if (ch == NULL) {
+                        assert((new_it->it_flags & ITEM_CHUNKED) == 0);
+                        /* if free memory, memcpy. clear prev/next/h_bucket */
+                        memcpy(new_it, it, ntotal);
+                        new_it->prev = 0;
+                        new_it->next = 0;
+                        new_it->h_next = 0;
+                        /* These are definitely required. else fails assert */
+                        new_it->it_flags &= ~ITEM_LINKED;
+                        new_it->refcount = 0;
+                        do_item_replace(it, new_it, hv);
+                        /* Need to walk the chunks and repoint head  */
+                        if (new_it->it_flags & ITEM_CHUNKED) {
+                            item_chunk *fch = (item_chunk *) ITEM_schunk(new_it);
+                            fch->next->prev = fch;
+                            while (fch) {
+                                fch->head = new_it;
+                                fch = fch->next;
+                            }
+                        }
+                        it->refcount = 0;
+                        it->it_flags = ITEM_SLABBED|ITEM_FETCHED;
+#ifdef DEBUG_SLAB_MOVER
+                        memcpy(ITEM_key(it), "deadbeef", 8);
+#endif
+                        slab_rebal.rescues++;
+                    } else {
+                        item_chunk *nch = (item_chunk *) new_it;
+                        /* Chunks always have head chunk (the main it) */
+                        ch->prev->next = nch;
+                        if (ch->next)
+                            ch->next->prev = nch;
+                        memcpy(nch, ch, ch->used + sizeof(item_chunk));
+                        ch->refcount = 0;
+                        ch->it_flags = ITEM_SLABBED|ITEM_FETCHED;
+                        slab_rebal.chunk_rescues++;
+#ifdef DEBUG_SLAB_MOVER
+                        memcpy(ITEM_key((item *)ch), "deadbeef", 8);
+#endif
+                        refcount_decr(it);
+                    }
+                    slab_rebal.completed[offset] = 1;
+                } else {
+                    /* unlink and mark as done if it's not
+                     * a chunked item as they require more book-keeping) */
+                    STORAGE_delete(storage, it);
+                    if (!ch && (it->it_flags & ITEM_CHUNKED) == 0) {
+                        do_item_unlink(it, hv);
+                        it->it_flags = ITEM_SLABBED|ITEM_FETCHED;
+                        it->refcount = 0;
+#ifdef DEBUG_SLAB_MOVER
+                        memcpy(ITEM_key(it), "deadbeef", 8);
+#endif
+                        slab_rebal.completed[offset] = 1;
+                    } else {
+                        ntotal = ITEM_ntotal(it);
+                        do_item_unlink(it, hv);
+                        slabs_free(it, ntotal, slab_rebal.s_clsid);
+                        /* Swing around again later to remove it from the freelist. */
+                        slab_rebal.busy_items++;
+                        was_busy++;
+                    }
+
+                }
+                item_trylock_unlock(hold_lock);
+                pthread_mutex_lock(&slabs_lock);
+                /* Always remove the ntotal, as we added it in during
+                 * do_slabs_alloc() when copying the item.
+                 */
+                break;
+            case MOVE_FROM_SLAB:
+                slab_rebal.completed[offset] = 1;
+                it->refcount = 0;
+                it->it_flags = ITEM_SLABBED|ITEM_FETCHED;
+#ifdef DEBUG_SLAB_MOVER
+                memcpy(ITEM_key(it), "deadbeef", 8);
+#endif
+                break;
+            case MOVE_BUSY:
+            case MOVE_LOCKED:
+                slab_rebal.busy_items++;
+                was_busy++;
+                break;
+            case MOVE_PASS:
+                break;
+        }
+
+        pthread_mutex_unlock(&slabs_lock);
+    }
+
+    // Note: slab_rebal.* is occasionally protected under slabs_lock, but
+    // the mover thread is the only user while active: so it's only necessary
+    // for start/stop synchronization.
+    slab_rebal.slab_pos = (char *)slab_rebal.slab_pos + s_cls->size;
+
+    if (slab_rebal.slab_pos >= slab_rebal.slab_end) {
+        /* Some items were busy, start again from the top */
+        if (slab_rebal.busy_items) {
+            slab_rebal.slab_pos = slab_rebal.slab_start;
+            STATS_LOCK();
+            stats.slab_reassign_busy_items += slab_rebal.busy_items;
+            STATS_UNLOCK();
+            slab_rebal.busy_items = 0;
+            slab_rebal.busy_loops++;
+        } else {
+            slab_rebal.done++;
+        }
+    }
+
+    return was_busy;
+}
+
+static void slab_rebalance_finish(void) {
+    slabclass_t *s_cls;
+    slabclass_t *d_cls;
+    int x;
+    uint32_t rescues;
+    uint32_t evictions_nomem;
+    uint32_t inline_reclaim;
+    uint32_t chunk_rescues;
+    uint32_t busy_deletes;
+
+    pthread_mutex_lock(&slabs_lock);
+
+    s_cls = &slabclass[slab_rebal.s_clsid];
+    d_cls = &slabclass[slab_rebal.d_clsid];
+
+#ifdef DEBUG_SLAB_MOVER
+    /* If the algorithm is broken, live items can sneak in. */
+    slab_rebal.slab_pos = slab_rebal.slab_start;
+    while (1) {
+        item *it = slab_rebal.slab_pos;
+        assert(it->it_flags == (ITEM_SLABBED|ITEM_FETCHED));
+        assert(memcmp(ITEM_key(it), "deadbeef", 8) == 0);
+        it->it_flags = ITEM_SLABBED|ITEM_FETCHED;
+        slab_rebal.slab_pos = (char *)slab_rebal.slab_pos + s_cls->size;
+        if (slab_rebal.slab_pos >= slab_rebal.slab_end)
+            break;
+    }
+#endif
+
+    /* At this point the stolen slab is completely clear.
+     * We always kill the "first"/"oldest" slab page in the slab_list, so
+     * shuffle the page list backwards and decrement.
+     */
+    s_cls->slabs--;
+    for (x = 0; x < s_cls->slabs; x++) {
+        s_cls->slab_list[x] = s_cls->slab_list[x+1];
+    }
+
+    d_cls->slab_list[d_cls->slabs++] = slab_rebal.slab_start;
+    /* Don't need to split the page into chunks if we're just storing it */
+    if (slab_rebal.d_clsid > SLAB_GLOBAL_PAGE_POOL) {
+        memset(slab_rebal.slab_start, 0, (size_t)settings.slab_page_size);
+        split_slab_page_into_freelist(slab_rebal.slab_start,
+            slab_rebal.d_clsid);
+    } else if (slab_rebal.d_clsid == SLAB_GLOBAL_PAGE_POOL) {
+        /* memset just enough to signal restart handler to skip */
+        memset(slab_rebal.slab_start, 0, sizeof(item));
+        /* mem_malloc'ed might be higher than mem_limit. */
+        mem_limit_reached = false;
+        memory_release();
+    }
+
+    slab_rebal.busy_loops = 0;
+    slab_rebal.done       = 0;
+    slab_rebal.s_clsid    = 0;
+    slab_rebal.d_clsid    = 0;
+    slab_rebal.slab_start = NULL;
+    slab_rebal.slab_end   = NULL;
+    slab_rebal.slab_pos   = NULL;
+    evictions_nomem    = slab_rebal.evictions_nomem;
+    inline_reclaim = slab_rebal.inline_reclaim;
+    rescues   = slab_rebal.rescues;
+    chunk_rescues = slab_rebal.chunk_rescues;
+    busy_deletes = slab_rebal.busy_deletes;
+    slab_rebal.evictions_nomem    = 0;
+    slab_rebal.inline_reclaim = 0;
+    slab_rebal.rescues  = 0;
+    slab_rebal.chunk_rescues = 0;
+    slab_rebal.busy_deletes = 0;
+
+    slab_rebalance_signal = 0;
+
+    free(slab_rebal.completed);
+    pthread_mutex_unlock(&slabs_lock);
+
+    STATS_LOCK();
+    stats.slabs_moved++;
+    stats.slab_reassign_rescues += rescues;
+    stats.slab_reassign_evictions_nomem += evictions_nomem;
+    stats.slab_reassign_inline_reclaim += inline_reclaim;
+    stats.slab_reassign_chunk_rescues += chunk_rescues;
+    stats.slab_reassign_busy_deletes += busy_deletes;
+    stats_state.slab_reassign_running = false;
+    STATS_UNLOCK();
+
+    if (settings.verbose > 1) {
+        fprintf(stderr, "finished a slab move\n");
+    }
+}
+
+/* Slab mover thread.
+ * Sits waiting for a condition to jump off and shovel some memory about
+ */
+static void *slab_rebalance_thread(void *arg) {
+    int was_busy = 0;
+    int backoff_timer = 1;
+    int backoff_max = 1000;
+    /* So we first pass into cond_wait with the mutex held */
+    mutex_lock(&slabs_rebalance_lock);
+
+    /* Must finish moving page before stopping */
+    while (slab_rebalance_signal || do_run_slab_rebalance_thread) {
+        if (slab_rebalance_signal == 1) {
+            if (slab_rebalance_start() < 0) {
+                /* Handle errors with more specificity as required. */
+                slab_rebalance_signal = 0;
+            }
+
+            was_busy = 0;
+        } else if (slab_rebalance_signal && slab_rebal.slab_start != NULL) {
+            was_busy = slab_rebalance_move();
+        }
+
+        if (slab_rebal.done) {
+            slab_rebalance_finish();
+        } else if (was_busy) {
+            /* Stuck waiting for some items to unlock, so slow down a bit
+             * to give them a chance to free up */
+            usleep(backoff_timer);
+            backoff_timer = backoff_timer * 2;
+            if (backoff_timer > backoff_max)
+                backoff_timer = backoff_max;
+        }
+
+        if (slab_rebalance_signal == 0) {
+            /* always hold this lock while we're running */
+            pthread_cond_wait(&slab_rebalance_cond, &slabs_rebalance_lock);
+        }
+    }
+
+    // TODO: cancel in-flight slab page move
+    mutex_unlock(&slabs_rebalance_lock);
+    return NULL;
+}
+
+/* Iterate at most once through the slab classes and pick a "random" source.
+ * I like this better than calling rand() since rand() is slow enough that we
+ * can just check all of the classes once instead.
+ */
+static int slabs_reassign_pick_any(int dst) {
+    static int cur = POWER_SMALLEST - 1;
+    int tries = power_largest - POWER_SMALLEST + 1;
+    for (; tries > 0; tries--) {
+        cur++;
+        if (cur > power_largest)
+            cur = POWER_SMALLEST;
+        if (cur == dst)
+            continue;
+        if (slabclass[cur].slabs > 1) {
+            return cur;
+        }
+    }
+    return -1;
+}
+
+static enum reassign_result_type do_slabs_reassign(int src, int dst) {
+    bool nospare = false;
+    if (slab_rebalance_signal != 0)
+        return REASSIGN_RUNNING;
+
+    if (src == dst)
+        return REASSIGN_SRC_DST_SAME;
+
+    /* Special indicator to choose ourselves. */
+    if (src == -1) {
+        src = slabs_reassign_pick_any(dst);
+        /* TODO: If we end up back at -1, return a new error type */
+    }
+
+    if (src < SLAB_GLOBAL_PAGE_POOL || src > power_largest ||
+        dst < SLAB_GLOBAL_PAGE_POOL || dst > power_largest)
+        return REASSIGN_BADCLASS;
+
+    pthread_mutex_lock(&slabs_lock);
+    if (slabclass[src].slabs < 2)
+        nospare = true;
+    pthread_mutex_unlock(&slabs_lock);
+    if (nospare)
+        return REASSIGN_NOSPARE;
+
+    slab_rebal.s_clsid = src;
+    slab_rebal.d_clsid = dst;
+
+    slab_rebalance_signal = 1;
+    pthread_cond_signal(&slab_rebalance_cond);
+
+    return REASSIGN_OK;
+}
+
+enum reassign_result_type slabs_reassign(int src, int dst) {
+    enum reassign_result_type ret;
+    if (pthread_mutex_trylock(&slabs_rebalance_lock) != 0) {
+        return REASSIGN_RUNNING;
+    }
+    ret = do_slabs_reassign(src, dst);
+    pthread_mutex_unlock(&slabs_rebalance_lock);
+    return ret;
+}
+
+/* If we hold this lock, rebalancer can't wake up or move */
+void slabs_rebalancer_pause(void) {
+    pthread_mutex_lock(&slabs_rebalance_lock);
+}
+
+void slabs_rebalancer_resume(void) {
+    pthread_mutex_unlock(&slabs_rebalance_lock);
+}
+
+static pthread_t rebalance_tid;
+
+int start_slab_maintenance_thread(void) {
+    int ret;
+    slab_rebalance_signal = 0;
+    slab_rebal.slab_start = NULL;
+
+    if ((ret = pthread_create(&rebalance_tid, NULL,
+                              slab_rebalance_thread, NULL)) != 0) {
+        fprintf(stderr, "Can't create rebal thread: %s\n", strerror(ret));
+        return -1;
+    }
+    return 0;
+}
+
+/* The maintenance thread is on a sleep/loop cycle, so it should join after a
+ * short wait */
+void stop_slab_maintenance_thread(void) {
+    mutex_lock(&slabs_rebalance_lock);
+    do_run_slab_rebalance_thread = 0;
+    pthread_cond_signal(&slab_rebalance_cond);
+    pthread_mutex_unlock(&slabs_rebalance_lock);
+
+    /* Wait for the maintenance thread to stop */
+    pthread_join(rebalance_tid, NULL);
+}
diff --git a/cachelib/external/memcached/slabs.h b/cachelib/external/memcached/slabs.h
new file mode 100644
index 0000000000..ab7b5068e0
--- /dev/null
+++ b/cachelib/external/memcached/slabs.h
@@ -0,0 +1,72 @@
+/* slabs memory allocation */
+#ifndef SLABS_H
+#define SLABS_H
+
+/** Init the subsystem. 1st argument is the limit on no. of bytes to allocate,
+    0 if no limit. 2nd argument is the growth factor; each slab will use a chunk
+    size equal to the previous slab's chunk size times this factor.
+    3rd argument specifies if the slab allocator should allocate all memory
+    up front (if true), or allocate memory in chunks as it is needed (if false)
+*/
+void slabs_init(const size_t limit, const double factor, const bool prealloc, const uint32_t *slab_sizes, void *mem_base_external, bool reuse_mem);
+
+/** Call only during init. Pre-allocates all available memory */
+void slabs_prefill_global(void);
+
+/**
+ * Given object size, return id to use when allocating/freeing memory for object
+ * 0 means error: can't store such a large object
+ */
+
+unsigned int slabs_clsid(const size_t size);
+unsigned int slabs_size(const int clsid);
+
+/** Allocate object of given length. 0 on error */ /*@null@*/
+#define SLABS_ALLOC_NO_NEWPAGE 1
+void *slabs_alloc(const size_t size, unsigned int id, unsigned int flags);
+
+/** Free previously allocated object */
+void slabs_free(void *ptr, size_t size, unsigned int id);
+
+/** Adjust global memory limit up or down */
+bool slabs_adjust_mem_limit(size_t new_mem_limit);
+
+typedef struct {
+    unsigned int chunks_per_page;
+    unsigned int chunk_size;
+    long int free_chunks;
+    long int total_pages;
+} slab_stats_automove;
+void fill_slab_stats_automove(slab_stats_automove *am);
+unsigned int global_page_pool_size(bool *mem_flag);
+
+/** Fill buffer with stats */ /*@null@*/
+void slabs_stats(ADD_STAT add_stats, void *c);
+
+/* Hints as to freespace in slab class */
+unsigned int slabs_available_chunks(unsigned int id, bool *mem_flag, unsigned int *chunks_perslab);
+
+void slabs_mlock(void);
+void slabs_munlock(void);
+
+int start_slab_maintenance_thread(void);
+void stop_slab_maintenance_thread(void);
+
+enum reassign_result_type {
+    REASSIGN_OK=0, REASSIGN_RUNNING, REASSIGN_BADCLASS, REASSIGN_NOSPARE,
+    REASSIGN_SRC_DST_SAME
+};
+
+enum reassign_result_type slabs_reassign(int src, int dst);
+
+void slabs_rebalancer_pause(void);
+void slabs_rebalancer_resume(void);
+
+#ifdef EXTSTORE
+void slabs_set_storage(void *arg);
+#endif
+
+/* Fixup for restartable code. */
+unsigned int slabs_fixup(char *chunk, const int border);
+
+#endif
diff --git a/cachelib/external/memcached/solaris_priv.c b/cachelib/external/memcached/solaris_priv.c
new file mode 100644
index 0000000000..160fee5097
--- /dev/null
+++ b/cachelib/external/memcached/solaris_priv.c
@@ -0,0 +1,48 @@
+#include <stdlib.h>
+#include <priv.h>
+#include <stdio.h>
+#include "memcached.h"
+
+/*
+ * this section of code will drop all (Solaris) privileges including
+ * those normally granted to all userland process (basic privileges). The
+ * effect of this is that after running this code, the process will not able
+ * to fork(), exec(), etc.  See privileges(5) for more information.
+ */
+void drop_privileges(void) {
+   priv_set_t *privs = priv_str_to_set("basic", ",", NULL);
+
+   if (privs == NULL) {
+      perror("priv_str_to_set");
+      exit(EXIT_FAILURE);
+   }
+
+   (void)priv_delset(privs, PRIV_FILE_LINK_ANY);
+   (void)priv_delset(privs, PRIV_PROC_EXEC);
+   (void)priv_delset(privs, PRIV_PROC_FORK);
+   (void)priv_delset(privs, PRIV_PROC_INFO);
+   (void)priv_delset(privs, PRIV_PROC_SESSION);
+
+   if (setppriv(PRIV_SET, PRIV_PERMITTED, privs) != 0) {
+      perror("setppriv(PRIV_SET, PRIV_PERMITTED)");
+      exit(EXIT_FAILURE);
+   }
+
+   priv_emptyset(privs);
+
+   if (setppriv(PRIV_SET, PRIV_INHERITABLE, privs) != 0) {
+      perror("setppriv(PRIV_SET, PRIV_INHERITABLE)");
+      exit(EXIT_FAILURE);
+   }
+
+   if (setppriv(PRIV_SET, PRIV_LIMIT, privs) != 0) {
+      perror("setppriv(PRIV_SET, PRIV_LIMIT)");
+      exit(EXIT_FAILURE);
+   }
+
+   priv_freeset(privs);
+}
+
+void setup_privilege_violations_handler(void) {
+   // not needed
+}
diff --git a/cachelib/external/memcached/stats_prefix.c b/cachelib/external/memcached/stats_prefix.c
new file mode 100644
index 0000000000..62f0d044d2
--- /dev/null
+++ b/cachelib/external/memcached/stats_prefix.c
@@ -0,0 +1,167 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* Author: Steven Grimm <sgrimm@facebook.com> */
+#include "memcached.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/* Hash table that uses the global hash function */
+static PREFIX_STATS *prefix_stats[PREFIX_HASH_SIZE];
+
+static char prefix_delimiter;
+static int num_prefixes = 0;
+static int total_prefix_size = 0;
+
+void stats_prefix_init(char delimiter) {
+    prefix_delimiter = delimiter;
+    memset(prefix_stats, 0, sizeof(prefix_stats));
+}
+
+void stats_prefix_clear(void) {
+    int i;
+
+    for (i = 0; i < PREFIX_HASH_SIZE; i++) {
+        PREFIX_STATS *cur, *next;
+        for (cur = prefix_stats[i]; cur != NULL; cur = next) {
+            next = cur->next;
+            free(cur->prefix);
+            free(cur);
+        }
+        prefix_stats[i] = NULL;
+    }
+    num_prefixes = 0;
+    total_prefix_size = 0;
+}
+
+PREFIX_STATS *stats_prefix_find(const char *key, const size_t nkey) {
+    PREFIX_STATS *pfs;
+    uint32_t hashval;
+    size_t length;
+    bool bailout = true;
+
+    assert(key != NULL);
+
+    for (length = 0; length < nkey && key[length] != '\0'; length++) {
+        if (key[length] == prefix_delimiter) {
+            bailout = false;
+            break;
+        }
+    }
+
+    if (bailout) {
+        return NULL;
+    }
+
+    hashval = hash(key, length) % PREFIX_HASH_SIZE;
+
+    for (pfs = prefix_stats[hashval]; NULL != pfs; pfs = pfs->next) {
+        if (strncmp(pfs->prefix, key, length) == 0)
+            return pfs;
+    }
+
+    pfs = calloc(sizeof(PREFIX_STATS), 1);
+    if (NULL == pfs) {
+        perror("Can't allocate space for stats structure: calloc");
+        return NULL;
+    }
+
+    pfs->prefix = malloc(length + 1);
+    if (NULL == pfs->prefix) {
+        perror("Can't allocate space for copy of prefix: malloc");
+        free(pfs);
+        return NULL;
+    }
+
+    strncpy(pfs->prefix, key, length);
+    pfs->prefix[length] = '\0';      /* because strncpy() sucks */
+    pfs->prefix_len = length;
+
+    pfs->next = prefix_stats[hashval];
+    prefix_stats[hashval] = pfs;
+
+    num_prefixes++;
+    total_prefix_size += length;
+
+    return pfs;
+}
+
+void stats_prefix_record_get(const char *key, const size_t nkey, const bool is_hit) {
+    PREFIX_STATS *pfs;
+
+    STATS_LOCK();
+    pfs = stats_prefix_find(key, nkey);
+    if (NULL != pfs) {
+        pfs->num_gets++;
+        if (is_hit) {
+            pfs->num_hits++;
+        }
+    }
+    STATS_UNLOCK();
+}
+
+void stats_prefix_record_delete(const char *key, const size_t nkey) {
+    PREFIX_STATS *pfs;
+
+    STATS_LOCK();
+    pfs = stats_prefix_find(key, nkey);
+    if (NULL != pfs) {
+        pfs->num_deletes++;
+    }
+    STATS_UNLOCK();
+}
+
+void stats_prefix_record_set(const char *key, const size_t nkey) {
+    PREFIX_STATS *pfs;
+
+    STATS_LOCK();
+    pfs = stats_prefix_find(key, nkey);
+    if (NULL != pfs) {
+        pfs->num_sets++;
+    }
+    STATS_UNLOCK();
+}
+
+char *stats_prefix_dump(int *length) {
+    const char *format = "PREFIX %s get %llu hit %llu set %llu del %llu\r\n";
+    PREFIX_STATS *pfs;
+    char *buf;
+    int i, pos;
+    size_t size = 0, written = 0, total_written = 0;
+
+    /*
+     * Figure out how big the buffer needs to be. This is the sum of the
+     * lengths of the prefixes themselves, plus the size of one copy of
+     * the per-prefix output with 20-digit values for all the counts,
+     * plus space for the "END" at the end.
+     */
+    STATS_LOCK();
+    size = strlen(format) + total_prefix_size +
+           num_prefixes * (strlen(format) - 2 /* %s */
+                           + 4 * (20 - 4)) /* %llu replaced by 20-digit num */
+                           + sizeof("END\r\n");
+    buf = malloc(size);
+    if (NULL == buf) {
+        perror("Can't allocate stats response: malloc");
+        STATS_UNLOCK();
+        return NULL;
+    }
+
+    pos = 0;
+    for (i = 0; i < PREFIX_HASH_SIZE; i++) {
+        for (pfs = prefix_stats[i]; NULL != pfs; pfs = pfs->next) {
+            written = snprintf(buf + pos, size-pos, format,
+                           pfs->prefix, pfs->num_gets, pfs->num_hits,
+                           pfs->num_sets, pfs->num_deletes);
+            pos += written;
+            total_written += written;
+            assert(total_written < size);
+        }
+    }
+
+    STATS_UNLOCK();
+    memcpy(buf + pos, "END\r\n", 6);
+
+    *length = pos + 5;
+    return buf;
+}
diff --git a/cachelib/external/memcached/stats_prefix.h b/cachelib/external/memcached/stats_prefix.h
new file mode 100644
index 0000000000..ae4d6002ce
--- /dev/null
+++ b/cachelib/external/memcached/stats_prefix.h
@@ -0,0 +1,59 @@
+#ifndef STATS_PREFIX_H
+#define STATS_PREFIX_H
+
+/* The stats prefix subsystem stores detailed statistics for each key prefix.
+ * Simple statistics like total number of GETS are stored by the Stats
+ * subsystem defined elsewhere.
+ *
+ * Suppose the prefix delimiter is ":", then "user:123" and "user:456" both
+ * have the same prefix "user".
+ */
+
+
+/* Initialize the stats prefix subsystem. Should be called once before other
+ * functions are called. The global hash initialization should be done before
+ * using this subsystem.
+ */
+void stats_prefix_init(char prefix_delimiter);
+
+/* Clear previously collected stats. Requires you to have the acquired
+ * the STATS_LOCK() first.
+ */
+void stats_prefix_clear(void);
+
+/* Record a GET for a key */
+void stats_prefix_record_get(const char *key, const size_t nkey, const bool is_hit);
+
+/* Record a DELETE for a key */
+void stats_prefix_record_delete(const char *key, const size_t nkey);
+
+/* Record a SET for a key */
+void stats_prefix_record_set(const char *key, const size_t nkey);
+
+/* Return the collected stats in a textual for suitable for writing to a client.
+ * The size of the output text is stored in the length parameter.
+ * Returns NULL on error
+ */
+char *stats_prefix_dump(int *length);
+
+/* Visible for testing */
+#define PREFIX_HASH_SIZE 256
+typedef struct _prefix_stats PREFIX_STATS;
+struct _prefix_stats {
+    char *prefix;
+    size_t prefix_len;
+    uint64_t num_gets;
+    uint64_t num_sets;
+    uint64_t num_deletes;
+    uint64_t num_hits;
+    PREFIX_STATS *next;
+};
+
+/* Return the PREFIX_STATS structure for the specified key, creating it if
+ * it does not already exist. Returns NULL if the key does not contain
+ * prefix delimiter, or if there was an error. Requires you to have acquired
+ * STATS_LOCK() first.
+ */
+PREFIX_STATS *stats_prefix_find(const char *key, const size_t nkey);
+
+#endif
diff --git a/cachelib/external/memcached/storage.c b/cachelib/external/memcached/storage.c
new file mode 100644
index 0000000000..a9ca028af4
--- /dev/null
+++ b/cachelib/external/memcached/storage.c
@@ -0,0 +1,653 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "memcached.h"
+#ifdef EXTSTORE
+
+#include "storage.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+
+#define PAGE_BUCKET_DEFAULT 0
+#define PAGE_BUCKET_COMPACT 1
+#define PAGE_BUCKET_CHUNKED 2
+#define PAGE_BUCKET_LOWTTL  3
+
+/*** WRITE FLUSH THREAD ***/
+
+static int storage_write(void *storage, const int clsid, const int item_age) {
+    int did_moves = 0;
+    struct lru_pull_tail_return it_info;
+
+    it_info.it = NULL;
+    lru_pull_tail(clsid, COLD_LRU, 0, LRU_PULL_RETURN_ITEM, 0, &it_info);
+    /* Item is locked, and we have a reference to it. */
+    if (it_info.it == NULL) {
+        return did_moves;
+    }
+
+    obj_io io;
+    item *it = it_info.it;
+    /* First, storage for the header object */
+    size_t orig_ntotal = ITEM_ntotal(it);
+    uint32_t flags;
+    if ((it->it_flags & ITEM_HDR) == 0 &&
+            (item_age == 0 || current_time - it->time > item_age)) {
+        FLAGS_CONV(it, flags);
+        item *hdr_it = do_item_alloc(ITEM_key(it), it->nkey, flags, it->exptime, sizeof(item_hdr));
+        /* Run the storage write understanding the start of the item is dirty.
+         * We will fill it (time/exptime/etc) from the header item on read.
+         */
+        if (hdr_it != NULL) {
+            int bucket = (it->it_flags & ITEM_CHUNKED) ?
+                PAGE_BUCKET_CHUNKED : PAGE_BUCKET_DEFAULT;
+            // Compress soon to expire items into similar pages.
+            if (it->exptime - current_time < settings.ext_low_ttl) {
+                bucket = PAGE_BUCKET_LOWTTL;
+            }
+            hdr_it->it_flags |= ITEM_HDR;
+            io.len = orig_ntotal;
+            io.mode = OBJ_IO_WRITE;
+            // NOTE: when the item is read back in, the slab mover
+            // may see it. Important to have refcount>=2 or ~ITEM_LINKED
+            assert(it->refcount >= 2);
+            // NOTE: write bucket vs free page bucket will disambiguate once
+            // lowttl feature is better understood.
+            if (extstore_write_request(storage, bucket, bucket, &io) == 0) {
+                // cuddle the hash value into the time field so we don't have
+                // to recalculate it.
+                item *buf_it = (item *) io.buf;
+                buf_it->time = it_info.hv;
+                // copy from past the headers + time headers.
+                // TODO: should be in items.c
+                if (it->it_flags & ITEM_CHUNKED) {
+                    // Need to loop through the item and copy
+                    item_chunk *sch = (item_chunk *) ITEM_schunk(it);
+                    int remain = orig_ntotal;
+                    int copied = 0;
+                    // copy original header
+                    int hdrtotal = ITEM_ntotal(it) - it->nbytes;
+                    memcpy((char *)io.buf+STORE_OFFSET, (char *)it+STORE_OFFSET, hdrtotal - STORE_OFFSET);
+                    copied = hdrtotal;
+                    // copy data in like it were one large object.
+                    while (sch && remain) {
+                        assert(remain >= sch->used);
+                        memcpy((char *)io.buf+copied, sch->data, sch->used);
+                        // FIXME: use one variable?
+                        remain -= sch->used;
+                        copied += sch->used;
+                        sch = sch->next;
+                    }
+                } else {
+                    memcpy((char *)io.buf+STORE_OFFSET, (char *)it+STORE_OFFSET, io.len-STORE_OFFSET);
+                }
+                // crc what we copied so we can do it sequentially.
+                buf_it->it_flags &= ~ITEM_LINKED;
+                buf_it->exptime = crc32c(0, (char*)io.buf+STORE_OFFSET, orig_ntotal-STORE_OFFSET);
+                extstore_write(storage, &io);
+                item_hdr *hdr = (item_hdr *) ITEM_data(hdr_it);
+                hdr->page_version = io.page_version;
+                hdr->page_id = io.page_id;
+                hdr->offset  = io.offset;
+                // overload nbytes for the header it
+                hdr_it->nbytes = it->nbytes;
+                /* success! Now we need to fill relevant data into the new
+                 * header and replace. Most of this requires the item lock
+                 */
+                /* CAS gets set while linking. Copy post-replace */
+                item_replace(it, hdr_it, it_info.hv);
+                ITEM_set_cas(hdr_it, ITEM_get_cas(it));
+                do_item_remove(hdr_it);
+                did_moves = 1;
+                LOGGER_LOG(NULL, LOG_EVICTIONS, LOGGER_EXTSTORE_WRITE, it, bucket);
+            } else {
+                /* Failed to write for some reason, can't continue. */
+                slabs_free(hdr_it, ITEM_ntotal(hdr_it), ITEM_clsid(hdr_it));
+            }
+        }
+    }
+    do_item_remove(it);
+    item_unlock(it_info.hv);
+    return did_moves;
+}
+
+static pthread_t storage_write_tid;
+static pthread_mutex_t storage_write_plock;
+#define WRITE_SLEEP_MAX 1000000
+#define WRITE_SLEEP_MIN 500
+
+static void *storage_write_thread(void *arg) {
+    void *storage = arg;
+    // NOTE: ignoring overflow since that would take years of uptime in a
+    // specific load pattern of never going to sleep.
+    unsigned int backoff[MAX_NUMBER_OF_SLAB_CLASSES] = {0};
+    unsigned int counter = 0;
+    useconds_t to_sleep = WRITE_SLEEP_MIN;
+    logger *l = logger_create();
+    if (l == NULL) {
+        fprintf(stderr, "Failed to allocate logger for storage compaction thread\n");
+        abort();
+    }
+
+    pthread_mutex_lock(&storage_write_plock);
+
+    while (1) {
+        // cache per-loop to avoid calls to the slabs_clsid() search loop
+        int min_class = slabs_clsid(settings.ext_item_size);
+        bool do_sleep = true;
+        counter++;
+        if (to_sleep > WRITE_SLEEP_MAX)
+            to_sleep = WRITE_SLEEP_MAX;
+
+        for (int x = 0; x < MAX_NUMBER_OF_SLAB_CLASSES; x++) {
+            bool did_move = false;
+            bool mem_limit_reached = false;
+            unsigned int chunks_free;
+            int item_age;
+            int target = settings.ext_free_memchunks[x];
+            if (min_class > x || (backoff[x] && (counter % backoff[x] != 0))) {
+                // Long sleeps means we should retry classes sooner.
+                if (to_sleep > WRITE_SLEEP_MIN * 10)
+                    backoff[x] /= 2;
+                continue;
+            }
+
+            // Avoid extra slab lock calls during heavy writing.
+            chunks_free = slabs_available_chunks(x, &mem_limit_reached,
+                    NULL);
+
+            // storage_write() will fail and cut loop after filling write buffer.
+            while (1) {
+                // if we are low on chunks and no spare, push out early.
+                if (chunks_free < target && mem_limit_reached) {
+                    item_age = 0;
+                } else {
+                    item_age = settings.ext_item_age;
+                }
+                if (storage_write(storage, x, item_age)) {
+                    chunks_free++; // Allow stopping if we've done enough this loop
+                    did_move = true;
+                    do_sleep = false;
+                    if (to_sleep > WRITE_SLEEP_MIN)
+                        to_sleep /= 2;
+                } else {
+                    break;
+                }
+            }
+
+            if (!did_move) {
+                backoff[x]++;
+            } else if (backoff[x]) {
+                backoff[x] /= 2;
+            }
+        }
+
+        // flip lock so we can be paused or stopped
+        pthread_mutex_unlock(&storage_write_plock);
+        if (do_sleep) {
+            usleep(to_sleep);
+            to_sleep *= 2;
+        }
+        pthread_mutex_lock(&storage_write_plock);
+    }
+    return NULL;
+}
+
+// TODO
+// logger needs logger_destroy() to exist/work before this is safe.
+/*int stop_storage_write_thread(void) {
+    int ret;
+    pthread_mutex_lock(&lru_maintainer_lock);
+    do_run_lru_maintainer_thread = 0;
+    pthread_mutex_unlock(&lru_maintainer_lock);
+    // WAKEUP SIGNAL
+    if ((ret = pthread_join(lru_maintainer_tid, NULL)) != 0) {
+        fprintf(stderr, "Failed to stop LRU maintainer thread: %s\n", strerror(ret));
+        return -1;
+    }
+    settings.lru_maintainer_thread = false;
+    return 0;
+}*/
+
+void storage_write_pause(void) {
+    pthread_mutex_lock(&storage_write_plock);
+}
+
+void storage_write_resume(void) {
+    pthread_mutex_unlock(&storage_write_plock);
+}
+
+int start_storage_write_thread(void *arg) {
+    int ret;
+
+    pthread_mutex_init(&storage_write_plock, NULL);
+    if ((ret = pthread_create(&storage_write_tid, NULL,
+        storage_write_thread, arg)) != 0) {
+        fprintf(stderr, "Can't create storage_write thread: %s\n",
+            strerror(ret));
+        return -1;
+    }
+
+    return 0;
+}
+
+/*** COMPACTOR ***/
+
+/* Fetch stats from the external storage system and decide to compact.
+ * If we're more than half full, start skewing how aggressively to run
+ * compaction, up to a desired target when all pages are full.
+ */
+static int storage_compact_check(void *storage, logger *l,
+        uint32_t *page_id, uint64_t *page_version,
+        uint64_t *page_size, bool *drop_unread) {
+    struct extstore_stats st;
+    int x;
+    double rate;
+    uint64_t frag_limit;
+    uint64_t low_version = ULLONG_MAX;
+    uint64_t lowest_version = ULLONG_MAX;
+    unsigned int low_page = 0;
+    unsigned int lowest_page = 0;
+    extstore_get_stats(storage, &st);
+    if (st.pages_used == 0)
+        return 0;
+
+    // lets pick a target "wasted" value and slew.
+    if (st.pages_free > settings.ext_compact_under)
+        return 0;
+    *drop_unread = false;
+
+    // the number of free pages reduces the configured frag limit
+    // this allows us to defrag early if pages are very empty.
+    rate = 1.0 - ((double)st.pages_free / st.page_count);
+    rate *= settings.ext_max_frag;
+    frag_limit = st.page_size * rate;
+    LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_COMPACT_FRAGINFO,
+            NULL, rate, frag_limit);
+    st.page_data = calloc(st.page_count, sizeof(struct extstore_page_data));
+    extstore_get_page_data(storage, &st);
+
+    // find oldest page by version that violates the constraint
+    for (x = 0; x < st.page_count; x++) {
+        if (st.page_data[x].version == 0 ||
+            st.page_data[x].bucket == PAGE_BUCKET_LOWTTL)
+            continue;
+        if (st.page_data[x].version < lowest_version) {
+            lowest_page = x;
+            lowest_version = st.page_data[x].version;
+        }
+        if (st.page_data[x].bytes_used < frag_limit) {
+            if (st.page_data[x].version < low_version) {
+                low_page = x;
+                low_version = st.page_data[x].version;
+            }
+        }
+    }
+    *page_size = st.page_size;
+    free(st.page_data);
+
+    // we have a page + version to attempt to reclaim.
+    if (low_version != ULLONG_MAX) {
+        *page_id = low_page;
+        *page_version = low_version;
+        return 1;
+    } else if (lowest_version != ULLONG_MAX && settings.ext_drop_unread
+            && st.pages_free <= settings.ext_drop_under) {
+        // nothing matched the frag rate barrier, so pick the absolute oldest
+        // version if we're configured to drop items.
+        *page_id = lowest_page;
+        *page_version = lowest_version;
+        *drop_unread = true;
+        return 1;
+    }
+
+    return 0;
+}
+
+static pthread_t storage_compact_tid;
+static pthread_mutex_t storage_compact_plock;
+#define MIN_STORAGE_COMPACT_SLEEP 10000
+#define MAX_STORAGE_COMPACT_SLEEP 2000000
+
+struct storage_compact_wrap {
+    obj_io io;
+    pthread_mutex_t lock; // gates the bools.
+    bool done;
+    bool submitted;
+    bool miss; // version flipped out from under us
+};
+
+static void storage_compact_readback(void *storage, logger *l,
+        bool drop_unread, char *readback_buf,
+        uint32_t page_id, uint64_t page_version, uint64_t read_size) {
+    uint64_t offset = 0;
+    unsigned int rescues = 0;
+    unsigned int lost = 0;
+    unsigned int skipped = 0;
+
+    while (offset < read_size) {
+        item *hdr_it = NULL;
+        item_hdr *hdr = NULL;
+        item *it = (item *)(readback_buf+offset);
+        unsigned int ntotal;
+        // probably zeroed out junk at the end of the wbuf
+        if (it->nkey == 0) {
+            break;
+        }
+
+        ntotal = ITEM_ntotal(it);
+        uint32_t hv = (uint32_t)it->time;
+        item_lock(hv);
+        // We don't have a conn and don't need to do most of do_item_get
+        hdr_it = assoc_find(ITEM_key(it), it->nkey, hv);
+        if (hdr_it != NULL) {
+            bool do_write = false;
+            refcount_incr(hdr_it);
+
+            // Check validity but don't bother removing it.
+            if ((hdr_it->it_flags & ITEM_HDR) && !item_is_flushed(hdr_it) &&
+                   (hdr_it->exptime == 0 || hdr_it->exptime > current_time)) {
+                hdr = (item_hdr *)ITEM_data(hdr_it);
+                if (hdr->page_id == page_id && hdr->page_version == page_version) {
+                    // Item header is still completely valid.
+                    extstore_delete(storage, page_id, page_version, 1, ntotal);
+                    // drop inactive items.
+                    if (drop_unread && GET_LRU(hdr_it->slabs_clsid) == COLD_LRU) {
+                        do_write = false;
+                        skipped++;
+                    } else {
+                        do_write = true;
+                    }
+                }
+            }
+
+            if (do_write) {
+                bool do_update = false;
+                int tries;
+                obj_io io;
+                io.len = ntotal;
+                io.mode = OBJ_IO_WRITE;
+                for (tries = 10; tries > 0; tries--) {
+                    if (extstore_write_request(storage, PAGE_BUCKET_COMPACT, PAGE_BUCKET_COMPACT, &io) == 0) {
+                        memcpy(io.buf, it, io.len);
+                        extstore_write(storage, &io);
+                        do_update = true;
+                        break;
+                    } else {
+                        usleep(1000);
+                    }
+                }
+
+                if (do_update) {
+                    if (it->refcount == 2) {
+                        hdr->page_version = io.page_version;
+                        hdr->page_id = io.page_id;
+                        hdr->offset = io.offset;
+                        rescues++;
+                    } else {
+                        lost++;
+                        // TODO: re-alloc and replace header.
+                    }
+                } else {
+                    lost++;
+                }
+            }
+
+            do_item_remove(hdr_it);
+        }
+
+        item_unlock(hv);
+        offset += ntotal;
+        if (read_size - offset < sizeof(struct _stritem))
+            break;
+    }
+
+    STATS_LOCK();
+    stats.extstore_compact_lost += lost;
+    stats.extstore_compact_rescues += rescues;
+    stats.extstore_compact_skipped += skipped;
+    STATS_UNLOCK();
+    LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_COMPACT_READ_END,
+            NULL, page_id, offset, rescues, lost, skipped);
+}
+
+static void _storage_compact_cb(void *e, obj_io *io, int ret) {
+    struct storage_compact_wrap *wrap = (struct storage_compact_wrap *)io->data;
+    assert(wrap->submitted == true);
+
+    pthread_mutex_lock(&wrap->lock);
+
+    if (ret < 1) {
+        wrap->miss = true;
+    }
+    wrap->done = true;
+
+    pthread_mutex_unlock(&wrap->lock);
+}
+
+// TODO: hoist the storage bits from lru_maintainer_thread in here.
+// would be nice if they could avoid hammering the same locks though?
+// I guess it's only COLD. that's probably fine.
+static void *storage_compact_thread(void *arg) {
+    void *storage = arg;
+    useconds_t to_sleep = MAX_STORAGE_COMPACT_SLEEP;
+    bool compacting = false;
+    uint64_t page_version = 0;
+    uint64_t page_size = 0;
+    uint64_t page_offset = 0;
+    uint32_t page_id = 0;
+    bool drop_unread = false;
+    char *readback_buf = NULL;
+    struct storage_compact_wrap wrap;
+
+    logger *l = logger_create();
+    if (l == NULL) {
+        fprintf(stderr, "Failed to allocate logger for storage compaction thread\n");
+        abort();
+    }
+
+    readback_buf = malloc(settings.ext_wbuf_size);
+    if (readback_buf == NULL) {
+        fprintf(stderr, "Failed to allocate readback buffer for storage compaction thread\n");
+        abort();
+    }
+
+    pthread_mutex_init(&wrap.lock, NULL);
+    wrap.done = false;
+    wrap.submitted = false;
+    wrap.io.data = &wrap;
+    wrap.io.buf = (void *)readback_buf;
+
+    wrap.io.len = settings.ext_wbuf_size;
+    wrap.io.mode = OBJ_IO_READ;
+    wrap.io.cb = _storage_compact_cb;
+    pthread_mutex_lock(&storage_compact_plock);
+
+    while (1) {
+        pthread_mutex_unlock(&storage_compact_plock);
+        if (to_sleep) {
+            extstore_run_maint(storage);
+            usleep(to_sleep);
+        }
+        pthread_mutex_lock(&storage_compact_plock);
+
+        if (!compacting && storage_compact_check(storage, l,
+                    &page_id, &page_version, &page_size, &drop_unread)) {
+            page_offset = 0;
+            compacting = true;
+            LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_COMPACT_START,
+                    NULL, page_id, page_version);
+        }
+
+        if (compacting) {
+            pthread_mutex_lock(&wrap.lock);
+            if (page_offset < page_size && !wrap.done && !wrap.submitted) {
+                wrap.io.page_version = page_version;
+                wrap.io.page_id = page_id;
+                wrap.io.offset = page_offset;
+                // FIXME: should be smarter about io->next (unlink at use?)
+                wrap.io.next = NULL;
+                wrap.submitted = true;
+                wrap.miss = false;
+
+                extstore_submit(storage, &wrap.io);
+            } else if (wrap.miss) {
+                LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_COMPACT_ABORT,
+                        NULL, page_id);
+                wrap.done = false;
+                wrap.submitted = false;
+                compacting = false;
+            } else if (wrap.submitted && wrap.done) {
+                LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_COMPACT_READ_START,
+                        NULL, page_id, page_offset);
+                storage_compact_readback(storage, l, drop_unread,
+                        readback_buf, page_id, page_version, settings.ext_wbuf_size);
+                page_offset += settings.ext_wbuf_size;
+                wrap.done = false;
+                wrap.submitted = false;
+            } else if (page_offset >= page_size) {
+                compacting = false;
+                wrap.done = false;
+                wrap.submitted = false;
+                extstore_close_page(storage, page_id, page_version);
+                LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_COMPACT_END,
+                        NULL, page_id);
+            }
+            pthread_mutex_unlock(&wrap.lock);
+
+            if (to_sleep > MIN_STORAGE_COMPACT_SLEEP)
+                to_sleep /= 2;
+        } else {
+            if (to_sleep < MAX_STORAGE_COMPACT_SLEEP)
+                to_sleep += MIN_STORAGE_COMPACT_SLEEP;
+        }
+    }
+    free(readback_buf);
+
+    return NULL;
+}
+
+// TODO
+// logger needs logger_destroy() to exist/work before this is safe.
+/*int stop_storage_compact_thread(void) {
+    int ret;
+    pthread_mutex_lock(&lru_maintainer_lock);
+    do_run_lru_maintainer_thread = 0;
+    pthread_mutex_unlock(&lru_maintainer_lock);
+    if ((ret = pthread_join(lru_maintainer_tid, NULL)) != 0) {
+        fprintf(stderr, "Failed to stop LRU maintainer thread: %s\n", strerror(ret));
+        return -1;
+    }
+    settings.lru_maintainer_thread = false;
+    return 0;
+}*/
+
+void storage_compact_pause(void) {
+    pthread_mutex_lock(&storage_compact_plock);
+}
+
+void storage_compact_resume(void) {
+    pthread_mutex_unlock(&storage_compact_plock);
+}
+
+int start_storage_compact_thread(void *arg) {
+    int ret;
+
+    pthread_mutex_init(&storage_compact_plock, NULL);
+    if ((ret = pthread_create(&storage_compact_tid, NULL,
+        storage_compact_thread, arg)) != 0) {
+        fprintf(stderr, "Can't create storage_compact thread: %s\n",
+            strerror(ret));
+        return -1;
+    }
+
+    return 0;
+}
+
+/*** UTILITY ***/
+// /path/to/file:100G:bucket1
+// FIXME: Modifies argument. copy instead?
+struct extstore_conf_file *storage_conf_parse(char *arg, unsigned int page_size) {
+    struct extstore_conf_file *cf = NULL;
+    char *b = NULL;
+    char *p = strtok_r(arg, ":", &b);
+    char unit = 0;
+    uint64_t multiplier = 0;
+    int base_size = 0;
+    if (p == NULL)
+        goto error;
+    // First arg is the filepath.
+    cf = calloc(1, sizeof(struct extstore_conf_file));
+    cf->file = strdup(p);
+
+    p = strtok_r(NULL, ":", &b);
+    if (p == NULL) {
+        fprintf(stderr, "must supply size to ext_path, ie: ext_path=/f/e:64m (M|G|T|P supported)\n");
+        goto error;
+    }
+    unit = tolower(p[strlen(p)-1]);
+    p[strlen(p)-1] = '\0';
+    // sigh.
+    switch (unit) {
+        case 'm':
+            multiplier = 1024 * 1024;
+            break;
+        case 'g':
+            multiplier = 1024 * 1024 * 1024;
+            break;
+        case 't':
+            multiplier = 1024 * 1024;
+            multiplier *= 1024 * 1024;
+            break;
+        case 'p':
+            multiplier = 1024 * 1024;
+            multiplier *= 1024 * 1024 * 1024;
+            break;
+    }
+    base_size = atoi(p);
+    multiplier *= base_size;
+    // page_count is nearest-but-not-larger-than pages * psize
+    cf->page_count = multiplier / page_size;
+    assert(page_size * cf->page_count <= multiplier);
+
+    // final token would be a default free bucket
+    p = strtok_r(NULL, ",", &b);
+    // TODO: We reuse the original DEFINES for now,
+    // but if lowttl gets split up this needs to be its own set.
+    if (p != NULL) {
+        if (strcmp(p, "compact") == 0) {
+            cf->free_bucket = PAGE_BUCKET_COMPACT;
+        } else if (strcmp(p, "lowttl") == 0) {
+            cf->free_bucket = PAGE_BUCKET_LOWTTL;
+        } else if (strcmp(p, "chunked") == 0) {
+            cf->free_bucket = PAGE_BUCKET_CHUNKED;
+        } else if (strcmp(p, "default") == 0) {
+            cf->free_bucket = PAGE_BUCKET_DEFAULT;
+        } else {
+            fprintf(stderr, "Unknown extstore bucket: %s\n", p);
+            goto error;
+        }
+    } else {
+        // TODO: is this necessary?
+        cf->free_bucket = PAGE_BUCKET_DEFAULT;
+    }
+
+    // TODO: disabling until compact algorithm is improved.
+    if (cf->free_bucket != PAGE_BUCKET_DEFAULT) {
+        fprintf(stderr, "ext_path only presently supports the default bucket\n");
+        goto error;
+    }
+
+    return cf;
+error:
+    if (cf) {
+        if (cf->file)
+            free(cf->file);
+        free(cf);
+    }
+    return NULL;
+}
+
+#endif
diff --git a/cachelib/external/memcached/storage.h b/cachelib/external/memcached/storage.h
new file mode 100644
index 0000000000..60f499b8c2
--- /dev/null
+++ b/cachelib/external/memcached/storage.h
@@ -0,0 +1,15 @@
+#ifndef STORAGE_H
+#define STORAGE_H
+
+int start_storage_write_thread(void *arg);
+void storage_write_pause(void);
+void storage_write_resume(void);
+int start_storage_compact_thread(void *arg);
+void storage_compact_pause(void);
+void storage_compact_resume(void);
+struct extstore_conf_file *storage_conf_parse(char *arg, unsigned int page_size);
+
+// Ignore pointers and header bits from the CRC
+#define STORE_OFFSET offsetof(item, nbytes)
+
+#endif
diff --git a/cachelib/external/memcached/testapp.c b/cachelib/external/memcached/testapp.c
new file mode 100644
index 0000000000..01f2ba5aef
--- /dev/null
+++ b/cachelib/external/memcached/testapp.c
@@ -0,0 +1,2338 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#undef NDEBUG
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+
+#include "config.h"
+#include "cache.h"
+#include "hash.h"
+#include "stats_prefix.h"
+#include "util.h"
+#include "protocol_binary.h"
+#ifdef TLS
+#include <openssl/ssl.h>
+#endif
+
+#define TMP_TEMPLATE "/tmp/test_file.XXXXXXX"
+
+enum test_return { TEST_SKIP, TEST_PASS, TEST_FAIL };
+
+struct conn {
+    int sock;
+#ifdef TLS
+    SSL_CTX   *ssl_ctx;
+    SSL    *ssl;
+#endif
+    ssize_t (*read)(struct conn  *c, void *buf, size_t count);
+    ssize_t (*write)(struct conn *c, const void *buf, size_t count);
+};
+
+struct settings {
+    char *hash_algorithm;
+};
+struct settings settings;
+
+static ssize_t tcp_read(struct conn *c, void *buf, size_t count);
+static ssize_t tcp_write(struct conn *c, const void *buf, size_t count);
+#ifdef TLS
+static ssize_t ssl_read(struct conn *c, void *buf, size_t count);
+static ssize_t ssl_write(struct conn *c, const void *buf, size_t count);
+#endif
+
+ssize_t tcp_read(struct conn *c, void *buf, size_t count) {
+    assert(c != NULL);
+    return read(c->sock, buf, count);
+}
+
+ssize_t tcp_write(struct conn *c, const void *buf, size_t count) {
+    assert(c != NULL);
+    return write(c->sock, buf, count);
+}
+#ifdef TLS
+ssize_t ssl_read(struct conn *c, void *buf, size_t count) {
+    assert(c != NULL);
+    return SSL_read(c->ssl, buf, count);
+}
+
+ssize_t ssl_write(struct conn *c, const void *buf, size_t count) {
+    assert(c != NULL);
+    return SSL_write(c->ssl, buf, count);
+}
+#endif
+
+static pid_t server_pid;
+static in_port_t port;
+struct conn *con = NULL;
+static bool allow_closed_read = false;
+static bool enable_ssl = false;
+
+static void close_conn() {
+    if (con == NULL) return;
+#ifdef TLS
+    if (con->ssl) {
+        SSL_shutdown(con->ssl);
+        SSL_free(con->ssl);
+    }
+    if (con->ssl_ctx)
+        SSL_CTX_free(con->ssl_ctx);
+#endif
+    if (con->sock > 0) close(con->sock);
+    free(con);
+    con = NULL;
+}
+
+static enum test_return cache_create_test(void)
+{
+    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
+                                  NULL, NULL);
+    assert(cache != NULL);
+    cache_destroy(cache);
+    return TEST_PASS;
+}
+
+const uint64_t constructor_pattern = 0xdeadcafebabebeef;
+
+static int cache_constructor(void *buffer, void *notused1, int notused2) {
+    uint64_t *ptr = buffer;
+    *ptr = constructor_pattern;
+    return 0;
+}
+
+static enum test_return cache_constructor_test(void)
+{
+    cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t),
+                                  cache_constructor, NULL);
+    assert(cache != NULL);
+    uint64_t *ptr = cache_alloc(cache);
+    uint64_t pattern = *ptr;
+    cache_free(cache, ptr);
+    cache_destroy(cache);
+    return (pattern == constructor_pattern) ? TEST_PASS : TEST_FAIL;
+}
+
+static int cache_fail_constructor(void *buffer, void *notused1, int notused2) {
+    return 1;
+}
+
+static enum test_return cache_fail_constructor_test(void)
+{
+    enum test_return ret = TEST_PASS;
+
+    cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t),
+                                  cache_fail_constructor, NULL);
+    assert(cache != NULL);
+    uint64_t *ptr = cache_alloc(cache);
+    if (ptr != NULL) {
+        ret = TEST_FAIL;
+    }
+    cache_destroy(cache);
+    return ret;
+}
+
+static void *destruct_data = 0;
+
+static void cache_destructor(void *buffer, void *notused) {
+    destruct_data = buffer;
+}
+
+static enum test_return cache_destructor_test(void)
+{
+    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
+                                  NULL, cache_destructor);
+    assert(cache != NULL);
+    char *ptr = cache_alloc(cache);
+    cache_free(cache, ptr);
+    cache_destroy(cache);
+
+    return (ptr == destruct_data) ? TEST_PASS : TEST_FAIL;
+}
+
+static enum test_return cache_reuse_test(void)
+{
+    int ii;
+    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
+                                  NULL, NULL);
+    char *ptr = cache_alloc(cache);
+    cache_free(cache, ptr);
+    for (ii = 0; ii < 100; ++ii) {
+        char *p = cache_alloc(cache);
+        assert(p == ptr);
+        cache_free(cache, ptr);
+    }
+    cache_destroy(cache);
+    return TEST_PASS;
+}
+
+
+static enum test_return cache_bulkalloc(size_t datasize)
+{
+    cache_t *cache = cache_create("test", datasize, sizeof(char*),
+                                  NULL, NULL);
+#define ITERATIONS 1024
+    void *ptr[ITERATIONS];
+
+    for (int ii = 0; ii < ITERATIONS; ++ii) {
+        ptr[ii] = cache_alloc(cache);
+        assert(ptr[ii] != 0);
+        memset(ptr[ii], 0xff, datasize);
+    }
+
+    for (int ii = 0; ii < ITERATIONS; ++ii) {
+        cache_free(cache, ptr[ii]);
+    }
+
+#undef ITERATIONS
+    cache_destroy(cache);
+    return TEST_PASS;
+}
+
+static enum test_return test_issue_161(void)
+{
+    enum test_return ret = cache_bulkalloc(1);
+    if (ret == TEST_PASS) {
+        ret = cache_bulkalloc(512);
+    }
+
+    return ret;
+}
+
+static enum test_return cache_redzone_test(void)
+{
+#ifndef HAVE_UMEM_H
+    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
+                                  NULL, NULL);
+
+    /* Ignore SIGABRT */
+    struct sigaction old_action;
+    struct sigaction action = { .sa_handler = SIG_IGN, .sa_flags = 0};
+    sigemptyset(&action.sa_mask);
+    sigaction(SIGABRT, &action, &old_action);
+
+    /* check memory debug.. */
+    char *p = cache_alloc(cache);
+    char old = *(p - 1);
+    *(p - 1) = 0;
+    cache_free(cache, p);
+    assert(cache_error == -1);
+    *(p - 1) = old;
+
+    p[sizeof(uint32_t)] = 0;
+    cache_free(cache, p);
+    assert(cache_error == 1);
+
+    /* restore signal handler */
+    sigaction(SIGABRT, &old_action, NULL);
+
+    cache_destroy(cache);
+
+    return TEST_PASS;
+#else
+    return TEST_SKIP;
+#endif
+}
+
+static enum test_return test_stats_prefix_find(void) {
+    PREFIX_STATS *pfs1, *pfs2;
+
+    stats_prefix_clear();
+    pfs1 = stats_prefix_find("abc", 3);
+    assert(pfs1 == NULL);
+    pfs1 = stats_prefix_find("abc|", 4);
+    assert(pfs1 == NULL);
+
+    pfs1 = stats_prefix_find("abc:", 4);
+    assert(pfs1 != NULL);
+    assert(0ULL == (pfs1->num_gets + pfs1->num_sets + pfs1->num_deletes + pfs1->num_hits));
+    pfs2 = stats_prefix_find("abc:", 4);
+    assert(pfs1 == pfs2);
+    pfs2 = stats_prefix_find("abc:d", 5);
+    assert(pfs1 == pfs2);
+    pfs2 = stats_prefix_find("xyz123:", 6);
+    assert(pfs1 != pfs2);
+    pfs2 = stats_prefix_find("ab:", 3);
+    assert(pfs1 != pfs2);
+    return TEST_PASS;
+}
+
+static enum test_return test_stats_prefix_record_get(void) {
+    PREFIX_STATS *pfs;
+    stats_prefix_clear();
+
+    stats_prefix_record_get("abc:123", 7, false);
+    pfs = stats_prefix_find("abc:123", 7);
+    assert(1 == pfs->num_gets);
+    assert(0 == pfs->num_hits);
+    stats_prefix_record_get("abc:456", 7, false);
+    assert(2 == pfs->num_gets);
+    assert(0 == pfs->num_hits);
+    stats_prefix_record_get("abc:456", 7, true);
+    assert(3 == pfs->num_gets);
+    assert(1 == pfs->num_hits);
+    stats_prefix_record_get("def:", 4, true);
+    assert(3 == pfs->num_gets);
+    assert(1 == pfs->num_hits);
+    return TEST_PASS;
+}
+
+static enum test_return test_stats_prefix_record_delete(void) {
+    PREFIX_STATS *pfs;
+    stats_prefix_clear();
+
+    stats_prefix_record_delete("abc:123", 7);
+    pfs = stats_prefix_find("abc:123", 7);
+    assert(0 == pfs->num_gets);
+    assert(0 == pfs->num_hits);
+    assert(1 == pfs->num_deletes);
+    assert(0 == pfs->num_sets);
+    stats_prefix_record_delete("def:", 4);
+    assert(1 == pfs->num_deletes);
+    return TEST_PASS;
+}
+
+static enum test_return test_stats_prefix_record_set(void) {
+    PREFIX_STATS *pfs;
+    stats_prefix_clear();
+
+    stats_prefix_record_set("abc:123", 7);
+    pfs = stats_prefix_find("abc:123", 7);
+    assert(0 == pfs->num_gets);
+    assert(0 == pfs->num_hits);
+    assert(0 == pfs->num_deletes);
+    assert(1 == pfs->num_sets);
+    stats_prefix_record_delete("def:", 4);
+    assert(1 == pfs->num_sets);
+    return TEST_PASS;
+}
+
+static enum test_return test_stats_prefix_dump(void) {
+    int hashval = hash("abc", 3) % PREFIX_HASH_SIZE;
+    char tmp[500];
+    char *buf;
+    const char *expected;
+    int keynum;
+    int length;
+
+    stats_prefix_clear();
+
+    assert(strcmp("END\r\n", (buf = stats_prefix_dump(&length))) == 0);
+    assert(5 == length);
+    stats_prefix_record_set("abc:123", 7);
+    free(buf);
+    expected = "PREFIX abc get 0 hit 0 set 1 del 0\r\nEND\r\n";
+    assert(strcmp(expected, (buf = stats_prefix_dump(&length))) == 0);
+    assert(strlen(expected) == length);
+    stats_prefix_record_get("abc:123", 7, false);
+    free(buf);
+    expected = "PREFIX abc get 1 hit 0 set 1 del 0\r\nEND\r\n";
+    assert(strcmp(expected, (buf = stats_prefix_dump(&length))) == 0);
+    assert(strlen(expected) == length);
+    stats_prefix_record_get("abc:123", 7, true);
+    free(buf);
+    expected = "PREFIX abc get 2 hit 1 set 1 del 0\r\nEND\r\n";
+    assert(strcmp(expected, (buf = stats_prefix_dump(&length))) == 0);
+    assert(strlen(expected) == length);
+    stats_prefix_record_delete("abc:123", 7);
+    free(buf);
+    expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\nEND\r\n";
+    assert(strcmp(expected, (buf = stats_prefix_dump(&length))) == 0);
+    assert(strlen(expected) == length);
+
+    stats_prefix_record_delete("def:123", 7);
+    free(buf);
+    /* NOTE: Prefixes can be dumped in any order, so we verify that
+       each expected line is present in the string. */
+    buf = stats_prefix_dump(&length);
+    assert(strstr(buf, "PREFIX abc get 2 hit 1 set 1 del 1\r\n") != NULL);
+    assert(strstr(buf, "PREFIX def get 0 hit 0 set 0 del 1\r\n") != NULL);
+    assert(strstr(buf, "END\r\n") != NULL);
+    free(buf);
+
+    /* Find a key that hashes to the same bucket as "abc" */
+    bool found_match = false;
+    for (keynum = 0; keynum < PREFIX_HASH_SIZE * 100; keynum++) {
+        snprintf(tmp, sizeof(tmp), "%d:", keynum);
+        /* -1 because only the prefix portion is used when hashing */
+        if (hashval == hash(tmp, strlen(tmp) - 1) % PREFIX_HASH_SIZE) {
+            found_match = true;
+            break;
+        }
+    }
+    assert(found_match);
+    stats_prefix_record_set(tmp, strlen(tmp));
+    buf = stats_prefix_dump(&length);
+    assert(strstr(buf, "PREFIX abc get 2 hit 1 set 1 del 1\r\n") != NULL);
+    assert(strstr(buf, "PREFIX def get 0 hit 0 set 0 del 1\r\n") != NULL);
+    assert(strstr(buf, "END\r\n") != NULL);
+    snprintf(tmp, sizeof(tmp), "PREFIX %d get 0 hit 0 set 1 del 0\r\n", keynum);
+    assert(strstr(buf, tmp) != NULL);
+    free(buf);
+
+    /* Marking the end of these tests */
+    stats_prefix_clear();
+
+    return TEST_PASS;
+}
+
+static enum test_return test_safe_strtoul(void) {
+    uint32_t val;
+    assert(safe_strtoul("123", &val));
+    assert(val == 123);
+    assert(safe_strtoul("+123", &val));
+    assert(val == 123);
+    assert(!safe_strtoul("", &val));  // empty
+    assert(!safe_strtoul("123BOGUS", &val));  // non-numeric
+    assert(!safe_strtoul(" issue221", &val));  // non-numeric
+    /* Not sure what it does, but this works with ICC :/
+       assert(!safe_strtoul("92837498237498237498029383", &val)); // out of range
+    */
+
+    // extremes:
+    assert(safe_strtoul("4294967295", &val)); // 2**32 - 1
+    assert(val == 4294967295L);
+    /* This actually works on 64-bit ubuntu
+       assert(!safe_strtoul("4294967296", &val)); // 2**32
+    */
+    assert(!safe_strtoul("-1", &val));  // negative
+    return TEST_PASS;
+}
+
+
+static enum test_return test_safe_strtoull(void) {
+    uint64_t val;
+    assert(safe_strtoull("123", &val));
+    assert(val == 123);
+    assert(safe_strtoull("+123", &val));
+    assert(val == 123);
+    assert(!safe_strtoull("", &val));  // empty
+    assert(!safe_strtoull("123BOGUS", &val));  // non-numeric
+    assert(!safe_strtoull("92837498237498237498029383", &val)); // out of range
+    assert(!safe_strtoull(" issue221", &val));  // non-numeric
+
+    // extremes:
+    assert(safe_strtoull("18446744073709551615", &val)); // 2**64 - 1
+    assert(val == 18446744073709551615ULL);
+    assert(!safe_strtoull("18446744073709551616", &val)); // 2**64
+    assert(!safe_strtoull("-1", &val));  // negative
+    return TEST_PASS;
+}
+
+static enum test_return test_safe_strtoll(void) {
+    int64_t val;
+    assert(safe_strtoll("123", &val));
+    assert(val == 123);
+    assert(safe_strtoll("+123", &val));
+    assert(val == 123);
+    assert(safe_strtoll("-123", &val));
+    assert(val == -123);
+    assert(!safe_strtoll("", &val));  // empty
+    assert(!safe_strtoll("123BOGUS", &val));  // non-numeric
+    assert(!safe_strtoll("92837498237498237498029383", &val)); // out of range
+    assert(!safe_strtoll(" issue221", &val));  // non-numeric
+
+    // extremes:
+    assert(!safe_strtoll("18446744073709551615", &val)); // 2**64 - 1
+    assert(safe_strtoll("9223372036854775807", &val)); // 2**63 - 1
+    assert(val == 9223372036854775807LL);
+    /*
+      assert(safe_strtoll("-9223372036854775808", &val)); // -2**63
+      assert(val == -9223372036854775808LL);
+    */
+    assert(!safe_strtoll("-9223372036854775809", &val)); // -2**63 - 1
+
+    // We'll allow space to terminate the string.  And leading space.
+    assert(safe_strtoll(" 123 foo", &val));
+    assert(val == 123);
+    return TEST_PASS;
+}
+
+static enum test_return test_safe_strtol(void) {
+    int32_t val;
+    assert(safe_strtol("123", &val));
+    assert(val == 123);
+    assert(safe_strtol("+123", &val));
+    assert(val == 123);
+    assert(safe_strtol("-123", &val));
+    assert(val == -123);
+    assert(!safe_strtol("", &val));  // empty
+    assert(!safe_strtol("123BOGUS", &val));  // non-numeric
+    assert(!safe_strtol("92837498237498237498029383", &val)); // out of range
+    assert(!safe_strtol(" issue221", &val));  // non-numeric
+
+    // extremes:
+    /* This actually works on 64-bit ubuntu
+       assert(!safe_strtol("2147483648", &val)); // (expt 2.0 31.0)
+    */
+    assert(safe_strtol("2147483647", &val)); // (- (expt 2.0 31) 1)
+    assert(val == 2147483647L);
+    /* This actually works on 64-bit ubuntu
+       assert(!safe_strtol("-2147483649", &val)); // (- (expt -2.0 31) 1)
+    */
+
+    // We'll allow space to terminate the string.  And leading space.
+    assert(safe_strtol(" 123 foo", &val));
+    assert(val == 123);
+    return TEST_PASS;
+}
+
+/**
+ * Function to start the server and let it listen on a random port
+ *
+ * @param port_out where to store the TCP port number the server is
+ *                 listening on
+ * @param daemon set to true if you want to run the memcached server
+ *               as a daemon process
+ * @return the pid of the memcached server
+ */
+static pid_t start_server(in_port_t *port_out, bool daemon, int timeout) {
+    char environment[80];
+    snprintf(environment, sizeof(environment),
+             "MEMCACHED_PORT_FILENAME=/tmp/ports.%lu", (long)getpid());
+    char *filename= environment + strlen("MEMCACHED_PORT_FILENAME=");
+    char pid_file[80];
+    snprintf(pid_file, sizeof(pid_file), "/tmp/pid.%lu", (long)getpid());
+
+    remove(filename);
+    remove(pid_file);
+
+#ifdef __sun
+    /* I want to name the corefiles differently so that they don't
+       overwrite each other
+    */
+    char coreadm[128];
+    snprintf(coreadm, sizeof(coreadm),
+             "coreadm -p core.%%f.%%p %lu", (unsigned long)getpid());
+    system(coreadm);
+#endif
+
+    pid_t pid = fork();
+    assert(pid != -1);
+
+    if (pid == 0) {
+        /* Child */
+        char *argv[24];
+        int arg = 0;
+        char tmo[24];
+        snprintf(tmo, sizeof(tmo), "%u", timeout);
+
+        putenv(environment);
+#ifdef __sun
+        putenv("LD_PRELOAD=watchmalloc.so.1");
+        putenv("MALLOC_DEBUG=WATCH");
+#endif
+
+        if (!daemon) {
+            argv[arg++] = "./timedrun";
+            argv[arg++] = tmo;
+        }
+        argv[arg++] = "./memcached-debug";
+        argv[arg++] = "-A";
+        argv[arg++] = "-p";
+        argv[arg++] = "-1";
+        argv[arg++] = "-U";
+        argv[arg++] = "0";
+#ifdef TLS
+        if (enable_ssl) {
+            argv[arg++] = "-Z";
+            argv[arg++] = "-o";
+            argv[arg++] = "ssl_chain_cert=t/server_crt.pem";
+            argv[arg++] = "-o";
+            argv[arg++] = "ssl_key=t/server_key.pem";
+        }
+#endif
+        /* Handle rpmbuild and the like doing this as root */
+        if (getuid() == 0) {
+            argv[arg++] = "-u";
+            argv[arg++] = "root";
+        }
+        if (daemon) {
+            argv[arg++] = "-d";
+            argv[arg++] = "-P";
+            argv[arg++] = pid_file;
+        }
+#ifdef MESSAGE_DEBUG
+         argv[arg++] = "-vvv";
+#endif
+#ifdef HAVE_DROP_PRIVILEGES
+        argv[arg++] = "-o";
+        argv[arg++] = "relaxed_privileges";
+#endif
+        argv[arg++] = NULL;
+        assert(execv(argv[0], argv) != -1);
+    }
+
+    /* Yeah just let us "busy-wait" for the file to be created ;-) */
+    while (access(filename, F_OK) == -1) {
+        usleep(10);
+    }
+
+    FILE *fp = fopen(filename, "r");
+    if (fp == NULL) {
+        fprintf(stderr, "Failed to open the file containing port numbers: %s\n",
+                strerror(errno));
+        assert(false);
+    }
+
+    *port_out = (in_port_t)-1;
+    char buffer[80];
+    while ((fgets(buffer, sizeof(buffer), fp)) != NULL) {
+        if (strncmp(buffer, "TCP INET: ", 10) == 0) {
+            int32_t val;
+            assert(safe_strtol(buffer + 10, &val));
+            *port_out = (in_port_t)val;
+        }
+    }
+    fclose(fp);
+    assert(remove(filename) == 0);
+
+    if (daemon) {
+        /* loop and wait for the pid file.. There is a potential race
+         * condition that the server just created the file but isn't
+         * finished writing the content, so we loop a few times
+         * reading as well */
+        while (access(pid_file, F_OK) == -1) {
+            usleep(10);
+        }
+
+        fp = fopen(pid_file, "r");
+        if (fp == NULL) {
+            fprintf(stderr, "Failed to open pid file: %s\n",
+                    strerror(errno));
+            assert(false);
+        }
+
+        /* Avoid race by retrying 20 times */
+        for (int x = 0; x < 20 && fgets(buffer, sizeof(buffer), fp) == NULL; x++) {
+            usleep(10);
+        }
+        fclose(fp);
+
+        int32_t val;
+        assert(safe_strtol(buffer, &val));
+        pid = (pid_t)val;
+    }
+
+    return pid;
+}
+
+static enum test_return test_issue_44(void) {
+    in_port_t port;
+    pid_t pid = start_server(&port, true, 15);
+    assert(kill(pid, SIGHUP) == 0);
+    sleep(1);
+    assert(kill(pid, SIGTERM) == 0);
+
+    return TEST_PASS;
+}
+
+static struct addrinfo *lookuphost(const char *hostname, in_port_t port)
+{
+    struct addrinfo *ai = 0;
+    struct addrinfo hints = { .ai_family = AF_UNSPEC,
+                              .ai_protocol = IPPROTO_TCP,
+                              .ai_socktype = SOCK_STREAM };
+    char service[NI_MAXSERV];
+    int error;
+
+    (void)snprintf(service, NI_MAXSERV, "%d", port);
+    if ((error = getaddrinfo(hostname, service, &hints, &ai)) != 0) {
+       if (error != EAI_SYSTEM) {
+          fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(error));
+       } else {
+          perror("getaddrinfo()");
+       }
+    }
+
+    return ai;
+}
+
+struct conn *connect_server(const char *hostname, in_port_t port,
+                            bool nonblock, const bool ssl)
+{
+    struct conn *c;
+    if (!(c = (struct conn *)calloc(1, sizeof(struct conn)))) {
+        fprintf(stderr, "Failed to allocate the client connection: %s\n",
+                strerror(errno));
+        return NULL;
+    }
+
+    struct addrinfo *ai = lookuphost(hostname, port);
+    int sock = -1;
+    if (ai != NULL) {
+       if ((sock = socket(ai->ai_family, ai->ai_socktype,
+                          ai->ai_protocol)) != -1) {
+          if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
+             fprintf(stderr, "Failed to connect socket: %s\n",
+                     strerror(errno));
+             close(sock);
+             sock = -1;
+          } else if (nonblock) {
+              int flags = fcntl(sock, F_GETFL, 0);
+              if (flags < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
+                  fprintf(stderr, "Failed to enable nonblocking mode: %s\n",
+                          strerror(errno));
+                  close(sock);
+                  sock = -1;
+              }
+          }
+       } else {
+          fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
+       }
+
+       freeaddrinfo(ai);
+    }
+    c->sock = sock;
+#ifdef TLS
+    if (sock > 0 && ssl) {
+        c->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+        if (c->ssl_ctx == NULL) {
+            fprintf(stderr, "Failed to create the SSL context: %s\n",
+                strerror(errno));
+            close(sock);
+            sock = -1;
+        }
+        c->ssl = SSL_new(c->ssl_ctx);
+        if (c->ssl == NULL) {
+            fprintf(stderr, "Failed to create the SSL object: %s\n",
+                strerror(errno));
+            close(sock);
+            sock = -1;
+        }
+        SSL_set_fd (c->ssl, c->sock);
+        int ret = SSL_connect(c->ssl);
+        if (ret < 0) {
+            int err = SSL_get_error(c->ssl, ret);
+            if (err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) {
+                fprintf(stderr, "SSL connection failed with error code : %s\n",
+                    strerror(errno));
+                close(sock);
+                sock = -1;
+            }
+        }
+        c->read = ssl_read;
+        c->write = ssl_write;
+    } else
+#endif
+    {
+        c->read = tcp_read;
+        c->write = tcp_write;
+    }
+    return c;
+}
+
+static enum test_return test_vperror(void) {
+    int rv = 0;
+    int oldstderr = dup(STDERR_FILENO);
+    assert(oldstderr >= 0);
+    char tmpl[sizeof(TMP_TEMPLATE)+1];
+    strncpy(tmpl, TMP_TEMPLATE, sizeof(TMP_TEMPLATE)+1);
+
+    int newfile = mkstemp(tmpl);
+    assert(newfile > 0);
+    rv = dup2(newfile, STDERR_FILENO);
+    assert(rv == STDERR_FILENO);
+    rv = close(newfile);
+    assert(rv == 0);
+
+    errno = EIO;
+    vperror("Old McDonald had a farm.  %s", "EI EIO");
+
+    /* Restore stderr */
+    rv = dup2(oldstderr, STDERR_FILENO);
+    assert(rv == STDERR_FILENO);
+
+
+    /* Go read the file */
+    char buf[80] = { 0 };
+    FILE *efile = fopen(tmpl, "r");
+    assert(efile);
+    char *prv = fgets(buf, sizeof(buf), efile);
+    assert(prv);
+    fclose(efile);
+
+    unlink(tmpl);
+
+    char expected[80] = { 0 };
+    snprintf(expected, sizeof(expected),
+             "Old McDonald had a farm.  EI EIO: %s\n", strerror(EIO));
+
+    /*
+    fprintf(stderr,
+            "\nExpected:  ``%s''"
+            "\nGot:       ``%s''\n", expected, buf);
+    */
+
+    return strcmp(expected, buf) == 0 ? TEST_PASS : TEST_FAIL;
+}
+
+void send_ascii_command(const char *buf) {
+    off_t offset = 0;
+    const char* ptr = buf;
+    size_t len = strlen(buf);
+
+    do {
+        ssize_t nw = con->write((void*)con, ptr + offset, len - offset);
+        if (nw == -1) {
+            if (errno != EINTR) {
+                fprintf(stderr, "Failed to write: %s\n", strerror(errno));
+                abort();
+            }
+        } else {
+            offset += nw;
+        }
+    } while (offset < len);
+}
+
+/*
+ * This is a dead slow single byte read, but it should only read out
+ * _one_ response and I don't have an input buffer... The current
+ * implementation only supports single-line responses, so if you want to use
+ * it for get commands you need to implement that first ;-)
+ */
+void read_ascii_response(char *buffer, size_t size) {
+    off_t offset = 0;
+    bool need_more = true;
+    do {
+        ssize_t nr = con->read(con, buffer + offset, 1);
+        if (nr == -1) {
+            if (errno != EINTR) {
+                fprintf(stderr, "Failed to read: %s\n", strerror(errno));
+                abort();
+            }
+        } else {
+            assert(nr == 1);
+            if (buffer[offset] == '\n') {
+                need_more = false;
+                buffer[offset + 1] = '\0';
+            }
+            offset += nr;
+            assert(offset + 1 < size);
+        }
+    } while (need_more);
+}
+
+static enum test_return test_issue_92(void) {
+    char buffer[1024];
+
+    close_conn();
+    con = connect_server("127.0.0.1", port, false, enable_ssl);
+    assert(con);
+
+    send_ascii_command("stats cachedump 1 0 0\r\n");
+
+    read_ascii_response(buffer, sizeof(buffer));
+    assert(strncmp(buffer, "END", strlen("END")) == 0);
+
+    send_ascii_command("stats cachedump 200 0 0\r\n");
+    read_ascii_response(buffer, sizeof(buffer));
+    assert(strncmp(buffer, "CLIENT_ERROR", strlen("CLIENT_ERROR")) == 0);
+
+    close_conn();
+    con = connect_server("127.0.0.1", port, false, enable_ssl);
+    assert(con);
+    return TEST_PASS;
+}
+
+static enum test_return test_issue_102(void) {
+    char buffer[4096];
+    memset(buffer, ' ', sizeof(buffer));
+    buffer[sizeof(buffer) - 1] = '\0';
+
+    close_conn();
+    con = connect_server("127.0.0.1", port, false, enable_ssl);
+    assert(con);
+
+    send_ascii_command(buffer);
+    /* verify that the server closed the connection */
+    assert(con->read(con, buffer, sizeof(buffer)) == 0);
+
+    close_conn();
+    con = connect_server("127.0.0.1", port, false, enable_ssl);
+    assert(con);
+
+    snprintf(buffer, sizeof(buffer), "gets ");
+    size_t offset = 5;
+    while (offset < 4000) {
+        offset += snprintf(buffer + offset, sizeof(buffer) - offset,
+                           "%010u ", (unsigned int)offset);
+    }
+
+    send_ascii_command(buffer);
+    usleep(250);
+
+    send_ascii_command("\r\n");
+    char rsp[80];
+    read_ascii_response(rsp, sizeof(rsp));
+    assert(strncmp(rsp, "END", strlen("END")) == 0);
+    buffer[3]= ' ';
+    send_ascii_command(buffer);
+    usleep(250);
+    send_ascii_command("\r\n");
+    read_ascii_response(rsp, sizeof(rsp));
+    assert(strncmp(rsp, "END", strlen("END")) == 0);
+
+    memset(buffer, ' ', sizeof(buffer));
+    int len = snprintf(buffer + 101, sizeof(buffer) - 101, "gets foo");
+    buffer[101 + len] = ' ';
+    buffer[sizeof(buffer) - 1] = '\0';
+    send_ascii_command(buffer);
+    /* verify that the server closed the connection */
+    assert(con->read(con, buffer, sizeof(buffer)) == 0);
+
+    close_conn();
+    con = connect_server("127.0.0.1", port, false, enable_ssl);
+    assert(con);
+
+    return TEST_PASS;
+}
+
+static enum test_return start_memcached_server(void) {
+    server_pid = start_server(&port, false, 600);
+    close_conn();
+    con = connect_server("127.0.0.1", port, false, enable_ssl);
+    assert(con);
+    return TEST_PASS;
+}
+
+static enum test_return stop_memcached_server(void) {
+    close_conn();
+    if (server_pid != -1) {
+        assert(kill(server_pid, SIGTERM) == 0);
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return shutdown_memcached_server(void) {
+    char buffer[1024];
+
+    close_conn();
+    con = connect_server("127.0.0.1", port, false, enable_ssl);
+    assert(con);
+
+    send_ascii_command("shutdown\r\n");
+    /* verify that the server closed the connection */
+    assert(con->read(con, buffer, sizeof(buffer)) == 0);
+
+    close_conn();
+
+    /* We set server_pid to -1 so that we don't later call kill() */
+    if (kill(server_pid, 0) == 0) {
+        server_pid = -1;
+    }
+
+    return TEST_PASS;
+}
+
+static void safe_send(const void* buf, size_t len, bool hickup)
+{
+    off_t offset = 0;
+    const char* ptr = buf;
+#ifdef MESSAGE_DEBUG
+    uint8_t val = *ptr;
+    assert(val == (uint8_t)0x80);
+    fprintf(stderr, "About to send %lu bytes:", (unsigned long)len);
+    for (int ii = 0; ii < len; ++ii) {
+        if (ii % 4 == 0) {
+            fprintf(stderr, "\n   ");
+        }
+        val = *(ptr + ii);
+        fprintf(stderr, " 0x%02x", val);
+    }
+    fprintf(stderr, "\n");
+    usleep(500);
+#endif
+
+    do {
+        size_t num_bytes = len - offset;
+        if (hickup) {
+            if (num_bytes > 1024) {
+                num_bytes = (rand() % 1023) + 1;
+            }
+        }
+        ssize_t nw = con->write(con, ptr + offset, num_bytes);
+        if (nw == -1) {
+            if (errno != EINTR) {
+                fprintf(stderr, "Failed to write: %s\n", strerror(errno));
+                abort();
+            }
+        } else {
+            if (hickup) {
+                usleep(100);
+            }
+            offset += nw;
+        }
+    } while (offset < len);
+}
+
+static bool safe_recv(void *buf, size_t len) {
+    if (len == 0) {
+        return true;
+    }
+    off_t offset = 0;
+    do {
+        ssize_t nr = con->read(con, ((char*)buf) + offset, len - offset);
+        if (nr == -1) {
+            if (errno != EINTR) {
+                fprintf(stderr, "Failed to read: %s\n", strerror(errno));
+                abort();
+            }
+        } else {
+            if (nr == 0 && allow_closed_read) {
+                return false;
+            }
+            assert(nr != 0);
+            offset += nr;
+        }
+    } while (offset < len);
+
+    return true;
+}
+
+static bool safe_recv_packet(void *buf, size_t size) {
+    protocol_binary_response_no_extras *response = buf;
+    assert(size > sizeof(*response));
+    if (!safe_recv(response, sizeof(*response))) {
+        return false;
+    }
+    response->message.header.response.keylen = ntohs(response->message.header.response.keylen);
+    response->message.header.response.status = ntohs(response->message.header.response.status);
+    response->message.header.response.bodylen = ntohl(response->message.header.response.bodylen);
+
+    size_t len = sizeof(*response);
+
+    char *ptr = buf;
+    ptr += len;
+    if (!safe_recv(ptr, response->message.header.response.bodylen)) {
+        return false;
+    }
+
+#ifdef MESSAGE_DEBUG
+    usleep(500);
+    ptr = buf;
+    len += response->message.header.response.bodylen;
+    uint8_t val = *ptr;
+    assert(val == (uint8_t)0x81);
+    fprintf(stderr, "Received %lu bytes:", (unsigned long)len);
+    for (int ii = 0; ii < len; ++ii) {
+        if (ii % 4 == 0) {
+            fprintf(stderr, "\n   ");
+        }
+        val = *(ptr + ii);
+        fprintf(stderr, " 0x%02x", val);
+    }
+    fprintf(stderr, "\n");
+#endif
+    return true;
+}
+
+static off_t storage_command(char*buf,
+                             size_t bufsz,
+                             uint8_t cmd,
+                             const void* key,
+                             size_t keylen,
+                             const void* dta,
+                             size_t dtalen,
+                             uint32_t flags,
+                             uint32_t exp) {
+    /* all of the storage commands use the same command layout */
+    protocol_binary_request_set *request = (void*)buf;
+    assert(bufsz > sizeof(*request) + keylen + dtalen);
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+    request->message.header.request.keylen = htons(keylen);
+    request->message.header.request.extlen = 8;
+    request->message.header.request.bodylen = htonl(keylen + 8 + dtalen);
+    request->message.header.request.opaque = 0xdeadbeef;
+    request->message.body.flags = flags;
+    request->message.body.expiration = exp;
+
+    off_t key_offset = sizeof(protocol_binary_request_no_extras) + 8;
+
+    memcpy(buf + key_offset, key, keylen);
+    if (dta != NULL) {
+        memcpy(buf + key_offset + keylen, dta, dtalen);
+    }
+
+    return key_offset + keylen + dtalen;
+}
+
+static off_t ext_command(char* buf,
+                         size_t bufsz,
+                         uint8_t cmd,
+                         const void* ext,
+                         size_t extlen,
+                         const void* key,
+                         size_t keylen,
+                         const void* dta,
+                         size_t dtalen) {
+    protocol_binary_request_no_extras *request = (void*)buf;
+    assert(bufsz > sizeof(*request) + extlen + keylen + dtalen);
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+    request->message.header.request.extlen = extlen;
+    request->message.header.request.keylen = htons(keylen);
+    request->message.header.request.bodylen = htonl(extlen + keylen + dtalen);
+    request->message.header.request.opaque = 0xdeadbeef;
+
+    off_t ext_offset = sizeof(protocol_binary_request_no_extras);
+    off_t key_offset = ext_offset + extlen;
+    off_t dta_offset = key_offset + keylen;
+
+    if (ext != NULL) {
+        memcpy(buf + ext_offset, ext, extlen);
+    }
+    if (key != NULL) {
+        memcpy(buf + key_offset, key, keylen);
+    }
+    if (dta != NULL) {
+        memcpy(buf + dta_offset, dta, dtalen);
+    }
+
+    return sizeof(*request) + extlen + keylen + dtalen;
+}
+
+static off_t raw_command(char* buf,
+                         size_t bufsz,
+                         uint8_t cmd,
+                         const void* key,
+                         size_t keylen,
+                         const void* dta,
+                         size_t dtalen) {
+    /* all of the storage commands use the same command layout */
+    return ext_command(buf, bufsz, cmd, NULL, 0, key, keylen, dta, dtalen);
+}
+
+static off_t flush_command(char* buf, size_t bufsz, uint8_t cmd, uint32_t exptime, bool use_extra) {
+    protocol_binary_request_flush *request = (void*)buf;
+    assert(bufsz > sizeof(*request));
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+
+    off_t size = sizeof(protocol_binary_request_no_extras);
+    if (use_extra) {
+        request->message.header.request.extlen = 4;
+        request->message.body.expiration = htonl(exptime);
+        request->message.header.request.bodylen = htonl(4);
+        size += 4;
+    }
+
+    request->message.header.request.opaque = 0xdeadbeef;
+
+    return size;
+}
+
+
+static off_t touch_command(char* buf,
+                           size_t bufsz,
+                           uint8_t cmd,
+                           const void* key,
+                           size_t keylen,
+                           uint32_t exptime) {
+    protocol_binary_request_touch *request = (void*)buf;
+    assert(bufsz > sizeof(*request));
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+
+    request->message.header.request.keylen = htons(keylen);
+    request->message.header.request.extlen = 4;
+    request->message.body.expiration = htonl(exptime);
+    request->message.header.request.bodylen = htonl(keylen + 4);
+
+    request->message.header.request.opaque = 0xdeadbeef;
+
+    off_t key_offset = sizeof(protocol_binary_request_no_extras) + 4;
+
+    memcpy(buf + key_offset, key, keylen);
+    return sizeof(protocol_binary_request_no_extras) + 4 + keylen;
+}
+
+static off_t arithmetic_command(char* buf,
+                                size_t bufsz,
+                                uint8_t cmd,
+                                const void* key,
+                                size_t keylen,
+                                uint64_t delta,
+                                uint64_t initial,
+                                uint32_t exp) {
+    protocol_binary_request_incr *request = (void*)buf;
+    assert(bufsz > sizeof(*request) + keylen);
+
+    memset(request, 0, sizeof(*request));
+    request->message.header.request.magic = PROTOCOL_BINARY_REQ;
+    request->message.header.request.opcode = cmd;
+    request->message.header.request.keylen = htons(keylen);
+    request->message.header.request.extlen = 20;
+    request->message.header.request.bodylen = htonl(keylen + 20);
+    request->message.header.request.opaque = 0xdeadbeef;
+    request->message.body.delta = htonll(delta);
+    request->message.body.initial = htonll(initial);
+    request->message.body.expiration = htonl(exp);
+
+    off_t key_offset = sizeof(protocol_binary_request_no_extras) + 20;
+
+    memcpy(buf + key_offset, key, keylen);
+    return key_offset + keylen;
+}
+
+static void validate_response_header(protocol_binary_response_no_extras *response,
+                                     uint8_t cmd, uint16_t status)
+{
+    assert(response->message.header.response.magic == PROTOCOL_BINARY_RES);
+    assert(response->message.header.response.opcode == cmd);
+    assert(response->message.header.response.datatype == PROTOCOL_BINARY_RAW_BYTES);
+    assert(response->message.header.response.status == status);
+    assert(response->message.header.response.opaque == 0xdeadbeef);
+
+    if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
+        switch (cmd) {
+        case PROTOCOL_BINARY_CMD_ADDQ:
+        case PROTOCOL_BINARY_CMD_APPENDQ:
+        case PROTOCOL_BINARY_CMD_DECREMENTQ:
+        case PROTOCOL_BINARY_CMD_DELETEQ:
+        case PROTOCOL_BINARY_CMD_FLUSHQ:
+        case PROTOCOL_BINARY_CMD_INCREMENTQ:
+        case PROTOCOL_BINARY_CMD_PREPENDQ:
+        case PROTOCOL_BINARY_CMD_QUITQ:
+        case PROTOCOL_BINARY_CMD_REPLACEQ:
+        case PROTOCOL_BINARY_CMD_SETQ:
+            assert("Quiet command shouldn't return on success" == NULL);
+        default:
+            break;
+        }
+
+        switch (cmd) {
+        case PROTOCOL_BINARY_CMD_ADD:
+        case PROTOCOL_BINARY_CMD_REPLACE:
+        case PROTOCOL_BINARY_CMD_SET:
+        case PROTOCOL_BINARY_CMD_APPEND:
+        case PROTOCOL_BINARY_CMD_PREPEND:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 0);
+            assert(response->message.header.response.bodylen == 0);
+            assert(response->message.header.response.cas != 0);
+            break;
+        case PROTOCOL_BINARY_CMD_FLUSH:
+        case PROTOCOL_BINARY_CMD_NOOP:
+        case PROTOCOL_BINARY_CMD_QUIT:
+        case PROTOCOL_BINARY_CMD_DELETE:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 0);
+            assert(response->message.header.response.bodylen == 0);
+            assert(response->message.header.response.cas == 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_DECREMENT:
+        case PROTOCOL_BINARY_CMD_INCREMENT:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 0);
+            assert(response->message.header.response.bodylen == 8);
+            assert(response->message.header.response.cas != 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_STAT:
+            assert(response->message.header.response.extlen == 0);
+            /* key and value exists in all packets except in the terminating */
+            assert(response->message.header.response.cas == 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_VERSION:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 0);
+            assert(response->message.header.response.bodylen != 0);
+            assert(response->message.header.response.cas == 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_GET:
+        case PROTOCOL_BINARY_CMD_GETQ:
+        case PROTOCOL_BINARY_CMD_GAT:
+        case PROTOCOL_BINARY_CMD_GATQ:
+            assert(response->message.header.response.keylen == 0);
+            assert(response->message.header.response.extlen == 4);
+            assert(response->message.header.response.cas != 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_GETK:
+        case PROTOCOL_BINARY_CMD_GETKQ:
+        case PROTOCOL_BINARY_CMD_GATK:
+        case PROTOCOL_BINARY_CMD_GATKQ:
+            assert(response->message.header.response.keylen != 0);
+            assert(response->message.header.response.extlen == 4);
+            assert(response->message.header.response.cas != 0);
+            break;
+
+        default:
+            /* Undefined command code */
+            break;
+        }
+    } else {
+        assert(response->message.header.response.cas == 0);
+        assert(response->message.header.response.extlen == 0);
+        if (cmd != PROTOCOL_BINARY_CMD_GETK &&
+            cmd != PROTOCOL_BINARY_CMD_GATK) {
+            assert(response->message.header.response.keylen == 0);
+        }
+    }
+}
+
+static enum test_return test_binary_noop(void) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } buffer;
+
+    size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                             PROTOCOL_BINARY_CMD_NOOP,
+                             NULL, 0, NULL, 0);
+
+    safe_send(buffer.bytes, len, false);
+    safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+    validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_NOOP,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_quit_impl(uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } buffer;
+    size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                             cmd, NULL, 0, NULL, 0);
+
+    safe_send(buffer.bytes, len, false);
+    if (cmd == PROTOCOL_BINARY_CMD_QUIT) {
+        safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+        validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_QUIT,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    /* Socket should be closed now, read should return 0 */
+    assert(con->read(con, buffer.bytes, sizeof(buffer.bytes)) == 0);
+    close_conn();
+    con = connect_server("127.0.0.1", port, false, enable_ssl);
+    assert(con);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_quit(void) {
+    return test_binary_quit_impl(PROTOCOL_BINARY_CMD_QUIT);
+}
+
+static enum test_return test_binary_quitq(void) {
+    return test_binary_quit_impl(PROTOCOL_BINARY_CMD_QUITQ);
+}
+
+static enum test_return test_binary_set_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    uint64_t value = 0xdeadbeefdeadcafe;
+    size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd,
+                                 key, strlen(key), &value, sizeof(value),
+                                 0, 0);
+
+    /* Set should work over and over again */
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_SET) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        }
+    }
+
+    if (cmd == PROTOCOL_BINARY_CMD_SETQ) {
+        return test_binary_noop();
+    }
+
+    send.request.message.header.request.cas = receive.response.message.header.response.cas;
+    safe_send(send.bytes, len, false);
+    if (cmd == PROTOCOL_BINARY_CMD_SET) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        assert(receive.response.message.header.response.cas != send.request.message.header.request.cas);
+    } else {
+        return test_binary_noop();
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_set(void) {
+    return test_binary_set_impl("test_binary_set", PROTOCOL_BINARY_CMD_SET);
+}
+
+static enum test_return test_binary_setq(void) {
+    return test_binary_set_impl("test_binary_setq", PROTOCOL_BINARY_CMD_SETQ);
+}
+
+
+static enum test_return test_binary_add_impl(const char *key, uint8_t cmd) {
+    uint64_t value = 0xdeadbeefdeadcafe;
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd, key,
+                                 strlen(key), &value, sizeof(value),
+                                 0, 0);
+
+    /* Add should only work the first time */
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        safe_send(send.bytes, len, false);
+        if (ii == 0) {
+            if (cmd == PROTOCOL_BINARY_CMD_ADD) {
+                safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+                validate_response_header(&receive.response, cmd,
+                                         PROTOCOL_BINARY_RESPONSE_SUCCESS);
+            }
+        } else {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS);
+        }
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_add(void) {
+    return test_binary_add_impl("test_binary_add", PROTOCOL_BINARY_CMD_ADD);
+}
+
+static enum test_return test_binary_addq(void) {
+    return test_binary_add_impl("test_binary_addq", PROTOCOL_BINARY_CMD_ADDQ);
+}
+
+static enum test_return test_binary_replace_impl(const char* key, uint8_t cmd) {
+    uint64_t value = 0xdeadbeefdeadcafe;
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd,
+                                 key, strlen(key), &value, sizeof(value),
+                                 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+    len = storage_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_ADD,
+                          key, strlen(key), &value, sizeof(value), 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    len = storage_command(send.bytes, sizeof(send.bytes), cmd,
+                          key, strlen(key), &value, sizeof(value), 0, 0);
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_REPLACE) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response,
+                                     PROTOCOL_BINARY_CMD_REPLACE,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        }
+    }
+
+    if (cmd == PROTOCOL_BINARY_CMD_REPLACEQ) {
+        test_binary_noop();
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_replace(void) {
+    return test_binary_replace_impl("test_binary_replace",
+                                    PROTOCOL_BINARY_CMD_REPLACE);
+}
+
+static enum test_return test_binary_replaceq(void) {
+    return test_binary_replace_impl("test_binary_replaceq",
+                                    PROTOCOL_BINARY_CMD_REPLACEQ);
+}
+
+static enum test_return test_binary_delete_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd,
+                             key, strlen(key), NULL, 0);
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+    len = storage_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_ADD,
+                          key, strlen(key), NULL, 0, 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    len = raw_command(send.bytes, sizeof(send.bytes),
+                      cmd, key, strlen(key), NULL, 0);
+    safe_send(send.bytes, len, false);
+
+    if (cmd == PROTOCOL_BINARY_CMD_DELETE) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_DELETE,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_delete(void) {
+    return test_binary_delete_impl("test_binary_delete",
+                                   PROTOCOL_BINARY_CMD_DELETE);
+}
+
+static enum test_return test_binary_deleteq(void) {
+    return test_binary_delete_impl("test_binary_deleteq",
+                                   PROTOCOL_BINARY_CMD_DELETEQ);
+}
+
+static enum test_return test_binary_get_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+
+    uint32_t expiration = htonl(3600);
+    size_t extlen = 0;
+    if (cmd == PROTOCOL_BINARY_CMD_GAT || cmd == PROTOCOL_BINARY_CMD_GATK)
+        extlen = sizeof(expiration);
+
+    size_t len = ext_command(send.bytes, sizeof(send.bytes), cmd,
+                             extlen ? &expiration : NULL, extlen,
+                             key, strlen(key), NULL, 0);
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+
+    len = storage_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_ADD,
+                          key, strlen(key), NULL, 0,
+                          0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    /* run a little pipeline test ;-) */
+    len = 0;
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        union {
+            protocol_binary_request_no_extras request;
+            char bytes[1024];
+        } temp;
+        size_t l = ext_command(temp.bytes, sizeof(temp.bytes), cmd,
+                               extlen ? &expiration : NULL, extlen,
+                               key, strlen(key), NULL, 0);
+        memcpy(send.bytes + len, temp.bytes, l);
+        len += l;
+    }
+
+    safe_send(send.bytes, len, false);
+    for (ii = 0; ii < 10; ++ii) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_get(void) {
+    return test_binary_get_impl("test_binary_get", PROTOCOL_BINARY_CMD_GET);
+}
+
+static enum test_return test_binary_getk(void) {
+    return test_binary_get_impl("test_binary_getk", PROTOCOL_BINARY_CMD_GETK);
+}
+
+static enum test_return test_binary_gat(void) {
+    return test_binary_get_impl("test_binary_gat", PROTOCOL_BINARY_CMD_GAT);
+}
+
+static enum test_return test_binary_gatk(void) {
+    return test_binary_get_impl("test_binary_gatk", PROTOCOL_BINARY_CMD_GATK);
+}
+
+static enum test_return test_binary_getq_impl(const char *key, uint8_t cmd) {
+    const char *missing = "test_binary_getq_missing";
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, temp, receive;
+
+    uint32_t expiration = htonl(3600);
+    size_t extlen = 0;
+    if (cmd == PROTOCOL_BINARY_CMD_GATQ || cmd == PROTOCOL_BINARY_CMD_GATKQ)
+        extlen = sizeof(expiration);
+
+    size_t len = storage_command(send.bytes, sizeof(send.bytes),
+                                 PROTOCOL_BINARY_CMD_ADD,
+                                 key, strlen(key), NULL, 0,
+                                 0, 0);
+    size_t len2 = ext_command(temp.bytes, sizeof(temp.bytes), cmd,
+                              extlen ? &expiration : NULL, extlen,
+                              missing, strlen(missing), NULL, 0);
+    /* I need to change the first opaque so that I can separate the two
+     * return packets */
+    temp.request.message.header.request.opaque = 0xfeedface;
+    memcpy(send.bytes + len, temp.bytes, len2);
+    len += len2;
+
+    len2 = ext_command(temp.bytes, sizeof(temp.bytes), cmd,
+                       extlen ? &expiration : NULL, extlen,
+                       key, strlen(key), NULL, 0);
+    memcpy(send.bytes + len, temp.bytes, len2);
+    len += len2;
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    /* The first GETQ shouldn't return anything */
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_getq(void) {
+    return test_binary_getq_impl("test_binary_getq", PROTOCOL_BINARY_CMD_GETQ);
+}
+
+static enum test_return test_binary_getkq(void) {
+    return test_binary_getq_impl("test_binary_getkq", PROTOCOL_BINARY_CMD_GETKQ);
+}
+
+static enum test_return test_binary_gatq(void) {
+    return test_binary_getq_impl("test_binary_gatq", PROTOCOL_BINARY_CMD_GATQ);
+}
+
+static enum test_return test_binary_gatkq(void) {
+    return test_binary_getq_impl("test_binary_gatkq", PROTOCOL_BINARY_CMD_GATKQ);
+}
+
+static enum test_return test_binary_incr_impl(const char* key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response_header;
+        protocol_binary_response_incr response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = arithmetic_command(send.bytes, sizeof(send.bytes), cmd,
+                                    key, strlen(key), 1, 0, 0);
+
+    int ii;
+    for (ii = 0; ii < 10; ++ii) {
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_INCREMENT) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response_header, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+            assert(ntohll(receive.response.message.body.value) == ii);
+        }
+    }
+
+    if (cmd == PROTOCOL_BINARY_CMD_INCREMENTQ) {
+        test_binary_noop();
+    }
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_incr(void) {
+    return test_binary_incr_impl("test_binary_incr",
+                                 PROTOCOL_BINARY_CMD_INCREMENT);
+}
+
+static enum test_return test_binary_incrq(void) {
+    return test_binary_incr_impl("test_binary_incrq",
+                                 PROTOCOL_BINARY_CMD_INCREMENTQ);
+}
+
+static enum test_return test_binary_decr_impl(const char* key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response_header;
+        protocol_binary_response_decr response;
+        char bytes[1024];
+    } send, receive;
+    size_t len = arithmetic_command(send.bytes, sizeof(send.bytes), cmd,
+                                    key, strlen(key), 1, 9, 0);
+
+    int ii;
+    for (ii = 9; ii >= 0; --ii) {
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_DECREMENT) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response_header, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+            assert(ntohll(receive.response.message.body.value) == ii);
+        }
+    }
+
+    /* decr on 0 should not wrap */
+    safe_send(send.bytes, len, false);
+    if (cmd == PROTOCOL_BINARY_CMD_DECREMENT) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response_header, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        assert(ntohll(receive.response.message.body.value) == 0);
+    } else {
+        test_binary_noop();
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_decr(void) {
+    return test_binary_decr_impl("test_binary_decr",
+                                 PROTOCOL_BINARY_CMD_DECREMENT);
+}
+
+static enum test_return test_binary_decrq(void) {
+    return test_binary_decr_impl("test_binary_decrq",
+                                 PROTOCOL_BINARY_CMD_DECREMENTQ);
+}
+
+static enum test_return test_binary_version(void) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } buffer;
+
+    size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                             PROTOCOL_BINARY_CMD_VERSION,
+                             NULL, 0, NULL, 0);
+
+    safe_send(buffer.bytes, len, false);
+    safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+    validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_VERSION,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_flush_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+
+    size_t len = storage_command(send.bytes, sizeof(send.bytes),
+                                 PROTOCOL_BINARY_CMD_ADD,
+                                 key, strlen(key), NULL, 0, 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    len = flush_command(send.bytes, sizeof(send.bytes), cmd, 2, true);
+    safe_send(send.bytes, len, false);
+    if (cmd == PROTOCOL_BINARY_CMD_FLUSH) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_GET,
+                      key, strlen(key), NULL, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    sleep(2);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET,
+                             PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+
+    int ii;
+    for (ii = 0; ii < 2; ++ii) {
+        len = storage_command(send.bytes, sizeof(send.bytes),
+                              PROTOCOL_BINARY_CMD_ADD,
+                              key, strlen(key), NULL, 0, 0, 0);
+        safe_send(send.bytes, len, false);
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+        len = flush_command(send.bytes, sizeof(send.bytes), cmd, 0, ii == 0);
+        safe_send(send.bytes, len, false);
+        if (cmd == PROTOCOL_BINARY_CMD_FLUSH) {
+            safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+            validate_response_header(&receive.response, cmd,
+                                     PROTOCOL_BINARY_RESPONSE_SUCCESS);
+        }
+
+        len = raw_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_GET,
+                          key, strlen(key), NULL, 0);
+        safe_send(send.bytes, len, false);
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET,
+                                 PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
+    }
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_flush(void) {
+    return test_binary_flush_impl("test_binary_flush",
+                                  PROTOCOL_BINARY_CMD_FLUSH);
+}
+
+static enum test_return test_binary_flushq(void) {
+    return test_binary_flush_impl("test_binary_flushq",
+                                  PROTOCOL_BINARY_CMD_FLUSHQ);
+}
+
+static enum test_return test_binary_concat_impl(const char *key, uint8_t cmd) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } send, receive;
+    const char *value = "world";
+
+    size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd,
+                              key, strlen(key), value, strlen(value));
+
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, cmd,
+                             PROTOCOL_BINARY_RESPONSE_NOT_STORED);
+
+    len = storage_command(send.bytes, sizeof(send.bytes),
+                          PROTOCOL_BINARY_CMD_ADD,
+                          key, strlen(key), value, strlen(value), 0, 0);
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    len = raw_command(send.bytes, sizeof(send.bytes), cmd,
+                      key, strlen(key), value, strlen(value));
+    safe_send(send.bytes, len, false);
+
+    if (cmd == PROTOCOL_BINARY_CMD_APPEND || cmd == PROTOCOL_BINARY_CMD_PREPEND) {
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    } else {
+        len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_NOOP,
+                          NULL, 0, NULL, 0);
+        safe_send(send.bytes, len, false);
+        safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+        validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_NOOP,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    }
+
+    len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_GETK,
+                      key, strlen(key), NULL, 0);
+
+    safe_send(send.bytes, len, false);
+    safe_recv_packet(receive.bytes, sizeof(receive.bytes));
+    validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GETK,
+                             PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    assert(receive.response.message.header.response.keylen == strlen(key));
+    assert(receive.response.message.header.response.bodylen == (strlen(key) + 2*strlen(value) + 4));
+
+    char *ptr = receive.bytes;
+    ptr += sizeof(receive.response);
+    ptr += 4;
+
+    assert(memcmp(ptr, key, strlen(key)) == 0);
+    ptr += strlen(key);
+    assert(memcmp(ptr, value, strlen(value)) == 0);
+    ptr += strlen(value);
+    assert(memcmp(ptr, value, strlen(value)) == 0);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_append(void) {
+    return test_binary_concat_impl("test_binary_append",
+                                   PROTOCOL_BINARY_CMD_APPEND);
+}
+
+static enum test_return test_binary_prepend(void) {
+    return test_binary_concat_impl("test_binary_prepend",
+                                   PROTOCOL_BINARY_CMD_PREPEND);
+}
+
+static enum test_return test_binary_appendq(void) {
+    return test_binary_concat_impl("test_binary_appendq",
+                                   PROTOCOL_BINARY_CMD_APPENDQ);
+}
+
+static enum test_return test_binary_prependq(void) {
+    return test_binary_concat_impl("test_binary_prependq",
+                                   PROTOCOL_BINARY_CMD_PREPENDQ);
+}
+
+static enum test_return test_binary_stat(void) {
+    union {
+        protocol_binary_request_no_extras request;
+        protocol_binary_response_no_extras response;
+        char bytes[1024];
+    } buffer;
+
+    size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                             PROTOCOL_BINARY_CMD_STAT,
+                             NULL, 0, NULL, 0);
+
+    safe_send(buffer.bytes, len, false);
+    do {
+        safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+        validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_STAT,
+                                 PROTOCOL_BINARY_RESPONSE_SUCCESS);
+    } while (buffer.response.message.header.response.keylen != 0);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_illegal(void) {
+    uint8_t cmd = 0x25;
+    while (cmd != 0x00) {
+        union {
+            protocol_binary_request_no_extras request;
+            protocol_binary_response_no_extras response;
+            char bytes[1024];
+        } buffer;
+        size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
+                                 cmd, NULL, 0, NULL, 0);
+        safe_send(buffer.bytes, len, false);
+        safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
+        validate_response_header(&buffer.response, cmd,
+                                 PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND);
+        ++cmd;
+    }
+
+    return TEST_PASS;
+}
+
+volatile bool hickup_thread_running;
+
+static void *binary_hickup_recv_verification_thread(void *arg) {
+    protocol_binary_response_no_extras *response = malloc(65*1024);
+    if (response != NULL) {
+        while (safe_recv_packet(response, 65*1024)) {
+            /* Just validate the packet format */
+            validate_response_header(response,
+                                     response->message.header.response.opcode,
+                                     response->message.header.response.status);
+        }
+        free(response);
+    }
+    hickup_thread_running = false;
+    allow_closed_read = false;
+    return NULL;
+}
+
+static enum test_return test_binary_pipeline_hickup_chunk(void *buffer, size_t buffersize) {
+    off_t offset = 0;
+    char *key[256];
+    uint64_t value = 0xfeedfacedeadbeef;
+
+    while (hickup_thread_running &&
+           offset + sizeof(protocol_binary_request_no_extras) < buffersize) {
+        union {
+            protocol_binary_request_no_extras request;
+            char bytes[65 * 1024];
+        } command;
+        uint8_t cmd = (uint8_t)(rand() & 0xff);
+        size_t len;
+        size_t keylen = (rand() % 250) + 1;
+
+        switch (cmd) {
+        case PROTOCOL_BINARY_CMD_ADD:
+        case PROTOCOL_BINARY_CMD_ADDQ:
+        case PROTOCOL_BINARY_CMD_REPLACE:
+        case PROTOCOL_BINARY_CMD_REPLACEQ:
+        case PROTOCOL_BINARY_CMD_SET:
+        case PROTOCOL_BINARY_CMD_SETQ:
+            len = storage_command(command.bytes, sizeof(command.bytes), cmd,
+                                  key, keylen , &value, sizeof(value),
+                                  0, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_APPEND:
+        case PROTOCOL_BINARY_CMD_APPENDQ:
+        case PROTOCOL_BINARY_CMD_PREPEND:
+        case PROTOCOL_BINARY_CMD_PREPENDQ:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                              key, keylen, &value, sizeof(value));
+            break;
+        case PROTOCOL_BINARY_CMD_FLUSH:
+        case PROTOCOL_BINARY_CMD_FLUSHQ:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                              NULL, 0, NULL, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_NOOP:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                              NULL, 0, NULL, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_DELETE:
+        case PROTOCOL_BINARY_CMD_DELETEQ:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                             key, keylen, NULL, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_DECREMENT:
+        case PROTOCOL_BINARY_CMD_DECREMENTQ:
+        case PROTOCOL_BINARY_CMD_INCREMENT:
+        case PROTOCOL_BINARY_CMD_INCREMENTQ:
+            len = arithmetic_command(command.bytes, sizeof(command.bytes), cmd,
+                                     key, keylen, 1, 0, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_VERSION:
+            len = raw_command(command.bytes, sizeof(command.bytes),
+                             PROTOCOL_BINARY_CMD_VERSION,
+                             NULL, 0, NULL, 0);
+            break;
+        case PROTOCOL_BINARY_CMD_GET:
+        case PROTOCOL_BINARY_CMD_GETK:
+        case PROTOCOL_BINARY_CMD_GETKQ:
+        case PROTOCOL_BINARY_CMD_GETQ:
+            len = raw_command(command.bytes, sizeof(command.bytes), cmd,
+                             key, keylen, NULL, 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_TOUCH:
+        case PROTOCOL_BINARY_CMD_GAT:
+        case PROTOCOL_BINARY_CMD_GATQ:
+        case PROTOCOL_BINARY_CMD_GATK:
+        case PROTOCOL_BINARY_CMD_GATKQ:
+            len = touch_command(command.bytes, sizeof(command.bytes), cmd,
+                                key, keylen, 10);
+            break;
+
+        case PROTOCOL_BINARY_CMD_STAT:
+            len = raw_command(command.bytes, sizeof(command.bytes),
+                              PROTOCOL_BINARY_CMD_STAT,
+                              NULL, 0, NULL, 0);
+            break;
+
+        case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS:
+        case PROTOCOL_BINARY_CMD_SASL_AUTH:
+        case PROTOCOL_BINARY_CMD_SASL_STEP:
+            /* Ignoring SASL */
+        case PROTOCOL_BINARY_CMD_QUITQ:
+        case PROTOCOL_BINARY_CMD_QUIT:
+            /* I don't want to pass on the quit commands ;-) */
+            cmd |= 0xf0;
+            /* FALLTHROUGH */
+        default:
+            len = raw_command(command.bytes, sizeof(command.bytes),
+                              cmd, NULL, 0, NULL, 0);
+        }
+
+        if ((len + offset) < buffersize) {
+            memcpy(((char*)buffer) + offset, command.bytes, len);
+            offset += len;
+        } else {
+            break;
+        }
+    }
+    safe_send(buffer, offset, true);
+
+    return TEST_PASS;
+}
+
+static enum test_return test_binary_pipeline_hickup(void)
+{
+    size_t buffersize = 65 * 1024;
+    void *buffer = malloc(buffersize);
+    int ii;
+
+    pthread_t tid;
+    int ret;
+    allow_closed_read = true;
+    hickup_thread_running = true;
+    if ((ret = pthread_create(&tid, NULL,
+                              binary_hickup_recv_verification_thread, NULL)) != 0) {
+        fprintf(stderr, "Can't create thread: %s\n", strerror(ret));
+        free(buffer);
+        return TEST_FAIL;
+    }
+
+    /* Allow the thread to start */
+    usleep(250);
+
+    srand((int)time(NULL));
+    for (ii = 0; ii < 2; ++ii) {
+        test_binary_pipeline_hickup_chunk(buffer, buffersize);
+    }
+
+    /* send quitq to shut down the read thread ;-) */
+    size_t len = raw_command(buffer, buffersize, PROTOCOL_BINARY_CMD_QUITQ,
+                             NULL, 0, NULL, 0);
+    safe_send(buffer, len, false);
+
+    pthread_join(tid, NULL);
+    free(buffer);
+    return TEST_PASS;
+}
+
+
+static enum test_return test_issue_101(void) {
+    enum { max = 2 };
+    enum test_return ret = TEST_PASS;
+    struct conn *conns[max];
+    int ii = 0;
+    pid_t child = 0;
+
+    if (getenv("SKIP_TEST_101") != NULL) {
+        return TEST_SKIP;
+    }
+
+    const char *command = "stats\r\nstats\r\nstats\r\nstats\r\nstats\r\n";
+    size_t cmdlen = strlen(command);
+
+    server_pid = start_server(&port, false, 1000);
+
+    for (ii = 0; ii < max; ++ii) {
+        conns[ii] = NULL;
+        conns[ii] = connect_server("127.0.0.1", port, true, enable_ssl);
+        assert(conns[ii]);
+        assert(conns[ii]->sock > 0);
+    }
+
+    /* Send command on the connection until it blocks */
+    for (ii = 0; ii < max; ++ii) {
+        bool more = true;
+        do {
+            ssize_t err = conns[ii]->write(conns[ii], command, cmdlen);
+            if (err == -1) {
+                switch (errno) {
+                case EINTR:
+                    break;
+                case ENOMEM:
+                case EWOULDBLOCK:
+                    more = false;
+                    break;
+                default:
+                    ret = TEST_FAIL;
+                    goto cleanup;
+                }
+            }
+        } while (more);
+    }
+
+    child = fork();
+    if (child == (pid_t)-1) {
+        abort();
+    } else if (child > 0) {
+        int stat;
+        pid_t c;
+        while ((c = waitpid(child, &stat, 0)) == (pid_t)-1 && errno == EINTR);
+        assert(c == child);
+        assert(stat == 0);
+    } else {
+        con = connect_server("127.0.0.1", port, false, enable_ssl);
+        assert(con);
+        ret = test_binary_noop();
+        close_conn();
+        exit(0);
+    }
+
+ cleanup:
+    /* close all connections */
+    for (ii = 0; ii < max; ++ii) {
+        struct conn *c = conns[ii];
+        if (c == NULL) continue;
+#ifdef TLS
+        if (c->ssl) {
+            SSL_shutdown(c->ssl);
+            SSL_free(c->ssl);
+        }
+        if (c->ssl_ctx)
+            SSL_CTX_free(c->ssl_ctx);
+#endif
+        if (c->sock > 0) close(c->sock);
+        free(conns[ii]);
+        conns[ii] = NULL;
+    }
+
+    assert(kill(server_pid, SIGTERM) == 0);
+
+    return ret;
+}
+
+typedef enum test_return (*TEST_FUNC)(void);
+struct testcase {
+    const char *description;
+    TEST_FUNC function;
+};
+
+struct testcase testcases[] = {
+    { "cache_create", cache_create_test },
+    { "cache_constructor", cache_constructor_test },
+    { "cache_constructor_fail", cache_fail_constructor_test },
+    { "cache_destructor", cache_destructor_test },
+    { "cache_reuse", cache_reuse_test },
+    { "cache_redzone", cache_redzone_test },
+    { "stats_prefix_find", test_stats_prefix_find },
+    { "stats_prefix_record_get", test_stats_prefix_record_get },
+    { "stats_prefix_record_delete", test_stats_prefix_record_delete },
+    { "stats_prefix_record_set", test_stats_prefix_record_set },
+    { "stats_prefix_dump", test_stats_prefix_dump },
+    { "issue_161", test_issue_161 },
+    { "strtol", test_safe_strtol },
+    { "strtoll", test_safe_strtoll },
+    { "strtoul", test_safe_strtoul },
+    { "strtoull", test_safe_strtoull },
+    { "issue_44", test_issue_44 },
+    { "vperror", test_vperror },
+    { "issue_101", test_issue_101 },
+    /* The following tests all run towards the same server */
+    { "start_server", start_memcached_server },
+    { "issue_92", test_issue_92 },
+    { "issue_102", test_issue_102 },
+    { "binary_noop", test_binary_noop },
+    { "binary_quit", test_binary_quit },
+    { "binary_quitq", test_binary_quitq },
+    { "binary_set", test_binary_set },
+    { "binary_setq", test_binary_setq },
+    { "binary_add", test_binary_add },
+    { "binary_addq", test_binary_addq },
+    { "binary_replace", test_binary_replace },
+    { "binary_replaceq", test_binary_replaceq },
+    { "binary_delete", test_binary_delete },
+    { "binary_deleteq", test_binary_deleteq },
+    { "binary_get", test_binary_get },
+    { "binary_getq", test_binary_getq },
+    { "binary_getk", test_binary_getk },
+    { "binary_getkq", test_binary_getkq },
+    { "binary_gat", test_binary_gat },
+    { "binary_gatq", test_binary_gatq },
+    { "binary_gatk", test_binary_gatk },
+    { "binary_gatkq", test_binary_gatkq },
+    { "binary_incr", test_binary_incr },
+    { "binary_incrq", test_binary_incrq },
+    { "binary_decr", test_binary_decr },
+    { "binary_decrq", test_binary_decrq },
+    { "binary_version", test_binary_version },
+    { "binary_flush", test_binary_flush },
+    { "binary_flushq", test_binary_flushq },
+    { "binary_append", test_binary_append },
+    { "binary_appendq", test_binary_appendq },
+    { "binary_prepend", test_binary_prepend },
+    { "binary_prependq", test_binary_prependq },
+    { "binary_stat", test_binary_stat },
+    { "binary_illegal", test_binary_illegal },
+    { "binary_pipeline_hickup", test_binary_pipeline_hickup },
+    { "shutdown", shutdown_memcached_server },
+    { "stop_server", stop_memcached_server },
+    { NULL, NULL }
+};
+
+/* Stub out function defined in memcached.c */
+void STATS_LOCK(void);
+void STATS_UNLOCK(void);
+//void STATS_LOCK(void)
+//{}
+//void STATS_UNLOCK(void)
+//{}
+
+int init_tester(int argc, char **argv)
+{
+    int exitcode = 0;
+    int ii = 0, num_cases = 0;
+#ifdef TLS
+    if (getenv("SSL_TEST") != NULL) {
+        SSLeay_add_ssl_algorithms();
+        SSL_load_error_strings();
+        enable_ssl = true;
+    }
+#endif
+    /* Stats prefix test is sensitive to the choice of hash function */
+    hash_init(JENKINS_HASH);
+    stats_prefix_init(':');
+
+    for (num_cases = 0; testcases[num_cases].description; num_cases++) {
+        /* Just counting */
+    }
+
+    printf("1..%d\n", num_cases);
+
+    //for (ii = 0; testcases[ii].description != NULL; ++ii) {
+    //    fflush(stdout);
+//#ifndef DEBUG
+    //    /* the test program shouldn't run longer than 10 minutes... */
+    //    alarm(600);
+//#endif
+    //    enum test_return ret = testcases[ii].function();
+    //    if (ret == TEST_SKIP) {
+    //        fprintf(stdout, "ok # SKIP %d - %s\n", ii + 1, testcases[ii].description);
+    //    } else if (ret == TEST_PASS) {
+    //        fprintf(stdout, "ok %d - %s\n", ii + 1, testcases[ii].description);
+    //    } else {
+    //        fprintf(stdout, "not ok %d - %s\n", ii + 1, testcases[ii].description);
+    //        exitcode = 1;
+    //    }
+    //    fflush(stdout);
+    //}
+
+    return exitcode;
+}
diff --git a/cachelib/external/memcached/thread.c b/cachelib/external/memcached/thread.c
new file mode 100644
index 0000000000..09c9be01e1
--- /dev/null
+++ b/cachelib/external/memcached/thread.c
@@ -0,0 +1,960 @@
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Thread management for memcached.
+ */
+#include "memcached.h"
+#ifdef EXTSTORE
+#include "storage.h"
+#endif
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+#ifdef __sun
+#include <atomic.h>
+#endif
+
+#ifdef TLS
+#include <openssl/ssl.h>
+#endif
+
+#define ITEMS_PER_ALLOC 64
+
+
+/* An item in the connection queue. */
+enum conn_queue_item_modes {
+    queue_new_conn,   /* brand new connection. */
+};
+typedef struct conn_queue_item CQ_ITEM;
+struct conn_queue_item {
+    int               sfd;
+    enum conn_states  init_state;
+    int               event_flags;
+    int               read_buffer_size;
+    enum network_transport     transport;
+    enum conn_queue_item_modes mode;
+    conn *c;
+    void    *ssl;
+    CQ_ITEM          *next;
+};
+
+/* A connection queue. */
+typedef struct conn_queue CQ;
+struct conn_queue {
+    CQ_ITEM *head;
+    CQ_ITEM *tail;
+    pthread_mutex_t lock;
+};
+
+/* Locks for cache LRU operations */
+pthread_mutex_t lru_locks[POWER_LARGEST];
+
+/* Connection lock around accepting new connections */
+pthread_mutex_t conn_lock = PTHREAD_MUTEX_INITIALIZER;
+
+#if !defined(HAVE_GCC_ATOMICS) && !defined(__sun)
+pthread_mutex_t atomics_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+/* Lock for global stats */
+static pthread_mutex_t stats_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Lock to cause worker threads to hang up after being woken */
+static pthread_mutex_t worker_hang_lock;
+
+/* Free list of CQ_ITEM structs */
+static CQ_ITEM *cqi_freelist;
+static pthread_mutex_t cqi_freelist_lock;
+
+static pthread_mutex_t *item_locks;
+/* size of the item lock hash table */
+static uint32_t item_lock_count;
+unsigned int item_lock_hashpower;
+#define hashsize(n) ((unsigned long int)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+
+/*
+ * Each libevent instance has a wakeup pipe, which other threads
+ * can use to signal that they've put a new connection on its queue.
+ */
+LIBEVENT_THREAD *threads = NULL;
+
+/*
+ * Number of worker threads that have finished setting themselves up.
+ */
+static int init_count = 0;
+static pthread_mutex_t init_lock;
+static pthread_cond_t init_cond;
+
+
+static void thread_libevent_process(int fd, short which, void *arg);
+
+/* item_lock() must be held for an item before any modifications to either its
+ * associated hash bucket, or the structure itself.
+ * LRU modifications must hold the item lock, and the LRU lock.
+ * LRU's accessing items must item_trylock() before modifying an item.
+ * Items accessible from an LRU must not be freed or modified
+ * without first locking and removing from the LRU.
+ */
+
+void item_lock(uint32_t hv) {
+    mutex_lock(&item_locks[hv & hashmask(item_lock_hashpower)]);
+}
+
+void *item_trylock(uint32_t hv) {
+    pthread_mutex_t *lock = &item_locks[hv & hashmask(item_lock_hashpower)];
+    if (pthread_mutex_trylock(lock) == 0) {
+        return lock;
+    }
+    return NULL;
+}
+
+void item_trylock_unlock(void *lock) {
+    mutex_unlock((pthread_mutex_t *) lock);
+}
+
+void item_unlock(uint32_t hv) {
+    mutex_unlock(&item_locks[hv & hashmask(item_lock_hashpower)]);
+}
+
+static void wait_for_thread_registration(int nthreads) {
+    while (init_count < nthreads) {
+        pthread_cond_wait(&init_cond, &init_lock);
+    }
+}
+
+static void register_thread_initialized(void) {
+    pthread_mutex_lock(&init_lock);
+    init_count++;
+    pthread_cond_signal(&init_cond);
+    pthread_mutex_unlock(&init_lock);
+    /* Force worker threads to pile up if someone wants us to */
+    pthread_mutex_lock(&worker_hang_lock);
+    pthread_mutex_unlock(&worker_hang_lock);
+}
+
+/* Must not be called with any deeper locks held */
+void pause_threads(enum pause_thread_types type) {
+    char buf[1];
+    int i;
+
+    buf[0] = 0;
+    switch (type) {
+        case PAUSE_ALL_THREADS:
+            slabs_rebalancer_pause();
+            lru_maintainer_pause();
+            lru_crawler_pause();
+#ifdef EXTSTORE
+            storage_compact_pause();
+            storage_write_pause();
+#endif
+        case PAUSE_WORKER_THREADS:
+            buf[0] = 'p';
+            pthread_mutex_lock(&worker_hang_lock);
+            break;
+        case RESUME_ALL_THREADS:
+            slabs_rebalancer_resume();
+            lru_maintainer_resume();
+            lru_crawler_resume();
+#ifdef EXTSTORE
+            storage_compact_resume();
+            storage_write_resume();
+#endif
+        case RESUME_WORKER_THREADS:
+            pthread_mutex_unlock(&worker_hang_lock);
+            break;
+        default:
+            fprintf(stderr, "Unknown lock type: %d\n", type);
+            assert(1 == 0);
+            break;
+    }
+
+    /* Only send a message if we have one. */
+    if (buf[0] == 0) {
+        return;
+    }
+
+    pthread_mutex_lock(&init_lock);
+    init_count = 0;
+    for (i = 0; i < settings.num_threads; i++) {
+        if (write(threads[i].notify_send_fd, buf, 1) != 1) {
+            perror("Failed writing to notify pipe");
+            /* TODO: This is a fatal problem. Can it ever happen temporarily? */
+        }
+    }
+    wait_for_thread_registration(settings.num_threads);
+    pthread_mutex_unlock(&init_lock);
+}
+
+// MUST not be called with any deeper locks held
+// MUST be called only by parent thread
+// Note: listener thread is the "main" event base, which has exited its
+// loop in order to call this function.
+void stop_threads(void) {
+    char buf[1];
+    int i;
+
+    // assoc can call pause_threads(), so we have to stop it first.
+    stop_assoc_maintenance_thread();
+    if (settings.verbose > 0)
+        fprintf(stderr, "stopped assoc\n");
+
+    if (settings.verbose > 0)
+        fprintf(stderr, "asking workers to stop\n");
+    buf[0] = 's';
+    pthread_mutex_lock(&worker_hang_lock);
+    pthread_mutex_lock(&init_lock);
+    init_count = 0;
+    for (i = 0; i < settings.num_threads; i++) {
+        if (write(threads[i].notify_send_fd, buf, 1) != 1) {
+            perror("Failed writing to notify pipe");
+            /* TODO: This is a fatal problem. Can it ever happen temporarily? */
+        }
+    }
+    wait_for_thread_registration(settings.num_threads);
+    pthread_mutex_unlock(&init_lock);
+
+    // All of the workers are hung but haven't done cleanup yet.
+
+    if (settings.verbose > 0)
+        fprintf(stderr, "asking background threads to stop\n");
+
+    // stop each side thread.
+    // TODO: Verify these all work if the threads are already stopped
+    stop_item_crawler_thread(CRAWLER_WAIT);
+    if (settings.verbose > 0)
+        fprintf(stderr, "stopped lru crawler\n");
+    stop_lru_maintainer_thread();
+    if (settings.verbose > 0)
+        fprintf(stderr, "stopped maintainer\n");
+    stop_slab_maintenance_thread();
+    if (settings.verbose > 0)
+        fprintf(stderr, "stopped slab mover\n");
+    logger_stop();
+    if (settings.verbose > 0)
+        fprintf(stderr, "stopped logger thread\n");
+    stop_conn_timeout_thread();
+    if (settings.verbose > 0)
+        fprintf(stderr, "stopped idle timeout thread\n");
+
+    // Close all connections then let the workers finally exit.
+    if (settings.verbose > 0)
+        fprintf(stderr, "closing connections\n");
+    //conn_close_all();
+    pthread_mutex_unlock(&worker_hang_lock);
+    if (settings.verbose > 0)
+        fprintf(stderr, "reaping worker threads\n");
+    for (i = 0; i < settings.num_threads; i++) {
+        pthread_join(threads[i].thread_id, NULL);
+    }
+
+    if (settings.verbose > 0)
+        fprintf(stderr, "all background threads stopped\n");
+
+    // At this point, every background thread must be stopped.
+}
+
+/*
+ * Initializes a connection queue.
+ */
+static void cq_init(CQ *cq) {
+    pthread_mutex_init(&cq->lock, NULL);
+    cq->head = NULL;
+    cq->tail = NULL;
+}
+
+/*
+ * Looks for an item on a connection queue, but doesn't block if there isn't
+ * one.
+ * Returns the item, or NULL if no item is available
+ */
+static CQ_ITEM *cq_pop(CQ *cq) {
+    CQ_ITEM *item;
+
+    pthread_mutex_lock(&cq->lock);
+    item = cq->head;
+    if (NULL != item) {
+        cq->head = item->next;
+        if (NULL == cq->head)
+            cq->tail = NULL;
+    }
+    pthread_mutex_unlock(&cq->lock);
+
+    return item;
+}
+
+/*
+ * Adds an item to a connection queue.
+ */
+static void cq_push(CQ *cq, CQ_ITEM *item) {
+    item->next = NULL;
+
+    pthread_mutex_lock(&cq->lock);
+    if (NULL == cq->tail)
+        cq->head = item;
+    else
+        cq->tail->next = item;
+    cq->tail = item;
+    pthread_mutex_unlock(&cq->lock);
+}
+
+/*
+ * Returns a fresh connection queue item.
+ */
+static CQ_ITEM *cqi_new(void) {
+    CQ_ITEM *item = NULL;
+    pthread_mutex_lock(&cqi_freelist_lock);
+    if (cqi_freelist) {
+        item = cqi_freelist;
+        cqi_freelist = item->next;
+    }
+    pthread_mutex_unlock(&cqi_freelist_lock);
+
+    if (NULL == item) {
+        int i;
+
+        /* Allocate a bunch of items at once to reduce fragmentation */
+        item = malloc(sizeof(CQ_ITEM) * ITEMS_PER_ALLOC);
+        if (NULL == item) {
+            STATS_LOCK();
+            stats.malloc_fails++;
+            STATS_UNLOCK();
+            return NULL;
+        }
+
+        /*
+         * Link together all the new items except the first one
+         * (which we'll return to the caller) for placement on
+         * the freelist.
+         */
+        for (i = 2; i < ITEMS_PER_ALLOC; i++)
+            item[i - 1].next = &item[i];
+
+        pthread_mutex_lock(&cqi_freelist_lock);
+        item[ITEMS_PER_ALLOC - 1].next = cqi_freelist;
+        cqi_freelist = &item[1];
+        pthread_mutex_unlock(&cqi_freelist_lock);
+    }
+
+    return item;
+}
+
+
+/*
+ * Frees a connection queue item (adds it to the freelist.)
+ */
+static void cqi_free(CQ_ITEM *item) {
+    pthread_mutex_lock(&cqi_freelist_lock);
+    item->next = cqi_freelist;
+    cqi_freelist = item;
+    pthread_mutex_unlock(&cqi_freelist_lock);
+}
+
+
+/*
+ * Creates a worker thread.
+ */
+static void create_worker(void *(*func)(void *), void *arg) {
+    pthread_attr_t  attr;
+    int             ret;
+
+    pthread_attr_init(&attr);
+
+    if ((ret = pthread_create(&((LIBEVENT_THREAD*)arg)->thread_id, &attr, func, arg)) != 0) {
+        fprintf(stderr, "Can't create thread: %s\n",
+                strerror(ret));
+        exit(1);
+    }
+}
+
+/*
+ * Sets whether or not we accept new connections.
+ */
+void accept_new_conns(const bool do_accept) {
+    pthread_mutex_lock(&conn_lock);
+    do_accept_new_conns(do_accept);
+    pthread_mutex_unlock(&conn_lock);
+}
+/****************************** LIBEVENT THREADS *****************************/
+
+/*
+ * Set up a thread's information.
+ */
+static void setup_thread(LIBEVENT_THREAD *me) {
+#if defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER >= 0x02000101
+    struct event_config *ev_config;
+    ev_config = event_config_new();
+    event_config_set_flag(ev_config, EVENT_BASE_FLAG_NOLOCK);
+    me->base = event_base_new_with_config(ev_config);
+    event_config_free(ev_config);
+#else
+    me->base = event_init();
+#endif
+
+    if (! me->base) {
+        fprintf(stderr, "Can't allocate event base\n");
+        exit(1);
+    }
+
+    /* Listen for notifications from other threads */
+    event_set(&me->notify_event, me->notify_receive_fd,
+              EV_READ | EV_PERSIST, thread_libevent_process, me);
+    event_base_set(me->base, &me->notify_event);
+
+    if (event_add(&me->notify_event, 0) == -1) {
+        fprintf(stderr, "Can't monitor libevent notify pipe\n");
+        exit(1);
+    }
+
+    me->new_conn_queue = malloc(sizeof(struct conn_queue));
+    if (me->new_conn_queue == NULL) {
+        perror("Failed to allocate memory for connection queue");
+        exit(EXIT_FAILURE);
+    }
+    cq_init(me->new_conn_queue);
+
+    if (pthread_mutex_init(&me->stats.mutex, NULL) != 0) {
+        perror("Failed to initialize mutex");
+        exit(EXIT_FAILURE);
+    }
+
+    me->resp_cache = cache_create("resp", sizeof(mc_resp), sizeof(char *), NULL, NULL);
+    if (me->resp_cache == NULL) {
+        fprintf(stderr, "Failed to create response cache\n");
+        exit(EXIT_FAILURE);
+    }
+    // Note: we were cleanly passing in num_threads before, but this now
+    // relies on settings globals too much.
+    if (settings.resp_obj_mem_limit) {
+        int limit = settings.resp_obj_mem_limit / settings.num_threads;
+        if (limit < sizeof(mc_resp)) {
+            limit = 1;
+        } else {
+            limit = limit / sizeof(mc_resp);
+        }
+        cache_set_limit(me->resp_cache, limit);
+    }
+
+    me->rbuf_cache = cache_create("rbuf", READ_BUFFER_SIZE, sizeof(char *), NULL, NULL);
+    if (me->rbuf_cache == NULL) {
+        fprintf(stderr, "Failed to create read buffer cache\n");
+        exit(EXIT_FAILURE);
+    }
+    if (settings.read_buf_mem_limit) {
+        int limit = settings.read_buf_mem_limit / settings.num_threads;
+        if (limit < READ_BUFFER_SIZE) {
+            limit = 1;
+        } else {
+            limit = limit / READ_BUFFER_SIZE;
+        }
+        cache_set_limit(me->rbuf_cache, limit);
+    }
+
+#ifdef EXTSTORE
+    me->io_cache = cache_create("io", sizeof(io_wrap), sizeof(char*), NULL, NULL);
+    if (me->io_cache == NULL) {
+        fprintf(stderr, "Failed to create IO object cache\n");
+        exit(EXIT_FAILURE);
+    }
+#endif
+#ifdef TLS
+    if (settings.ssl_enabled) {
+        me->ssl_wbuf = (char *)malloc((size_t)settings.ssl_wbuf_size);
+        if (me->ssl_wbuf == NULL) {
+            fprintf(stderr, "Failed to allocate the SSL write buffer\n");
+            exit(EXIT_FAILURE);
+        }
+    }
+#endif
+}
+
+/*
+ * Worker thread: main event loop
+ */
+static void *worker_libevent(void *arg) {
+    LIBEVENT_THREAD *me = arg;
+
+    /* Any per-thread setup can happen here; memcached_thread_init() will block until
+     * all threads have finished initializing.
+     */
+    me->l = logger_create();
+    me->lru_bump_buf = item_lru_bump_buf_create();
+    if (me->l == NULL || me->lru_bump_buf == NULL) {
+        abort();
+    }
+
+    if (settings.drop_privileges) {
+        drop_worker_privileges();
+    }
+
+    register_thread_initialized();
+
+    event_base_loop(me->base, 0);
+
+    // same mechanism used to watch for all threads exiting.
+    register_thread_initialized();
+
+    event_base_free(me->base);
+    return NULL;
+}
+
+void bind_thread_helper(conn * c, uint32_t wid){
+    c->thread = threads + wid;
+}
+
+
+/*
+ * Processes an incoming "handle a new connection" item. This is called when
+ * input arrives on the libevent wakeup pipe.
+ */
+static void thread_libevent_process(int fd, short which, void *arg) {
+    LIBEVENT_THREAD *me = arg;
+    CQ_ITEM *item;
+    char buf[1];
+    conn *c;
+    unsigned int fd_from_pipe;
+
+    if (read(fd, buf, 1) != 1) {
+        if (settings.verbose > 0)
+            fprintf(stderr, "Can't read from libevent pipe\n");
+        return;
+    }
+
+    switch (buf[0]) {
+    case 'c':
+        item = cq_pop(me->new_conn_queue);
+
+        if (NULL == item) {
+            break;
+        }
+        switch (item->mode) {
+            case queue_new_conn:
+                c = conn_new(item->sfd, item->init_state, item->event_flags,
+                                   item->read_buffer_size, item->transport,
+                                   me->base, item->ssl);
+                if (c == NULL) {
+                    if (IS_UDP(item->transport)) {
+                        fprintf(stderr, "Can't listen for events on UDP socket\n");
+                        exit(1);
+                    } else {
+                        if (settings.verbose > 0) {
+                            fprintf(stderr, "Can't listen for events on fd %d\n",
+                                item->sfd);
+                        }
+#ifdef TLS
+                        if (item->ssl) {
+                            SSL_shutdown(item->ssl);
+                            SSL_free(item->ssl);
+                        }
+#endif
+                        close(item->sfd);
+                    }
+                } else {
+                    c->thread = me;
+#ifdef TLS
+                    if (settings.ssl_enabled && c->ssl != NULL) {
+                        assert(c->thread && c->thread->ssl_wbuf);
+                        c->ssl_wbuf = c->thread->ssl_wbuf;
+                    }
+#endif
+                }
+                break;
+        }
+        cqi_free(item);
+        break;
+    /* we were told to pause and report in */
+    case 'p':
+        register_thread_initialized();
+        break;
+    /* a client socket timed out */
+    case 't':
+        if (read(fd, &fd_from_pipe, sizeof(fd_from_pipe)) != sizeof(fd_from_pipe)) {
+            if (settings.verbose > 0)
+                fprintf(stderr, "Can't read timeout fd from libevent pipe\n");
+            return;
+        }
+        conn_close_idle(conns[fd_from_pipe]);
+        break;
+    /* a side thread redispatched a client connection */
+    case 'r':
+        if (read(fd, &fd_from_pipe, sizeof(fd_from_pipe)) != sizeof(fd_from_pipe)) {
+            if (settings.verbose > 0)
+                fprintf(stderr, "Can't read redispatch fd from libevent pipe\n");
+            return;
+        }
+        conn_worker_readd(conns[fd_from_pipe]);
+        break;
+    /* asked to stop */
+    case 's':
+        event_base_loopexit(me->base, NULL);
+        break;
+    }
+}
+
+/* Which thread we assigned a connection to most recently. */
+static int last_thread = -1;
+
+/*
+ * Dispatches a new connection to another thread. This is only ever called
+ * from the main thread, either during initialization (for UDP) or because
+ * of an incoming connection.
+ */
+void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,
+                       int read_buffer_size, enum network_transport transport, void *ssl) {
+    CQ_ITEM *item = cqi_new();
+    char buf[1];
+    if (item == NULL) {
+        close(sfd);
+        /* given that malloc failed this may also fail, but let's try */
+        fprintf(stderr, "Failed to allocate memory for connection object\n");
+        return;
+    }
+
+    int tid = (last_thread + 1) % settings.num_threads;
+
+    LIBEVENT_THREAD *thread = threads + tid;
+
+    last_thread = tid;
+
+    item->sfd = sfd;
+    item->init_state = init_state;
+    item->event_flags = event_flags;
+    item->read_buffer_size = read_buffer_size;
+    item->transport = transport;
+    item->mode = queue_new_conn;
+    item->ssl = ssl;
+
+    cq_push(thread->new_conn_queue, item);
+
+    MEMCACHED_CONN_DISPATCH(sfd, (int64_t)thread->thread_id);
+    buf[0] = 'c';
+    if (write(thread->notify_send_fd, buf, 1) != 1) {
+        perror("Writing to thread notify pipe");
+    }
+}
+
+/*
+ * Re-dispatches a connection back to the original thread. Can be called from
+ * any side thread borrowing a connection.
+ */
+#define REDISPATCH_MSG_SIZE (1 + sizeof(int))
+void redispatch_conn(conn *c) {
+    char buf[REDISPATCH_MSG_SIZE];
+    LIBEVENT_THREAD *thread = c->thread;
+
+    buf[0] = 'r';
+    memcpy(&buf[1], &c->sfd, sizeof(int));
+    if (write(thread->notify_send_fd, buf, REDISPATCH_MSG_SIZE) != REDISPATCH_MSG_SIZE) {
+        perror("Writing redispatch to thread notify pipe");
+    }
+}
+
+/* This misses the allow_new_conns flag :( */
+void sidethread_conn_close(conn *c) {
+    if (settings.verbose > 1)
+        fprintf(stderr, "<%d connection closing from side thread.\n", c->sfd);
+
+    c->state = conn_closing;
+    // redispatch will see closing flag and properly close connection.
+    redispatch_conn(c);
+    return;
+}
+
+/********************************* ITEM ACCESS *******************************/
+
+/*
+ * Allocates a new item.
+ */
+item *item_alloc(char *key, size_t nkey, int flags, rel_time_t exptime, int nbytes) {
+    item *it;
+    /* do_item_alloc handles its own locks */
+    it = do_item_alloc(key, nkey, flags, exptime, nbytes);
+    return it;
+}
+
+/*
+ * Returns an item if it hasn't been marked as expired,
+ * lazy-expiring as needed.
+ */
+item *item_get(const char *key, const size_t nkey, conn *c, const bool do_update) {
+    item *it;
+    uint32_t hv;
+    hv = hash(key, nkey);
+    item_lock(hv);
+    it = do_item_get(key, nkey, hv, c, do_update);
+    item_unlock(hv);
+    return it;
+}
+
+// returns an item with the item lock held.
+// lock will still be held even if return is NULL, allowing caller to replace
+// an item atomically if desired.
+item *item_get_locked(const char *key, const size_t nkey, conn *c, const bool do_update, uint32_t *hv) {
+    item *it;
+    *hv = hash(key, nkey);
+    item_lock(*hv);
+    it = do_item_get(key, nkey, *hv, c, do_update);
+    return it;
+}
+
+item *item_touch(const char *key, size_t nkey, uint32_t exptime, conn *c) {
+    item *it;
+    uint32_t hv;
+    hv = hash(key, nkey);
+    item_lock(hv);
+    it = do_item_touch(key, nkey, exptime, hv, c);
+    item_unlock(hv);
+    return it;
+}
+
+/*
+ * Links an item into the LRU and hashtable.
+ */
+int item_link(item *item) {
+    int ret;
+    uint32_t hv;
+
+    hv = hash(ITEM_key(item), item->nkey);
+    item_lock(hv);
+    ret = do_item_link(item, hv);
+    item_unlock(hv);
+    return ret;
+}
+
+/*
+ * Decrements the reference count on an item and adds it to the freelist if
+ * needed.
+ */
+void item_remove(item *item) {
+    uint32_t hv;
+    hv = hash(ITEM_key(item), item->nkey);
+
+    item_lock(hv);
+    do_item_remove(item);
+    item_unlock(hv);
+}
+
+/*
+ * Replaces one item with another in the hashtable.
+ * Unprotected by a mutex lock since the core server does not require
+ * it to be thread-safe.
+ */
+int item_replace(item *old_it, item *new_it, const uint32_t hv) {
+    return do_item_replace(old_it, new_it, hv);
+}
+
+/*
+ * Unlinks an item from the LRU and hashtable.
+ */
+void item_unlink(item *item) {
+    uint32_t hv;
+    hv = hash(ITEM_key(item), item->nkey);
+    item_lock(hv);
+    do_item_unlink(item, hv);
+    item_unlock(hv);
+}
+
+/*
+ * Does arithmetic on a numeric item value.
+ */
+enum delta_result_type add_delta(conn *c, const char *key,
+                                 const size_t nkey, bool incr,
+                                 const int64_t delta, char *buf,
+                                 uint64_t *cas) {
+    enum delta_result_type ret;
+    uint32_t hv;
+
+    hv = hash(key, nkey);
+    item_lock(hv);
+    ret = do_add_delta(c, key, nkey, incr, delta, buf, cas, hv, NULL);
+    item_unlock(hv);
+    return ret;
+}
+
+/*
+ * Stores an item in the cache (high level, obeys set/add/replace semantics)
+ */
+enum store_item_type store_item(item *item, int comm, conn* c) {
+    enum store_item_type ret;
+    uint32_t hv;
+
+    hv = hash(ITEM_key(item), item->nkey);
+    item_lock(hv);
+    ret = do_store_item(item, comm, c, hv);
+    item_unlock(hv);
+    return ret;
+}
+
+/******************************* GLOBAL STATS ******************************/
+
+void STATS_LOCK() {
+    pthread_mutex_lock(&stats_lock);
+}
+
+void STATS_UNLOCK() {
+    pthread_mutex_unlock(&stats_lock);
+}
+
+void threadlocal_stats_reset(void) {
+    int ii;
+    for (ii = 0; ii < settings.num_threads; ++ii) {
+        pthread_mutex_lock(&threads[ii].stats.mutex);
+#define X(name) threads[ii].stats.name = 0;
+        THREAD_STATS_FIELDS
+#ifdef EXTSTORE
+        EXTSTORE_THREAD_STATS_FIELDS
+#endif
+#undef X
+
+        memset(&threads[ii].stats.slab_stats, 0,
+                sizeof(threads[ii].stats.slab_stats));
+        memset(&threads[ii].stats.lru_hits, 0,
+                sizeof(uint64_t) * POWER_LARGEST);
+
+        pthread_mutex_unlock(&threads[ii].stats.mutex);
+    }
+}
+
+void threadlocal_stats_aggregate(struct thread_stats *stats) {
+    int ii, sid;
+
+    /* The struct has a mutex, but we can safely set the whole thing
+     * to zero since it is unused when aggregating. */
+    memset(stats, 0, sizeof(*stats));
+
+    for (ii = 0; ii < settings.num_threads; ++ii) {
+        pthread_mutex_lock(&threads[ii].stats.mutex);
+#define X(name) stats->name += threads[ii].stats.name;
+        THREAD_STATS_FIELDS
+#ifdef EXTSTORE
+        EXTSTORE_THREAD_STATS_FIELDS
+#endif
+#undef X
+
+        for (sid = 0; sid < MAX_NUMBER_OF_SLAB_CLASSES; sid++) {
+#define X(name) stats->slab_stats[sid].name += \
+            threads[ii].stats.slab_stats[sid].name;
+            SLAB_STATS_FIELDS
+#undef X
+        }
+
+        for (sid = 0; sid < POWER_LARGEST; sid++) {
+            stats->lru_hits[sid] +=
+                threads[ii].stats.lru_hits[sid];
+            stats->slab_stats[CLEAR_LRU(sid)].get_hits +=
+                threads[ii].stats.lru_hits[sid];
+        }
+
+        stats->response_obj_bytes += threads[ii].resp_cache->total * sizeof(mc_resp);
+        stats->response_obj_total += threads[ii].resp_cache->total;
+        stats->response_obj_free += threads[ii].resp_cache->freecurr;
+        stats->read_buf_bytes += threads[ii].rbuf_cache->total * READ_BUFFER_SIZE;
+        stats->read_buf_bytes_free += threads[ii].rbuf_cache->freecurr * READ_BUFFER_SIZE;
+        pthread_mutex_unlock(&threads[ii].stats.mutex);
+    }
+}
+
+void slab_stats_aggregate(struct thread_stats *stats, struct slab_stats *out) {
+    int sid;
+
+    memset(out, 0, sizeof(*out));
+
+    for (sid = 0; sid < MAX_NUMBER_OF_SLAB_CLASSES; sid++) {
+#define X(name) out->name += stats->slab_stats[sid].name;
+        SLAB_STATS_FIELDS
+#undef X
+    }
+}
+
+/*
+ * Initializes the thread subsystem, creating various worker threads.
+ *
+ * nthreads  Number of worker event handler threads to spawn
+ */
+void memcached_thread_init(int nthreads, void *arg) {
+    int         i;
+    int         power;
+
+    for (i = 0; i < POWER_LARGEST; i++) {
+        pthread_mutex_init(&lru_locks[i], NULL);
+    }
+    pthread_mutex_init(&worker_hang_lock, NULL);
+
+    pthread_mutex_init(&init_lock, NULL);
+    pthread_cond_init(&init_cond, NULL);
+
+    pthread_mutex_init(&cqi_freelist_lock, NULL);
+    cqi_freelist = NULL;
+
+    /* Want a wide lock table, but don't waste memory */
+    if (nthreads < 3) {
+        power = 10;
+    } else if (nthreads < 4) {
+        power = 11;
+    } else if (nthreads < 5) {
+        power = 12;
+    } else if (nthreads <= 10) {
+        power = 13;
+    } else if (nthreads <= 20) {
+        power = 14;
+    } else {
+        /* 32k buckets. just under the hashpower default. */
+        power = 15;
+    }
+
+    if (power >= hashpower) {
+        fprintf(stderr, "Hash table power size (%d) cannot be equal to or less than item lock table (%d)\n", hashpower, power);
+        fprintf(stderr, "Item lock table grows with `-t N` (worker threadcount)\n");
+        fprintf(stderr, "Hash table grows with `-o hashpower=N` \n");
+        exit(1);
+    }
+
+    item_lock_count = hashsize(power);
+    item_lock_hashpower = power;
+
+    item_locks = calloc(item_lock_count, sizeof(pthread_mutex_t));
+    if (! item_locks) {
+        perror("Can't allocate item locks");
+        exit(1);
+    }
+    for (i = 0; i < item_lock_count; i++) {
+        pthread_mutex_init(&item_locks[i], NULL);
+    }
+
+    threads = calloc(nthreads, sizeof(LIBEVENT_THREAD));
+    if (! threads) {
+        perror("Can't allocate thread descriptors");
+        exit(1);
+    }
+
+    for (i = 0; i < nthreads; i++) {
+        int fds[2];
+        if (pipe(fds)) {
+            perror("Can't create notify pipe");
+            exit(1);
+        }
+
+        threads[i].notify_receive_fd = fds[0];
+        threads[i].notify_send_fd = fds[1];
+#ifdef EXTSTORE
+        threads[i].storage = arg;
+#endif
+        setup_thread(&threads[i]);
+        /* Reserve three fds for the libevent base, and two for the pipe */
+        stats_state.reserved_fds += 5;
+    }
+
+    /* Create threads after we've done all the libevent setup. */
+    for (i = 0; i < nthreads; i++) {
+        create_worker(worker_libevent, &threads[i]);
+    }
+
+    /* Wait for all the threads to set themselves up before returning. */
+    pthread_mutex_lock(&init_lock);
+    wait_for_thread_registration(nthreads);
+    pthread_mutex_unlock(&init_lock);
+}
+
diff --git a/cachelib/external/memcached/timedrun.c b/cachelib/external/memcached/timedrun.c
new file mode 100644
index 0000000000..28ce498a74
--- /dev/null
+++ b/cachelib/external/memcached/timedrun.c
@@ -0,0 +1,111 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+
+#include <assert.h>
+
+volatile sig_atomic_t caught_sig = 0;
+
+static void signal_handler(int which)
+{
+    caught_sig = which;
+}
+
+static int wait_for_process(pid_t pid)
+{
+    int rv = EX_SOFTWARE;
+    int status = 0;
+    int i = 0;
+    struct sigaction sig_handler;
+
+    memset(&sig_handler, 0, sizeof(struct sigaction));
+    sig_handler.sa_handler = signal_handler;
+    sig_handler.sa_flags = 0;
+
+    sigaction(SIGALRM, &sig_handler, NULL);
+    sigaction(SIGHUP, &sig_handler, NULL);
+    sigaction(SIGINT, &sig_handler, NULL);
+    sigaction(SIGUSR1, &sig_handler, NULL);
+    sigaction(SIGTERM, &sig_handler, NULL);
+    sigaction(SIGPIPE, &sig_handler, NULL);
+
+    /* Loop forever waiting for the process to quit */
+    for (i = 0; ;i++) {
+        pid_t p = waitpid(pid, &status, 0);
+        if (p == pid) {
+            /* child exited.  Let's get out of here */
+            rv = WIFEXITED(status) ?
+                WEXITSTATUS(status) :
+                (0x80 | WTERMSIG(status));
+            break;
+        } else {
+            int sig = 0;
+            switch (i) {
+            case 0:
+                /* On the first iteration, pass the signal through */
+                sig = caught_sig > 0 ? caught_sig : SIGTERM;
+                if (caught_sig == SIGALRM) {
+                   fprintf(stderr, "Timeout.. killing the process\n");
+                }
+                break;
+            case 1:
+                sig = SIGTERM;
+                break;
+            default:
+                sig = SIGKILL;
+                break;
+            }
+            if (kill(pid, sig) < 0) {
+                /* Kill failed.  Must have lost the process. :/ */
+                perror("lost child when trying to kill");
+            }
+            /* Wait up to 5 seconds for the pid */
+            alarm(5);
+        }
+    }
+    return rv;
+}
+
+static int spawn_and_wait(char **argv)
+{
+    int rv = EX_SOFTWARE;
+    pid_t pid = fork();
+
+    switch (pid) {
+    case -1:
+        perror("fork");
+        rv = EX_OSERR;
+        break; /* NOTREACHED */
+    case 0:
+        execvp(argv[0], argv);
+        perror("exec");
+        rv = EX_SOFTWARE;
+        break; /* NOTREACHED */
+    default:
+        rv = wait_for_process(pid);
+    }
+    return rv;
+}
+
+static void usage(void) {
+    fprintf(stderr, "./timedrun <naptime in sec> args...\n");
+    exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+    int naptime = 0;
+    if (argc < 3)
+        usage();
+
+    naptime = atoi(argv[1]);
+    assert(naptime > 0 && naptime < 1800);
+
+    alarm(naptime);
+
+    return spawn_and_wait(argv+2);
+}
diff --git a/cachelib/external/memcached/tls.c b/cachelib/external/memcached/tls.c
new file mode 100644
index 0000000000..4b93af8b5a
--- /dev/null
+++ b/cachelib/external/memcached/tls.c
@@ -0,0 +1,219 @@
+#include "memcached.h"
+
+#ifdef TLS
+
+#include "tls.h"
+#include <string.h>
+#include <sysexits.h>
+#include <sys/param.h>
+
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 4096
+#endif
+
+static pthread_mutex_t ssl_ctx_lock = PTHREAD_MUTEX_INITIALIZER;
+
+const unsigned MAX_ERROR_MSG_SIZE = 128;
+
+void SSL_LOCK() {
+    pthread_mutex_lock(&(ssl_ctx_lock));
+}
+
+void SSL_UNLOCK(void) {
+    pthread_mutex_unlock(&(ssl_ctx_lock));
+}
+
+/*
+ * Reads decrypted data from the underlying BIO read buffers,
+ * which reads from the socket.
+ */
+ssize_t ssl_read(conn *c, void *buf, size_t count) {
+    assert (c != NULL);
+    /* TODO : document the state machine interactions for SSL_read with
+        non-blocking sockets/ SSL re-negotiations
+    */
+    return SSL_read(c->ssl, buf, count);
+}
+
+/*
+ * SSL sendmsg implementation. Perform a SSL_write.
+ */
+ssize_t ssl_sendmsg(conn *c, struct msghdr *msg, int flags) {
+    assert (c != NULL);
+    size_t buf_remain = settings.ssl_wbuf_size;
+    size_t bytes = 0;
+    size_t to_copy;
+    int i;
+
+    // ssl_wbuf is pointing to the buffer allocated in the worker thread.
+    assert(c->ssl_wbuf);
+    // TODO: allocate a fix buffer in crawler/logger if they start using
+    // the sendmsg method. Also, set c->ssl_wbuf  when the side thread
+    // start owning the connection and reset the pointer in
+    // conn_worker_readd.
+    // Currntly this connection would not be served by a different thread
+    // than the one it's assigned.
+    assert(pthread_equal(c->thread->thread_id, pthread_self()) != 0);
+
+    char *bp = c->ssl_wbuf;
+    for (i = 0; i < msg->msg_iovlen; i++) {
+        size_t len = msg->msg_iov[i].iov_len;
+        to_copy = len < buf_remain ? len : buf_remain;
+
+        memcpy(bp + bytes, (void*)msg->msg_iov[i].iov_base, to_copy);
+        buf_remain -= to_copy;
+        bytes += to_copy;
+        if (buf_remain == 0)
+            break;
+    }
+    /* TODO : document the state machine interactions for SSL_write with
+        non-blocking sockets/ SSL re-negotiations
+    */
+    return SSL_write(c->ssl, c->ssl_wbuf, bytes);
+}
+
+/*
+ * Writes data to the underlying BIO write buffers,
+ * which encrypt and write them to the socket.
+ */
+ssize_t ssl_write(conn *c, void *buf, size_t count) {
+    assert (c != NULL);
+    return SSL_write(c->ssl, buf, count);
+}
+
+/*
+ * Loads server certificates to the SSL context and validate them.
+ * @return whether certificates are successfully loaded and verified or not.
+ * @param error_msg contains the error when unsuccessful.
+ */
+static bool load_server_certificates(char **errmsg) {
+    bool success = true;
+    char *error_msg = malloc(MAXPATHLEN + MAX_ERROR_MSG_SIZE);
+    size_t errmax = MAXPATHLEN + MAX_ERROR_MSG_SIZE - 1;
+    if (error_msg == NULL) {
+        *errmsg = NULL;
+        return false;
+    }
+    if (settings.ssl_ctx == NULL) {
+        snprintf(error_msg, errmax, "Error TLS not enabled\r\n");
+        *errmsg = error_msg;
+        return false;
+    }
+    SSL_LOCK();
+    if (!SSL_CTX_use_certificate_chain_file(settings.ssl_ctx,
+        settings.ssl_chain_cert)) {
+        snprintf(error_msg, errmax, "Error loading the certificate chain: %s\r\n",
+            settings.ssl_chain_cert);
+        success = false;
+    } else if (!SSL_CTX_use_PrivateKey_file(settings.ssl_ctx, settings.ssl_key,
+                                        settings.ssl_keyformat)) {
+        snprintf(error_msg, errmax, "Error loading the key: %s\r\n", settings.ssl_key);
+        success = false;
+    } else if (!SSL_CTX_check_private_key(settings.ssl_ctx)) {
+        snprintf(error_msg, errmax, "Error validating the certificate\r\n");
+        success = false;
+    } else if (settings.ssl_ca_cert) {
+        if (!SSL_CTX_load_verify_locations(settings.ssl_ctx,
+          settings.ssl_ca_cert, NULL)) {
+            snprintf(error_msg, errmax,
+              "Error loading the CA certificate: %s\r\n", settings.ssl_ca_cert);
+            success = false;
+        } else {
+            SSL_CTX_set_client_CA_list(settings.ssl_ctx,
+              SSL_load_client_CA_file(settings.ssl_ca_cert));
+        }
+    }
+    SSL_UNLOCK();
+    if (success) {
+        settings.ssl_last_cert_refresh_time = current_time;
+        free(error_msg);
+    } else {
+        *errmsg = error_msg;
+    }
+    return success;
+}
+
+/*
+ * Verify SSL settings and initiates the SSL context.
+ */
+int ssl_init(void) {
+    assert(settings.ssl_enabled);
+    // SSL context for the process. All connections will share one
+    // process level context.
+    settings.ssl_ctx = SSL_CTX_new(TLS_server_method());
+    // Clients should use at least TLSv1.2
+    int flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
+    SSL_CTX_set_options(settings.ssl_ctx, flags);
+
+    // The server certificate, private key and validations.
+    char *error_msg;
+    if (!load_server_certificates(&error_msg)) {
+        if (settings.verbose) {
+            fprintf(stderr, "%s", error_msg);
+        }
+        free(error_msg);
+        exit(EX_USAGE);
+    }
+
+    // The verification mode of client certificate, default is SSL_VERIFY_PEER.
+    SSL_CTX_set_verify(settings.ssl_ctx, settings.ssl_verify_mode, NULL);
+    if (settings.ssl_ciphers && !SSL_CTX_set_cipher_list(settings.ssl_ctx,
+                                                    settings.ssl_ciphers)) {
+        if (settings.verbose) {
+            fprintf(stderr, "Error setting the provided cipher(s): %s\n",
+                    settings.ssl_ciphers);
+        }
+        exit(EX_USAGE);
+    }
+
+    // Optional session caching; default disabled.
+    if (settings.ssl_session_cache) {
+        SSL_CTX_sess_set_new_cb(settings.ssl_ctx, ssl_new_session_callback);
+        SSL_CTX_set_session_cache_mode(settings.ssl_ctx, SSL_SESS_CACHE_SERVER);
+        SSL_CTX_set_session_id_context(settings.ssl_ctx,
+                                       (const unsigned char *) SESSION_ID_CONTEXT,
+                                       strlen(SESSION_ID_CONTEXT));
+    } else {
+        SSL_CTX_set_session_cache_mode(settings.ssl_ctx, SSL_SESS_CACHE_OFF);
+    }
+
+    return 0;
+}
+
+/*
+ * This method is registered with each SSL connection and abort the SSL session
+ * if a client initiates a renegotiation.
+ * TODO : Proper way to do this is to set SSL_OP_NO_RENEGOTIATION
+ *       using the SSL_CTX_set_options but that option only available in
+ *       openssl 1.1.0h or above.
+ */
+void ssl_callback(const SSL *s, int where, int ret) {
+    SSL* ssl = (SSL*)s;
+    if (SSL_in_before(ssl)) {
+        if (settings.verbose) {
+            fprintf(stderr, "%d: SSL renegotiation is not supported, "
+                    "closing the connection\n", SSL_get_fd(ssl));
+        }
+        SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
+        return;
+    }
+}
+
+/*
+ * This method is invoked with every new successfully negotiated SSL session,
+ * when server-side session caching is enabled. Note that this method is not
+ * invoked when a session is reused.
+ */
+int ssl_new_session_callback(SSL *s, SSL_SESSION *sess) {
+    STATS_LOCK();
+    stats.ssl_new_sessions++;
+    STATS_UNLOCK();
+
+    return 0;
+}
+
+bool refresh_certs(char **errmsg) {
+    return load_server_certificates(errmsg);
+}
+#endif
diff --git a/cachelib/external/memcached/tls.h b/cachelib/external/memcached/tls.h
new file mode 100644
index 0000000000..3ec2e2949e
--- /dev/null
+++ b/cachelib/external/memcached/tls.h
@@ -0,0 +1,19 @@
+#ifndef TLS_H
+#define TLS_H
+
+/* constant session ID context for application-level SSL session scoping.
+ * used in server-side SSL session caching, when enabled. */
+#define SESSION_ID_CONTEXT "memcached"
+
+void SSL_LOCK(void);
+void SSL_UNLOCK(void);
+ssize_t ssl_read(conn *c, void *buf, size_t count);
+ssize_t ssl_sendmsg(conn *c, struct msghdr *msg, int flags);
+ssize_t ssl_write(conn *c, void *buf, size_t count);
+
+int ssl_init(void);
+bool refresh_certs(char **errmsg);
+void ssl_callback(const SSL *s, int where, int ret);
+int ssl_new_session_callback(SSL *s, SSL_SESSION *sess);
+
+#endif
diff --git a/cachelib/external/memcached/trace.h b/cachelib/external/memcached/trace.h
new file mode 100644
index 0000000000..94e585d244
--- /dev/null
+++ b/cachelib/external/memcached/trace.h
@@ -0,0 +1,71 @@
+#ifndef TRACE_H
+#define TRACE_H
+
+#ifdef ENABLE_DTRACE
+#include "memcached_dtrace.h"
+#else
+#define MEMCACHED_ASSOC_DELETE(arg0, arg1)
+#define MEMCACHED_ASSOC_DELETE_ENABLED() (0)
+#define MEMCACHED_ASSOC_FIND(arg0, arg1, arg2)
+#define MEMCACHED_ASSOC_FIND_ENABLED() (0)
+#define MEMCACHED_ASSOC_INSERT(arg0, arg1)
+#define MEMCACHED_ASSOC_INSERT_ENABLED() (0)
+#define MEMCACHED_COMMAND_ADD(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_ADD_ENABLED() (0)
+#define MEMCACHED_COMMAND_APPEND(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_APPEND_ENABLED() (0)
+#define MEMCACHED_COMMAND_CAS(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_CAS_ENABLED() (0)
+#define MEMCACHED_COMMAND_DECR(arg0, arg1, arg2, arg3)
+#define MEMCACHED_COMMAND_DECR_ENABLED() (0)
+#define MEMCACHED_COMMAND_DELETE(arg0, arg1, arg2)
+#define MEMCACHED_COMMAND_DELETE_ENABLED() (0)
+#define MEMCACHED_COMMAND_GET(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_GET_ENABLED() (0)
+#define MEMCACHED_COMMAND_TOUCH(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_TOUCH_ENABLED() (0)
+#define MEMCACHED_COMMAND_INCR(arg0, arg1, arg2, arg3)
+#define MEMCACHED_COMMAND_INCR_ENABLED() (0)
+#define MEMCACHED_COMMAND_PREPEND(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_PREPEND_ENABLED() (0)
+#define MEMCACHED_COMMAND_REPLACE(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_REPLACE_ENABLED() (0)
+#define MEMCACHED_COMMAND_SET(arg0, arg1, arg2, arg3, arg4)
+#define MEMCACHED_COMMAND_SET_ENABLED() (0)
+#define MEMCACHED_CONN_ALLOCATE(arg0)
+#define MEMCACHED_CONN_ALLOCATE_ENABLED() (0)
+#define MEMCACHED_CONN_CREATE(arg0)
+#define MEMCACHED_CONN_CREATE_ENABLED() (0)
+#define MEMCACHED_CONN_DESTROY(arg0)
+#define MEMCACHED_CONN_DESTROY_ENABLED() (0)
+#define MEMCACHED_CONN_DISPATCH(arg0, arg1)
+#define MEMCACHED_CONN_DISPATCH_ENABLED() (0)
+#define MEMCACHED_CONN_RELEASE(arg0)
+#define MEMCACHED_CONN_RELEASE_ENABLED() (0)
+#define MEMCACHED_ITEM_LINK(arg0, arg1, arg2)
+#define MEMCACHED_ITEM_LINK_ENABLED() (0)
+#define MEMCACHED_ITEM_REMOVE(arg0, arg1, arg2)
+#define MEMCACHED_ITEM_REMOVE_ENABLED() (0)
+#define MEMCACHED_ITEM_REPLACE(arg0, arg1, arg2, arg3, arg4, arg5)
+#define MEMCACHED_ITEM_REPLACE_ENABLED() (0)
+#define MEMCACHED_ITEM_UNLINK(arg0, arg1, arg2)
+#define MEMCACHED_ITEM_UNLINK_ENABLED() (0)
+#define MEMCACHED_ITEM_UPDATE(arg0, arg1, arg2)
+#define MEMCACHED_ITEM_UPDATE_ENABLED() (0)
+#define MEMCACHED_PROCESS_COMMAND_END(arg0, arg1, arg2)
+#define MEMCACHED_PROCESS_COMMAND_END_ENABLED() (0)
+#define MEMCACHED_PROCESS_COMMAND_START(arg0, arg1, arg2)
+#define MEMCACHED_PROCESS_COMMAND_START_ENABLED() (0)
+#define MEMCACHED_SLABS_ALLOCATE(arg0, arg1, arg2, arg3)
+#define MEMCACHED_SLABS_ALLOCATE_ENABLED() (0)
+#define MEMCACHED_SLABS_ALLOCATE_FAILED(arg0, arg1)
+#define MEMCACHED_SLABS_ALLOCATE_FAILED_ENABLED() (0)
+#define MEMCACHED_SLABS_FREE(arg0, arg1, arg2)
+#define MEMCACHED_SLABS_FREE_ENABLED() (0)
+#define MEMCACHED_SLABS_SLABCLASS_ALLOCATE(arg0)
+#define MEMCACHED_SLABS_SLABCLASS_ALLOCATE_ENABLED() (0)
+#define MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(arg0)
+#define MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED_ENABLED() (0)
+#endif
+
+#endif
diff --git a/cachelib/external/memcached/util.c b/cachelib/external/memcached/util.c
new file mode 100644
index 0000000000..5072ae1f61
--- /dev/null
+++ b/cachelib/external/memcached/util.c
@@ -0,0 +1,265 @@
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "memcached.h"
+
+static char *uriencode_map[256];
+static char uriencode_str[768];
+
+void uriencode_init(void) {
+    int x;
+    char *str = uriencode_str;
+    for (x = 0; x < 256; x++) {
+        if (isalnum(x) || x == '-' || x == '.' || x == '_' || x == '~') {
+            uriencode_map[x] = NULL;
+        } else {
+            snprintf(str, 4, "%%%02hhX", (unsigned char)x);
+            uriencode_map[x] = str;
+            str += 3; /* lobbing off the \0 is fine */
+        }
+    }
+}
+
+bool uriencode(const char *src, char *dst, const size_t srclen, const size_t dstlen) {
+    int x;
+    size_t d = 0;
+    for (x = 0; x < srclen; x++) {
+        if (d + 4 > dstlen)
+            return false;
+        if (uriencode_map[(unsigned char) src[x]] != NULL) {
+            memcpy(&dst[d], uriencode_map[(unsigned char) src[x]], 3);
+            d += 3;
+        } else {
+            dst[d] = src[x];
+            d++;
+        }
+    }
+    dst[d] = '\0';
+    return true;
+}
+
+/* Avoid warnings on solaris, where isspace() is an index into an array, and gcc uses signed chars */
+#define xisspace(c) isspace((unsigned char)c)
+
+bool safe_strtoull(const char *str, uint64_t *out) {
+    assert(out != NULL);
+    errno = 0;
+    *out = 0;
+    char *endptr;
+    unsigned long long ull = strtoull(str, &endptr, 10);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        if ((long long) ull < 0) {
+            /* only check for negative signs in the uncommon case when
+             * the unsigned number is so big that it's negative as a
+             * signed number. */
+            if (strchr(str, '-') != NULL) {
+                return false;
+            }
+        }
+        *out = ull;
+        return true;
+    }
+    return false;
+}
+
+/* Could macro this. Decided to keep this unrolled for safety rather than add
+ * the base parameter to all callers. Very few places need to parse a number
+ * outside of base 10, currently exactly once, so splitting this up should
+ * help avoid typo bugs.
+ */
+bool safe_strtoull_hex(const char *str, uint64_t *out) {
+    assert(out != NULL);
+    errno = 0;
+    *out = 0;
+    char *endptr;
+    unsigned long long ull = strtoull(str, &endptr, 16);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        if ((long long) ull < 0) {
+            /* only check for negative signs in the uncommon case when
+             * the unsigned number is so big that it's negative as a
+             * signed number. */
+            if (strchr(str, '-') != NULL) {
+                return false;
+            }
+        }
+        *out = ull;
+        return true;
+    }
+    return false;
+}
+
+bool safe_strtoll(const char *str, int64_t *out) {
+    assert(out != NULL);
+    errno = 0;
+    *out = 0;
+    char *endptr;
+    long long ll = strtoll(str, &endptr, 10);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        *out = ll;
+        return true;
+    }
+    return false;
+}
+
+bool safe_strtoul(const char *str, uint32_t *out) {
+    char *endptr = NULL;
+    unsigned long l = 0;
+    assert(out);
+    assert(str);
+    *out = 0;
+    errno = 0;
+
+    l = strtoul(str, &endptr, 10);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        if ((long) l < 0) {
+            /* only check for negative signs in the uncommon case when
+             * the unsigned number is so big that it's negative as a
+             * signed number. */
+            if (strchr(str, '-') != NULL) {
+                return false;
+            }
+        }
+        *out = l;
+        return true;
+    }
+
+    return false;
+}
+
+bool safe_strtol(const char *str, int32_t *out) {
+    assert(out != NULL);
+    errno = 0;
+    *out = 0;
+    char *endptr;
+    long l = strtol(str, &endptr, 10);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        *out = l;
+        return true;
+    }
+    return false;
+}
+
+bool safe_strtod(const char *str, double *out) {
+    assert(out != NULL);
+    errno = 0;
+    *out = 0;
+    char *endptr;
+    double d = strtod(str, &endptr);
+    if ((errno == ERANGE) || (str == endptr)) {
+        return false;
+    }
+
+    if (xisspace(*endptr) || (*endptr == '\0' && endptr != str)) {
+        *out = d;
+        return true;
+    }
+    return false;
+}
+
+// slow, safe function for copying null terminated buffers.
+// ensures null terminator set on destination buffer. copies at most dstmax-1
+// non-null bytes.
+// Explicitly avoids over-reading src while looking for the null byte.
+// returns true if src was fully copied.
+// returns false if src was truncated into dst.
+bool safe_strcpy(char *dst, const char *src, const size_t dstmax) {
+   size_t x;
+
+   for (x = 0; x < dstmax - 1 && src[x] != '\0'; x++) {
+        dst[x] = src[x];
+   }
+
+   dst[x] = '\0';
+
+   if (src[x] == '\0') {
+       return true;
+   } else {
+       return false;
+   }
+}
+
+bool safe_memcmp(const void *a, const void *b, size_t len) {
+    const volatile unsigned char *ua = (const volatile unsigned char *)a;
+    const volatile unsigned char *ub = (const volatile unsigned char *)b;
+    int delta = 0;
+    size_t x;
+
+    for (x = 0; x < len; x++) {
+        delta |= ua[x] ^ ub[x];
+    }
+
+    if (delta == 0) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void vperror(const char *fmt, ...) {
+    int old_errno = errno;
+    char buf[1024];
+    va_list ap;
+
+    va_start(ap, fmt);
+    if (vsnprintf(buf, sizeof(buf), fmt, ap) == -1) {
+        buf[sizeof(buf) - 1] = '\0';
+    }
+    va_end(ap);
+
+    errno = old_errno;
+
+    perror(buf);
+}
+
+#ifndef HAVE_HTONLL
+static uint64_t mc_swap64(uint64_t in) {
+#ifdef ENDIAN_LITTLE
+    /* Little endian, flip the bytes around until someone makes a faster/better
+    * way to do this. */
+    int64_t rv = 0;
+    int i = 0;
+     for(i = 0; i<8; i++) {
+        rv = (rv << 8) | (in & 0xff);
+        in >>= 8;
+     }
+    return rv;
+#else
+    /* big-endian machines don't need byte swapping */
+    return in;
+#endif
+}
+
+uint64_t ntohll(uint64_t val) {
+   return mc_swap64(val);
+}
+
+uint64_t htonll(uint64_t val) {
+   return mc_swap64(val);
+}
+#endif
+
diff --git a/cachelib/external/memcached/util.h b/cachelib/external/memcached/util.h
new file mode 100644
index 0000000000..6355eb5070
--- /dev/null
+++ b/cachelib/external/memcached/util.h
@@ -0,0 +1,43 @@
+#include "config.h"
+
+/* fast-enough functions for uriencoding strings. */
+void uriencode_init(void);
+bool uriencode(const char *src, char *dst, const size_t srclen, const size_t dstlen);
+
+/*
+ * Wrappers around strtoull/strtoll that are safer and easier to
+ * use.  For tests and assumptions, see internal_tests.c.
+ *
+ * str   a NULL-terminated base decimal 10 unsigned integer
+ * out   out parameter, if conversion succeeded
+ *
+ * returns true if conversion succeeded.
+ */
+bool safe_strtoull(const char *str, uint64_t *out);
+bool safe_strtoull_hex(const char *str, uint64_t *out);
+bool safe_strtoll(const char *str, int64_t *out);
+bool safe_strtoul(const char *str, uint32_t *out);
+bool safe_strtol(const char *str, int32_t *out);
+bool safe_strtod(const char *str, double *out);
+bool safe_strcpy(char *dst, const char *src, const size_t dstmax);
+bool safe_memcmp(const void *a, const void *b, size_t len);
+
+#ifndef HAVE_HTONLL
+extern uint64_t htonll(uint64_t);
+extern uint64_t ntohll(uint64_t);
+#endif
+
+#ifdef __GCC
+# define __gcc_attribute__ __attribute__
+#else
+# define __gcc_attribute__(x)
+#endif
+
+/**
+ * Vararg variant of perror that makes for more useful error messages
+ * when reporting with parameters.
+ *
+ * @param fmt a printf format
+ */
+void vperror(const char *fmt, ...)
+    __gcc_attribute__ ((format (printf, 1, 2)));
diff --git a/cachelib/navy/CMakeLists.txt b/cachelib/navy/CMakeLists.txt
new file mode 100644
index 0000000000..58474ee3c7
--- /dev/null
+++ b/cachelib/navy/CMakeLists.txt
@@ -0,0 +1,148 @@
+#https://github.com/facebook/fbthrift/blob/master/ThriftLibrary.cmake lines 88 to 121
+thrift_object("navy_objects" "" "cpp2" "" "${CMAKE_CURRENT_SOURCE_DIR}/serialization" "${CMAKE_CURRENT_SOURCE_DIR}/serialization" "${CMAKE_CURRENT_SOURCE_DIR}/serialization")
+
+add_library(
+    navy
+    
+    AbstractCache.h
+    Factory.h
+    Factory.cpp
+
+    admission_policy/AdmissionPolicy.h
+    admission_policy/DynamicRandomAP.h
+    admission_policy/RejectRandomAP.h
+    admission_policy/DynamicRandomAP.cpp
+    admission_policy/RejectRandomAP.cpp
+
+    bighash/BigHash.cpp
+    bighash/BloomFilter.cpp
+    bighash/Bucket.cpp
+    bighash/BucketStorage.cpp
+    bighash/BigHash.h
+    bighash/BloomFilter.h
+    bighash/Bucket.h
+    bighash/BucketStorage.h
+    $<TARGET_OBJECTS:navy_objects-cpp2-obj> 
+
+    block_cache/Allocator.h
+    block_cache/BTree.h
+    block_cache/BlockCache.h
+    block_cache/EvictionPolicy.h
+    block_cache/FifoPolicy.h
+    block_cache/HitsReinsertionPolicy.h
+    block_cache/Index.h
+    block_cache/LruPolicy.h
+    block_cache/ProbabilisticReinsertionPolicy.h
+    block_cache/Region.h
+    block_cache/RegionManager.h
+    block_cache/ReinsertionPolicy.h
+    block_cache/Types.h
+    block_cache/Allocator.cpp
+    block_cache/BlockCache.cpp
+    block_cache/FifoPolicy.cpp
+    block_cache/HitsReinsertionPolicy.cpp
+    block_cache/Index.cpp
+    block_cache/LruPolicy.cpp
+    block_cache/Region.cpp
+    block_cache/RegionManager.cpp
+
+    common/Buffer.cpp
+    common/Buffer.h
+    common/CompilerUtils.h
+    common/Device.cpp
+    common/Device.h
+    common/Hash.cpp
+    common/Hash.h
+    common/SizeDistribution.cpp
+    common/SizeDistribution.h
+    common/Types.cpp
+    common/Types.h
+    common/Utils.h
+
+    driver/Driver.h
+    driver/NoopEngine.h
+    driver/Driver.cpp
+
+    serialization/Serialization.h
+    serialization/RecordIO.cpp
+    serialization/RecordIO.h
+    serialization/navy_objects.thrift
+
+    scheduler/JobScheduler.h
+    scheduler/ThreadPoolJobScheduler.h
+    scheduler/ThreadPoolJobScheduler.cpp
+
+    testing/BufferGen.cpp
+    testing/BufferGen.h
+    testing/Callbacks.h
+    testing/MockDevice.h
+    testing/MockDevice.cpp
+    testing/MockJobScheduler.cpp
+    testing/MockJobScheduler.h
+    testing/SeqPoints.cpp
+    testing/SeqPoints.h
+)
+target_link_libraries(
+        navy
+        PUBLIC
+        ${FOLLY_LIBRARIES}
+        ${FBTHRIFT_LIBRARIES}
+
+)
+add_executable(
+        navy_test_bin
+
+        admission_policy/tests/DynamicRandomAPTest.cpp
+        admission_policy/tests/RejectRandomAPTest.cpp
+
+        bighash/tests/BigHashTest.cpp
+        bighash/tests/BloomFilterTest.cpp
+        bighash/tests/BucketStorageTest.cpp
+        bighash/tests/BucketTest.cpp
+
+        block_cache/tests/MockPolicy.h
+        block_cache/tests/AllocatorTest.cpp
+        block_cache/tests/BTreeTest.cpp
+        block_cache/tests/BlockCacheTest.cpp
+        block_cache/tests/FifoPolicyTest.cpp
+        block_cache/tests/HitsReinsertionPolicyTest.cpp
+        block_cache/tests/IndexTest.cpp
+        block_cache/tests/LruPolicyTest.cpp
+        block_cache/tests/RegionManagerTest.cpp
+        block_cache/tests/RegionTest.cpp
+
+        common/tests/BufferTest.cpp
+        common/tests/DeviceTest.cpp
+        common/tests/HashTest.cpp
+        common/tests/UtilsTest.cpp
+
+        driver/tests/DriverTest.cpp
+
+        scheduler/tests/OrderedThreadPoolJobSchedulerTest.cpp
+        scheduler/tests/ThreadPoolJobSchedulerTest.cpp
+        serialization/tests/RecordIOTest.cpp
+        serialization/tests/SerializationTest.cpp
+
+        testing/tests/BufferGenTest.cpp
+        testing/tests/MockJobSchedulerTest.cpp
+        testing/tests/SeqPointsTest.cpp
+)
+
+target_link_libraries(
+        navy_test_bin
+
+        PUBLIC
+            navy
+            cachelib_common
+            GTest::GTest
+            GTest::Main
+            ${GMOCK_LIBRARIES}
+            ${FOLLY_LIBRARIES}
+            ${FBTHRIFT_LIBRARIES}
+)
+
+add_test(
+        NAME navy_tests
+        COMMAND navy_test_bin
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
diff --git a/cachelib/navy/bighash/tests/BucketTest.cpp b/cachelib/navy/bighash/tests/BucketTest.cpp
index 81ebe5c8ff..027fd0f58d 100644
--- a/cachelib/navy/bighash/tests/BucketTest.cpp
+++ b/cachelib/navy/bighash/tests/BucketTest.cpp
@@ -270,6 +270,7 @@ TEST(Bucket, Checksum) {
 }
 
 TEST(Bucket, Iteration) {
+  EXPECT_EQ(1,1);
   Buffer buf(96 + sizeof(Bucket));
   auto& bucket = Bucket::initNew(buf.mutableView(), 0);
 
@@ -320,6 +321,7 @@ TEST(Bucket, Iteration) {
     EXPECT_TRUE(itr.done());
   }
 }
+
 } // namespace tests
 } // namespace navy
 } // namespace cachelib
diff --git a/cachelib/navy/serialization/Serialization.h b/cachelib/navy/serialization/Serialization.h
index f7200884a6..72cc53e238 100644
--- a/cachelib/navy/serialization/Serialization.h
+++ b/cachelib/navy/serialization/Serialization.h
@@ -27,7 +27,9 @@ ThriftObject deserializeProto(RecordReader& reader) {
 
 template <typename ThriftObject>
 std::string serializeToJson(const ThriftObject& obj) {
-  return apache::thrift::SimpleJSONSerializer::serialize<std::string>(obj);
+  std::string tmp;
+  ProtoSerializer::serialize(obj,&tmp);
+  return tmp;
 }
 } // namespace navy
 } // namespace cachelib
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_constants.cpp b/cachelib/navy/serialization/gen-cpp2/objects_constants.cpp
new file mode 100644
index 0000000000..5479bf9173
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_constants.cpp
@@ -0,0 +1,15 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_constants.h"
+
+#include <thrift/lib/cpp2/gen/module_constants_cpp.h>
+
+
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+}}}} // facebook::cachelib::navy::serialization
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_constants.h b/cachelib/navy/serialization/gen-cpp2/objects_constants.h
new file mode 100644
index 0000000000..ea87f32a35
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_constants.h
@@ -0,0 +1,19 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+#pragma once
+
+#include <thrift/lib/cpp2/gen/module_constants_h.h>
+
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_types.h"
+
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+struct objects_constants {
+
+};
+
+}}}} // facebook::cachelib::navy::serialization
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_data.cpp b/cachelib/navy/serialization/gen-cpp2/objects_data.cpp
new file mode 100644
index 0000000000..a97b994590
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_data.cpp
@@ -0,0 +1,12 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_data.h"
+
+#include <thrift/lib/cpp2/gen/module_data_cpp.h>
+
+
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_data.h b/cachelib/navy/serialization/gen-cpp2/objects_data.h
new file mode 100644
index 0000000000..a8b2313a16
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_data.h
@@ -0,0 +1,13 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+#pragma once
+
+#include <thrift/lib/cpp2/gen/module_data_h.h>
+
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_types.h"
+
+
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_metadata.cpp b/cachelib/navy/serialization/gen-cpp2/objects_metadata.cpp
new file mode 100644
index 0000000000..72b4aadcf1
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_metadata.cpp
@@ -0,0 +1,243 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+#include <thrift/lib/cpp2/gen/module_metadata_cpp.h>
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_metadata.h"
+
+namespace apache {
+namespace thrift {
+namespace detail {
+namespace md {
+using ThriftMetadata = ::apache::thrift::metadata::ThriftMetadata;
+using ThriftPrimitiveType = ::apache::thrift::metadata::ThriftPrimitiveType;
+using ThriftType = ::apache::thrift::metadata::ThriftType;
+using ThriftService = ::apache::thrift::metadata::ThriftService;
+using ThriftServiceContext = ::apache::thrift::metadata::ThriftServiceContext;
+using ThriftFunctionGenerator = void (*)(ThriftMetadata&, ThriftService&);
+
+
+void StructMetadata<::facebook::cachelib::navy::serialization::IndexEntry>::gen(ThriftMetadata& metadata) {
+  auto res = metadata.structs.emplace("objects.IndexEntry", ::apache::thrift::metadata::ThriftStruct{});
+  if (!res.second) {
+    return;
+  }
+  ::apache::thrift::metadata::ThriftStruct& objects_IndexEntry = res.first->second;
+  objects_IndexEntry.name = "objects.IndexEntry";
+  objects_IndexEntry.is_union = false;
+  static const std::tuple<int32_t, const char*, bool, std::unique_ptr<MetadataTypeInterface>>
+  objects_IndexEntry_fields[] = {
+    std::make_tuple(1, "key", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(2, "value", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+  };
+  for (const auto& f : objects_IndexEntry_fields) {
+    ::apache::thrift::metadata::ThriftField field;
+    field.id = std::get<0>(f);
+    field.name = std::get<1>(f);
+    field.is_optional = std::get<2>(f);
+    std::get<3>(f)->writeAndGenType(field.type, metadata);
+    objects_IndexEntry.fields.push_back(std::move(field));
+  }
+}
+void StructMetadata<::facebook::cachelib::navy::serialization::IndexBucket>::gen(ThriftMetadata& metadata) {
+  auto res = metadata.structs.emplace("objects.IndexBucket", ::apache::thrift::metadata::ThriftStruct{});
+  if (!res.second) {
+    return;
+  }
+  ::apache::thrift::metadata::ThriftStruct& objects_IndexBucket = res.first->second;
+  objects_IndexBucket.name = "objects.IndexBucket";
+  objects_IndexBucket.is_union = false;
+  static const std::tuple<int32_t, const char*, bool, std::unique_ptr<MetadataTypeInterface>>
+  objects_IndexBucket_fields[] = {
+    std::make_tuple(1, "bucketId", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(2, "entries", false, std::make_unique<List>(std::make_unique<Struct< ::facebook::cachelib::navy::serialization::IndexEntry>>("objects.IndexEntry"))),
+  };
+  for (const auto& f : objects_IndexBucket_fields) {
+    ::apache::thrift::metadata::ThriftField field;
+    field.id = std::get<0>(f);
+    field.name = std::get<1>(f);
+    field.is_optional = std::get<2>(f);
+    std::get<3>(f)->writeAndGenType(field.type, metadata);
+    objects_IndexBucket.fields.push_back(std::move(field));
+  }
+}
+void StructMetadata<::facebook::cachelib::navy::serialization::Region>::gen(ThriftMetadata& metadata) {
+  auto res = metadata.structs.emplace("objects.Region", ::apache::thrift::metadata::ThriftStruct{});
+  if (!res.second) {
+    return;
+  }
+  ::apache::thrift::metadata::ThriftStruct& objects_Region = res.first->second;
+  objects_Region.name = "objects.Region";
+  objects_Region.is_union = false;
+  static const std::tuple<int32_t, const char*, bool, std::unique_ptr<MetadataTypeInterface>>
+  objects_Region_fields[] = {
+    std::make_tuple(1, "regionId", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(2, "lastEntryEndOffset", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(3, "classId", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(4, "numItems", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(5, "pinned", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_BOOL_TYPE)),
+  };
+  for (const auto& f : objects_Region_fields) {
+    ::apache::thrift::metadata::ThriftField field;
+    field.id = std::get<0>(f);
+    field.name = std::get<1>(f);
+    field.is_optional = std::get<2>(f);
+    std::get<3>(f)->writeAndGenType(field.type, metadata);
+    objects_Region.fields.push_back(std::move(field));
+  }
+}
+void StructMetadata<::facebook::cachelib::navy::serialization::RegionData>::gen(ThriftMetadata& metadata) {
+  auto res = metadata.structs.emplace("objects.RegionData", ::apache::thrift::metadata::ThriftStruct{});
+  if (!res.second) {
+    return;
+  }
+  ::apache::thrift::metadata::ThriftStruct& objects_RegionData = res.first->second;
+  objects_RegionData.name = "objects.RegionData";
+  objects_RegionData.is_union = false;
+  static const std::tuple<int32_t, const char*, bool, std::unique_ptr<MetadataTypeInterface>>
+  objects_RegionData_fields[] = {
+    std::make_tuple(1, "regions", false, std::make_unique<List>(std::make_unique<Struct< ::facebook::cachelib::navy::serialization::Region>>("objects.Region"))),
+    std::make_tuple(2, "regionSize", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+  };
+  for (const auto& f : objects_RegionData_fields) {
+    ::apache::thrift::metadata::ThriftField field;
+    field.id = std::get<0>(f);
+    field.name = std::get<1>(f);
+    field.is_optional = std::get<2>(f);
+    std::get<3>(f)->writeAndGenType(field.type, metadata);
+    objects_RegionData.fields.push_back(std::move(field));
+  }
+}
+void StructMetadata<::facebook::cachelib::navy::serialization::AccessStats>::gen(ThriftMetadata& metadata) {
+  auto res = metadata.structs.emplace("objects.AccessStats", ::apache::thrift::metadata::ThriftStruct{});
+  if (!res.second) {
+    return;
+  }
+  ::apache::thrift::metadata::ThriftStruct& objects_AccessStats = res.first->second;
+  objects_AccessStats.name = "objects.AccessStats";
+  objects_AccessStats.is_union = false;
+  static const std::tuple<int32_t, const char*, bool, std::unique_ptr<MetadataTypeInterface>>
+  objects_AccessStats_fields[] = {
+    std::make_tuple(1, "totalHits", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_BYTE_TYPE)),
+    std::make_tuple(2, "currHits", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_BYTE_TYPE)),
+    std::make_tuple(3, "numReinsertions", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_BYTE_TYPE)),
+  };
+  for (const auto& f : objects_AccessStats_fields) {
+    ::apache::thrift::metadata::ThriftField field;
+    field.id = std::get<0>(f);
+    field.name = std::get<1>(f);
+    field.is_optional = std::get<2>(f);
+    std::get<3>(f)->writeAndGenType(field.type, metadata);
+    objects_AccessStats.fields.push_back(std::move(field));
+  }
+}
+void StructMetadata<::facebook::cachelib::navy::serialization::AccessTracker>::gen(ThriftMetadata& metadata) {
+  auto res = metadata.structs.emplace("objects.AccessTracker", ::apache::thrift::metadata::ThriftStruct{});
+  if (!res.second) {
+    return;
+  }
+  ::apache::thrift::metadata::ThriftStruct& objects_AccessTracker = res.first->second;
+  objects_AccessTracker.name = "objects.AccessTracker";
+  objects_AccessTracker.is_union = false;
+  static const std::tuple<int32_t, const char*, bool, std::unique_ptr<MetadataTypeInterface>>
+  objects_AccessTracker_fields[] = {
+    std::make_tuple(1, "data", false, std::make_unique<Map>(std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE), std::make_unique<Struct< ::facebook::cachelib::navy::serialization::AccessStats>>("objects.AccessStats"))),
+  };
+  for (const auto& f : objects_AccessTracker_fields) {
+    ::apache::thrift::metadata::ThriftField field;
+    field.id = std::get<0>(f);
+    field.name = std::get<1>(f);
+    field.is_optional = std::get<2>(f);
+    std::get<3>(f)->writeAndGenType(field.type, metadata);
+    objects_AccessTracker.fields.push_back(std::move(field));
+  }
+}
+void StructMetadata<::facebook::cachelib::navy::serialization::BlockCacheConfig>::gen(ThriftMetadata& metadata) {
+  auto res = metadata.structs.emplace("objects.BlockCacheConfig", ::apache::thrift::metadata::ThriftStruct{});
+  if (!res.second) {
+    return;
+  }
+  ::apache::thrift::metadata::ThriftStruct& objects_BlockCacheConfig = res.first->second;
+  objects_BlockCacheConfig.name = "objects.BlockCacheConfig";
+  objects_BlockCacheConfig.is_union = false;
+  static const std::tuple<int32_t, const char*, bool, std::unique_ptr<MetadataTypeInterface>>
+  objects_BlockCacheConfig_fields[] = {
+    std::make_tuple(1, "version", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(2, "cacheBaseOffset", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(3, "cacheSize", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(4, "blockSize", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(5, "sizeClasses", false, std::make_unique<Set>(std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE))),
+    std::make_tuple(6, "checksum", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_BOOL_TYPE)),
+    std::make_tuple(7, "sizeDist", false, std::make_unique<Map>(std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE), std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE))),
+    std::make_tuple(8, "holeCount", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(9, "holeSizeTotal", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(10, "reinsertionPolicyEnabled", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_BOOL_TYPE)),
+  };
+  for (const auto& f : objects_BlockCacheConfig_fields) {
+    ::apache::thrift::metadata::ThriftField field;
+    field.id = std::get<0>(f);
+    field.name = std::get<1>(f);
+    field.is_optional = std::get<2>(f);
+    std::get<3>(f)->writeAndGenType(field.type, metadata);
+    objects_BlockCacheConfig.fields.push_back(std::move(field));
+  }
+}
+void StructMetadata<::facebook::cachelib::navy::serialization::BigHashPersistentData>::gen(ThriftMetadata& metadata) {
+  auto res = metadata.structs.emplace("objects.BigHashPersistentData", ::apache::thrift::metadata::ThriftStruct{});
+  if (!res.second) {
+    return;
+  }
+  ::apache::thrift::metadata::ThriftStruct& objects_BigHashPersistentData = res.first->second;
+  objects_BigHashPersistentData.name = "objects.BigHashPersistentData";
+  objects_BigHashPersistentData.is_union = false;
+  static const std::tuple<int32_t, const char*, bool, std::unique_ptr<MetadataTypeInterface>>
+  objects_BigHashPersistentData_fields[] = {
+    std::make_tuple(1, "version", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(2, "generationTime", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(3, "itemCount", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(4, "bucketSize", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(5, "cacheBaseOffset", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(6, "numBuckets", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(7, "sizeDist", false, std::make_unique<Map>(std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE), std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE))),
+  };
+  for (const auto& f : objects_BigHashPersistentData_fields) {
+    ::apache::thrift::metadata::ThriftField field;
+    field.id = std::get<0>(f);
+    field.name = std::get<1>(f);
+    field.is_optional = std::get<2>(f);
+    std::get<3>(f)->writeAndGenType(field.type, metadata);
+    objects_BigHashPersistentData.fields.push_back(std::move(field));
+  }
+}
+void StructMetadata<::facebook::cachelib::navy::serialization::BloomFilterPersistentData>::gen(ThriftMetadata& metadata) {
+  auto res = metadata.structs.emplace("objects.BloomFilterPersistentData", ::apache::thrift::metadata::ThriftStruct{});
+  if (!res.second) {
+    return;
+  }
+  ::apache::thrift::metadata::ThriftStruct& objects_BloomFilterPersistentData = res.first->second;
+  objects_BloomFilterPersistentData.name = "objects.BloomFilterPersistentData";
+  objects_BloomFilterPersistentData.is_union = false;
+  static const std::tuple<int32_t, const char*, bool, std::unique_ptr<MetadataTypeInterface>>
+  objects_BloomFilterPersistentData_fields[] = {
+    std::make_tuple(1, "numFilters", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(2, "hashTableBitSize", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(3, "filterByteSize", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE)),
+    std::make_tuple(4, "fragmentSize", false, std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I32_TYPE)),
+    std::make_tuple(5, "seeds", false, std::make_unique<List>(std::make_unique<Primitive>(ThriftPrimitiveType::THRIFT_I64_TYPE))),
+  };
+  for (const auto& f : objects_BloomFilterPersistentData_fields) {
+    ::apache::thrift::metadata::ThriftField field;
+    field.id = std::get<0>(f);
+    field.name = std::get<1>(f);
+    field.is_optional = std::get<2>(f);
+    std::get<3>(f)->writeAndGenType(field.type, metadata);
+    objects_BloomFilterPersistentData.fields.push_back(std::move(field));
+  }
+}
+
+} // namespace md
+} // namespace detail
+} // namespace thrift
+} // namespace apache
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_metadata.h b/cachelib/navy/serialization/gen-cpp2/objects_metadata.h
new file mode 100644
index 0000000000..e8de2e1886
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_metadata.h
@@ -0,0 +1,69 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+#pragma once
+
+#include <thrift/lib/cpp2/gen/module_metadata_h.h>
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_types.h"
+
+
+namespace apache {
+namespace thrift {
+namespace detail {
+namespace md {
+using ThriftMetadata = ::apache::thrift::metadata::ThriftMetadata;
+using ThriftServiceContext = ::apache::thrift::metadata::ThriftServiceContext;
+using ThriftService = ::apache::thrift::metadata::ThriftService;
+
+template <>
+class StructMetadata<::facebook::cachelib::navy::serialization::IndexEntry> {
+ public:
+  static void gen(ThriftMetadata& metadata);
+};
+template <>
+class StructMetadata<::facebook::cachelib::navy::serialization::IndexBucket> {
+ public:
+  static void gen(ThriftMetadata& metadata);
+};
+template <>
+class StructMetadata<::facebook::cachelib::navy::serialization::Region> {
+ public:
+  static void gen(ThriftMetadata& metadata);
+};
+template <>
+class StructMetadata<::facebook::cachelib::navy::serialization::RegionData> {
+ public:
+  static void gen(ThriftMetadata& metadata);
+};
+template <>
+class StructMetadata<::facebook::cachelib::navy::serialization::AccessStats> {
+ public:
+  static void gen(ThriftMetadata& metadata);
+};
+template <>
+class StructMetadata<::facebook::cachelib::navy::serialization::AccessTracker> {
+ public:
+  static void gen(ThriftMetadata& metadata);
+};
+template <>
+class StructMetadata<::facebook::cachelib::navy::serialization::BlockCacheConfig> {
+ public:
+  static void gen(ThriftMetadata& metadata);
+};
+template <>
+class StructMetadata<::facebook::cachelib::navy::serialization::BigHashPersistentData> {
+ public:
+  static void gen(ThriftMetadata& metadata);
+};
+template <>
+class StructMetadata<::facebook::cachelib::navy::serialization::BloomFilterPersistentData> {
+ public:
+  static void gen(ThriftMetadata& metadata);
+};
+} // namespace md
+} // namespace detail
+} // namespace thrift
+} // namespace apache
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_types.cpp b/cachelib/navy/serialization/gen-cpp2/objects_types.cpp
new file mode 100644
index 0000000000..bed34a6af4
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_types.cpp
@@ -0,0 +1,1001 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_types.h"
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_types.tcc"
+
+#include <thrift/lib/cpp2/gen/module_types_cpp.h>
+
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_data.h"
+
+
+namespace apache {
+namespace thrift {
+namespace detail {
+
+void TccStructTraits<::facebook::cachelib::navy::serialization::IndexEntry>::translateFieldName(
+    FOLLY_MAYBE_UNUSED folly::StringPiece _fname,
+    FOLLY_MAYBE_UNUSED int16_t& fid,
+    FOLLY_MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) {
+  if (false) {}
+  else if (_fname == "key") {
+    fid = 1;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "value") {
+    fid = 2;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+}
+void TccStructTraits<::facebook::cachelib::navy::serialization::IndexBucket>::translateFieldName(
+    FOLLY_MAYBE_UNUSED folly::StringPiece _fname,
+    FOLLY_MAYBE_UNUSED int16_t& fid,
+    FOLLY_MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) {
+  if (false) {}
+  else if (_fname == "bucketId") {
+    fid = 1;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "entries") {
+    fid = 2;
+    _ftype = apache::thrift::protocol::T_LIST;
+  }
+}
+void TccStructTraits<::facebook::cachelib::navy::serialization::Region>::translateFieldName(
+    FOLLY_MAYBE_UNUSED folly::StringPiece _fname,
+    FOLLY_MAYBE_UNUSED int16_t& fid,
+    FOLLY_MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) {
+  if (false) {}
+  else if (_fname == "regionId") {
+    fid = 1;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "lastEntryEndOffset") {
+    fid = 2;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "classId") {
+    fid = 3;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "numItems") {
+    fid = 4;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "pinned") {
+    fid = 5;
+    _ftype = apache::thrift::protocol::T_BOOL;
+  }
+}
+void TccStructTraits<::facebook::cachelib::navy::serialization::RegionData>::translateFieldName(
+    FOLLY_MAYBE_UNUSED folly::StringPiece _fname,
+    FOLLY_MAYBE_UNUSED int16_t& fid,
+    FOLLY_MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) {
+  if (false) {}
+  else if (_fname == "regions") {
+    fid = 1;
+    _ftype = apache::thrift::protocol::T_LIST;
+  }
+  else if (_fname == "regionSize") {
+    fid = 2;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+}
+void TccStructTraits<::facebook::cachelib::navy::serialization::AccessStats>::translateFieldName(
+    FOLLY_MAYBE_UNUSED folly::StringPiece _fname,
+    FOLLY_MAYBE_UNUSED int16_t& fid,
+    FOLLY_MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) {
+  if (false) {}
+  else if (_fname == "totalHits") {
+    fid = 1;
+    _ftype = apache::thrift::protocol::T_BYTE;
+  }
+  else if (_fname == "currHits") {
+    fid = 2;
+    _ftype = apache::thrift::protocol::T_BYTE;
+  }
+  else if (_fname == "numReinsertions") {
+    fid = 3;
+    _ftype = apache::thrift::protocol::T_BYTE;
+  }
+}
+void TccStructTraits<::facebook::cachelib::navy::serialization::AccessTracker>::translateFieldName(
+    FOLLY_MAYBE_UNUSED folly::StringPiece _fname,
+    FOLLY_MAYBE_UNUSED int16_t& fid,
+    FOLLY_MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) {
+  if (false) {}
+  else if (_fname == "data") {
+    fid = 1;
+    _ftype = apache::thrift::protocol::T_MAP;
+  }
+}
+void TccStructTraits<::facebook::cachelib::navy::serialization::BlockCacheConfig>::translateFieldName(
+    FOLLY_MAYBE_UNUSED folly::StringPiece _fname,
+    FOLLY_MAYBE_UNUSED int16_t& fid,
+    FOLLY_MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) {
+  if (false) {}
+  else if (_fname == "version") {
+    fid = 1;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "cacheBaseOffset") {
+    fid = 2;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "cacheSize") {
+    fid = 3;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "blockSize") {
+    fid = 4;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "sizeClasses") {
+    fid = 5;
+    _ftype = apache::thrift::protocol::T_SET;
+  }
+  else if (_fname == "checksum") {
+    fid = 6;
+    _ftype = apache::thrift::protocol::T_BOOL;
+  }
+  else if (_fname == "sizeDist") {
+    fid = 7;
+    _ftype = apache::thrift::protocol::T_MAP;
+  }
+  else if (_fname == "holeCount") {
+    fid = 8;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "holeSizeTotal") {
+    fid = 9;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "reinsertionPolicyEnabled") {
+    fid = 10;
+    _ftype = apache::thrift::protocol::T_BOOL;
+  }
+}
+void TccStructTraits<::facebook::cachelib::navy::serialization::BigHashPersistentData>::translateFieldName(
+    FOLLY_MAYBE_UNUSED folly::StringPiece _fname,
+    FOLLY_MAYBE_UNUSED int16_t& fid,
+    FOLLY_MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) {
+  if (false) {}
+  else if (_fname == "version") {
+    fid = 1;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "generationTime") {
+    fid = 2;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "itemCount") {
+    fid = 3;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "bucketSize") {
+    fid = 4;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "cacheBaseOffset") {
+    fid = 5;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "numBuckets") {
+    fid = 6;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "sizeDist") {
+    fid = 7;
+    _ftype = apache::thrift::protocol::T_MAP;
+  }
+}
+void TccStructTraits<::facebook::cachelib::navy::serialization::BloomFilterPersistentData>::translateFieldName(
+    FOLLY_MAYBE_UNUSED folly::StringPiece _fname,
+    FOLLY_MAYBE_UNUSED int16_t& fid,
+    FOLLY_MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) {
+  if (false) {}
+  else if (_fname == "numFilters") {
+    fid = 1;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "hashTableBitSize") {
+    fid = 2;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "filterByteSize") {
+    fid = 3;
+    _ftype = apache::thrift::protocol::T_I64;
+  }
+  else if (_fname == "fragmentSize") {
+    fid = 4;
+    _ftype = apache::thrift::protocol::T_I32;
+  }
+  else if (_fname == "seeds") {
+    fid = 5;
+    _ftype = apache::thrift::protocol::T_LIST;
+  }
+}
+
+} // namespace detail
+} // namespace thrift
+} // namespace apache
+
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+IndexEntry::IndexEntry(apache::thrift::FragileConstructor, int32_t key__arg, int32_t value__arg) :
+    key(std::move(key__arg)),
+    value(std::move(value__arg)) {}
+
+void IndexEntry::__clear() {
+  // clear all fields
+  key = 0;
+  value = 0;
+}
+
+bool IndexEntry::operator==(const IndexEntry& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.key == rhs.key)) {
+    return false;
+  }
+  if (!(lhs.value == rhs.value)) {
+    return false;
+  }
+  return true;
+}
+
+bool IndexEntry::operator<(const IndexEntry& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.key == rhs.key)) {
+    return lhs.key < rhs.key;
+  }
+  if (!(lhs.value == rhs.value)) {
+    return lhs.value < rhs.value;
+  }
+  return false;
+}
+
+
+void swap(IndexEntry& a, IndexEntry& b) {
+  using ::std::swap;
+  swap(a.key, b.key);
+  swap(a.value, b.value);
+}
+
+template void IndexEntry::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+template uint32_t IndexEntry::write<>(apache::thrift::BinaryProtocolWriter*) const;
+template uint32_t IndexEntry::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+template uint32_t IndexEntry::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+template void IndexEntry::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+template uint32_t IndexEntry::write<>(apache::thrift::CompactProtocolWriter*) const;
+template uint32_t IndexEntry::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+template uint32_t IndexEntry::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+IndexBucket::IndexBucket(apache::thrift::FragileConstructor, int32_t bucketId__arg, ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry> entries__arg) :
+    bucketId(std::move(bucketId__arg)),
+    entries(std::move(entries__arg)) {}
+
+void IndexBucket::__clear() {
+  // clear all fields
+  bucketId = 0;
+  entries.clear();
+}
+
+bool IndexBucket::operator==(const IndexBucket& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.bucketId == rhs.bucketId)) {
+    return false;
+  }
+  if (!(lhs.entries == rhs.entries)) {
+    return false;
+  }
+  return true;
+}
+
+bool IndexBucket::operator<(const IndexBucket& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.bucketId == rhs.bucketId)) {
+    return lhs.bucketId < rhs.bucketId;
+  }
+  if (!(lhs.entries == rhs.entries)) {
+    return lhs.entries < rhs.entries;
+  }
+  return false;
+}
+
+const ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry>& IndexBucket::get_entries() const& {
+  return entries;
+}
+
+::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry> IndexBucket::get_entries() && {
+  return std::move(entries);
+}
+
+
+void swap(IndexBucket& a, IndexBucket& b) {
+  using ::std::swap;
+  swap(a.bucketId, b.bucketId);
+  swap(a.entries, b.entries);
+}
+
+template void IndexBucket::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+template uint32_t IndexBucket::write<>(apache::thrift::BinaryProtocolWriter*) const;
+template uint32_t IndexBucket::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+template uint32_t IndexBucket::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+template void IndexBucket::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+template uint32_t IndexBucket::write<>(apache::thrift::CompactProtocolWriter*) const;
+template uint32_t IndexBucket::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+template uint32_t IndexBucket::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+Region::Region(apache::thrift::FragileConstructor, int32_t regionId__arg, int32_t lastEntryEndOffset__arg, int32_t classId__arg, int32_t numItems__arg, bool pinned__arg) :
+    regionId(std::move(regionId__arg)),
+    lastEntryEndOffset(std::move(lastEntryEndOffset__arg)),
+    classId(std::move(classId__arg)),
+    numItems(std::move(numItems__arg)),
+    pinned(std::move(pinned__arg)) {}
+
+void Region::__clear() {
+  // clear all fields
+  regionId = 0;
+  lastEntryEndOffset = 0;
+  classId = 0;
+  numItems = 0;
+  pinned = false;
+}
+
+bool Region::operator==(const Region& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.regionId == rhs.regionId)) {
+    return false;
+  }
+  if (!(lhs.lastEntryEndOffset == rhs.lastEntryEndOffset)) {
+    return false;
+  }
+  if (!(lhs.classId == rhs.classId)) {
+    return false;
+  }
+  if (!(lhs.numItems == rhs.numItems)) {
+    return false;
+  }
+  if (!(lhs.pinned == rhs.pinned)) {
+    return false;
+  }
+  return true;
+}
+
+bool Region::operator<(const Region& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.regionId == rhs.regionId)) {
+    return lhs.regionId < rhs.regionId;
+  }
+  if (!(lhs.lastEntryEndOffset == rhs.lastEntryEndOffset)) {
+    return lhs.lastEntryEndOffset < rhs.lastEntryEndOffset;
+  }
+  if (!(lhs.classId == rhs.classId)) {
+    return lhs.classId < rhs.classId;
+  }
+  if (!(lhs.numItems == rhs.numItems)) {
+    return lhs.numItems < rhs.numItems;
+  }
+  if (!(lhs.pinned == rhs.pinned)) {
+    return lhs.pinned < rhs.pinned;
+  }
+  return false;
+}
+
+
+void swap(Region& a, Region& b) {
+  using ::std::swap;
+  swap(a.regionId, b.regionId);
+  swap(a.lastEntryEndOffset, b.lastEntryEndOffset);
+  swap(a.classId, b.classId);
+  swap(a.numItems, b.numItems);
+  swap(a.pinned, b.pinned);
+}
+
+template void Region::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+template uint32_t Region::write<>(apache::thrift::BinaryProtocolWriter*) const;
+template uint32_t Region::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+template uint32_t Region::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+template void Region::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+template uint32_t Region::write<>(apache::thrift::CompactProtocolWriter*) const;
+template uint32_t Region::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+template uint32_t Region::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+RegionData::RegionData(apache::thrift::FragileConstructor, ::std::vector< ::facebook::cachelib::navy::serialization::Region> regions__arg, int32_t regionSize__arg) :
+    regions(std::move(regions__arg)),
+    regionSize(std::move(regionSize__arg)) {}
+
+void RegionData::__clear() {
+  // clear all fields
+  regions.clear();
+  regionSize = 0;
+}
+
+bool RegionData::operator==(const RegionData& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.regions == rhs.regions)) {
+    return false;
+  }
+  if (!(lhs.regionSize == rhs.regionSize)) {
+    return false;
+  }
+  return true;
+}
+
+bool RegionData::operator<(const RegionData& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.regions == rhs.regions)) {
+    return lhs.regions < rhs.regions;
+  }
+  if (!(lhs.regionSize == rhs.regionSize)) {
+    return lhs.regionSize < rhs.regionSize;
+  }
+  return false;
+}
+
+const ::std::vector< ::facebook::cachelib::navy::serialization::Region>& RegionData::get_regions() const& {
+  return regions;
+}
+
+::std::vector< ::facebook::cachelib::navy::serialization::Region> RegionData::get_regions() && {
+  return std::move(regions);
+}
+
+
+void swap(RegionData& a, RegionData& b) {
+  using ::std::swap;
+  swap(a.regions, b.regions);
+  swap(a.regionSize, b.regionSize);
+}
+
+template void RegionData::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+template uint32_t RegionData::write<>(apache::thrift::BinaryProtocolWriter*) const;
+template uint32_t RegionData::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+template uint32_t RegionData::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+template void RegionData::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+template uint32_t RegionData::write<>(apache::thrift::CompactProtocolWriter*) const;
+template uint32_t RegionData::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+template uint32_t RegionData::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+AccessStats::AccessStats(apache::thrift::FragileConstructor, int8_t totalHits__arg, int8_t currHits__arg, int8_t numReinsertions__arg) :
+    totalHits(std::move(totalHits__arg)),
+    currHits(std::move(currHits__arg)),
+    numReinsertions(std::move(numReinsertions__arg)) {
+  __isset.totalHits = true;
+  __isset.currHits = true;
+  __isset.numReinsertions = true;
+}
+
+void AccessStats::__clear() {
+  // clear all fields
+  totalHits = static_cast<int8_t>(0);
+  currHits = static_cast<int8_t>(0);
+  numReinsertions = static_cast<int8_t>(0);
+  __isset = {};
+}
+
+bool AccessStats::operator==(const AccessStats& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.totalHits == rhs.totalHits)) {
+    return false;
+  }
+  if (!(lhs.currHits == rhs.currHits)) {
+    return false;
+  }
+  if (!(lhs.numReinsertions == rhs.numReinsertions)) {
+    return false;
+  }
+  return true;
+}
+
+bool AccessStats::operator<(const AccessStats& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.totalHits == rhs.totalHits)) {
+    return lhs.totalHits < rhs.totalHits;
+  }
+  if (!(lhs.currHits == rhs.currHits)) {
+    return lhs.currHits < rhs.currHits;
+  }
+  if (!(lhs.numReinsertions == rhs.numReinsertions)) {
+    return lhs.numReinsertions < rhs.numReinsertions;
+  }
+  return false;
+}
+
+
+void swap(AccessStats& a, AccessStats& b) {
+  using ::std::swap;
+  swap(a.totalHits, b.totalHits);
+  swap(a.currHits, b.currHits);
+  swap(a.numReinsertions, b.numReinsertions);
+  swap(a.__isset, b.__isset);
+}
+
+template void AccessStats::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+template uint32_t AccessStats::write<>(apache::thrift::BinaryProtocolWriter*) const;
+template uint32_t AccessStats::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+template uint32_t AccessStats::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+template void AccessStats::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+template uint32_t AccessStats::write<>(apache::thrift::CompactProtocolWriter*) const;
+template uint32_t AccessStats::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+template uint32_t AccessStats::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+AccessTracker::AccessTracker(apache::thrift::FragileConstructor, ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats> data__arg) :
+    data(std::move(data__arg)) {
+  __isset.data = true;
+}
+
+void AccessTracker::__clear() {
+  // clear all fields
+  data.clear();
+  __isset = {};
+}
+
+bool AccessTracker::operator==(const AccessTracker& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.data == rhs.data)) {
+    return false;
+  }
+  return true;
+}
+
+bool AccessTracker::operator<(const AccessTracker& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.data == rhs.data)) {
+    return lhs.data < rhs.data;
+  }
+  return false;
+}
+
+const ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>& AccessTracker::get_data() const& {
+  return data;
+}
+
+::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats> AccessTracker::get_data() && {
+  return std::move(data);
+}
+
+
+void swap(AccessTracker& a, AccessTracker& b) {
+  using ::std::swap;
+  swap(a.data, b.data);
+  swap(a.__isset, b.__isset);
+}
+
+template void AccessTracker::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+template uint32_t AccessTracker::write<>(apache::thrift::BinaryProtocolWriter*) const;
+template uint32_t AccessTracker::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+template uint32_t AccessTracker::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+template void AccessTracker::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+template uint32_t AccessTracker::write<>(apache::thrift::CompactProtocolWriter*) const;
+template uint32_t AccessTracker::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+template uint32_t AccessTracker::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+BlockCacheConfig::BlockCacheConfig() :
+      version(0LL),
+      cacheBaseOffset(0LL),
+      cacheSize(0LL),
+      blockSize(0),
+      checksum(false),
+      holeCount(0LL),
+      holeSizeTotal(0LL),
+      reinsertionPolicyEnabled(false) {}
+
+
+BlockCacheConfig::~BlockCacheConfig() {}
+
+BlockCacheConfig::BlockCacheConfig(apache::thrift::FragileConstructor, int64_t version__arg, int64_t cacheBaseOffset__arg, int64_t cacheSize__arg, int32_t blockSize__arg, ::std::set<int32_t> sizeClasses__arg, bool checksum__arg, ::std::map<int64_t, int64_t> sizeDist__arg, int64_t holeCount__arg, int64_t holeSizeTotal__arg, bool reinsertionPolicyEnabled__arg) :
+    version(std::move(version__arg)),
+    cacheBaseOffset(std::move(cacheBaseOffset__arg)),
+    cacheSize(std::move(cacheSize__arg)),
+    blockSize(std::move(blockSize__arg)),
+    sizeClasses(std::move(sizeClasses__arg)),
+    checksum(std::move(checksum__arg)),
+    sizeDist(std::move(sizeDist__arg)),
+    holeCount(std::move(holeCount__arg)),
+    holeSizeTotal(std::move(holeSizeTotal__arg)),
+    reinsertionPolicyEnabled(std::move(reinsertionPolicyEnabled__arg)) {
+  __isset.sizeDist = true;
+  __isset.holeCount = true;
+  __isset.holeSizeTotal = true;
+  __isset.reinsertionPolicyEnabled = true;
+}
+
+void BlockCacheConfig::__clear() {
+  // clear all fields
+  version = 0LL;
+  cacheBaseOffset = 0LL;
+  cacheSize = 0LL;
+  blockSize = 0;
+  sizeClasses.clear();
+  checksum = false;
+  sizeDist.clear();
+  holeCount = 0LL;
+  holeSizeTotal = 0LL;
+  reinsertionPolicyEnabled = false;
+  __isset = {};
+}
+
+bool BlockCacheConfig::operator==(const BlockCacheConfig& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.version == rhs.version)) {
+    return false;
+  }
+  if (!(lhs.cacheBaseOffset == rhs.cacheBaseOffset)) {
+    return false;
+  }
+  if (!(lhs.cacheSize == rhs.cacheSize)) {
+    return false;
+  }
+  if (!(lhs.blockSize == rhs.blockSize)) {
+    return false;
+  }
+  if (!(lhs.sizeClasses == rhs.sizeClasses)) {
+    return false;
+  }
+  if (!(lhs.checksum == rhs.checksum)) {
+    return false;
+  }
+  if (!(lhs.sizeDist == rhs.sizeDist)) {
+    return false;
+  }
+  if (!(lhs.holeCount == rhs.holeCount)) {
+    return false;
+  }
+  if (!(lhs.holeSizeTotal == rhs.holeSizeTotal)) {
+    return false;
+  }
+  if (!(lhs.reinsertionPolicyEnabled == rhs.reinsertionPolicyEnabled)) {
+    return false;
+  }
+  return true;
+}
+
+bool BlockCacheConfig::operator<(const BlockCacheConfig& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.version == rhs.version)) {
+    return lhs.version < rhs.version;
+  }
+  if (!(lhs.cacheBaseOffset == rhs.cacheBaseOffset)) {
+    return lhs.cacheBaseOffset < rhs.cacheBaseOffset;
+  }
+  if (!(lhs.cacheSize == rhs.cacheSize)) {
+    return lhs.cacheSize < rhs.cacheSize;
+  }
+  if (!(lhs.blockSize == rhs.blockSize)) {
+    return lhs.blockSize < rhs.blockSize;
+  }
+  if (!(lhs.sizeClasses == rhs.sizeClasses)) {
+    return lhs.sizeClasses < rhs.sizeClasses;
+  }
+  if (!(lhs.checksum == rhs.checksum)) {
+    return lhs.checksum < rhs.checksum;
+  }
+  if (!(lhs.sizeDist == rhs.sizeDist)) {
+    return lhs.sizeDist < rhs.sizeDist;
+  }
+  if (!(lhs.holeCount == rhs.holeCount)) {
+    return lhs.holeCount < rhs.holeCount;
+  }
+  if (!(lhs.holeSizeTotal == rhs.holeSizeTotal)) {
+    return lhs.holeSizeTotal < rhs.holeSizeTotal;
+  }
+  if (!(lhs.reinsertionPolicyEnabled == rhs.reinsertionPolicyEnabled)) {
+    return lhs.reinsertionPolicyEnabled < rhs.reinsertionPolicyEnabled;
+  }
+  return false;
+}
+
+const ::std::set<int32_t>& BlockCacheConfig::get_sizeClasses() const& {
+  return sizeClasses;
+}
+
+::std::set<int32_t> BlockCacheConfig::get_sizeClasses() && {
+  return std::move(sizeClasses);
+}
+
+const ::std::map<int64_t, int64_t>& BlockCacheConfig::get_sizeDist() const& {
+  return sizeDist;
+}
+
+::std::map<int64_t, int64_t> BlockCacheConfig::get_sizeDist() && {
+  return std::move(sizeDist);
+}
+
+
+void swap(BlockCacheConfig& a, BlockCacheConfig& b) {
+  using ::std::swap;
+  swap(a.version, b.version);
+  swap(a.cacheBaseOffset, b.cacheBaseOffset);
+  swap(a.cacheSize, b.cacheSize);
+  swap(a.blockSize, b.blockSize);
+  swap(a.sizeClasses, b.sizeClasses);
+  swap(a.checksum, b.checksum);
+  swap(a.sizeDist, b.sizeDist);
+  swap(a.holeCount, b.holeCount);
+  swap(a.holeSizeTotal, b.holeSizeTotal);
+  swap(a.reinsertionPolicyEnabled, b.reinsertionPolicyEnabled);
+  swap(a.__isset, b.__isset);
+}
+
+template void BlockCacheConfig::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+template uint32_t BlockCacheConfig::write<>(apache::thrift::BinaryProtocolWriter*) const;
+template uint32_t BlockCacheConfig::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+template uint32_t BlockCacheConfig::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+template void BlockCacheConfig::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+template uint32_t BlockCacheConfig::write<>(apache::thrift::CompactProtocolWriter*) const;
+template uint32_t BlockCacheConfig::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+template uint32_t BlockCacheConfig::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+BigHashPersistentData::BigHashPersistentData() :
+      version(0),
+      generationTime(0LL),
+      itemCount(0LL),
+      bucketSize(0LL),
+      cacheBaseOffset(0LL),
+      numBuckets(0LL) {}
+
+
+BigHashPersistentData::~BigHashPersistentData() {}
+
+BigHashPersistentData::BigHashPersistentData(apache::thrift::FragileConstructor, int32_t version__arg, int64_t generationTime__arg, int64_t itemCount__arg, int64_t bucketSize__arg, int64_t cacheBaseOffset__arg, int64_t numBuckets__arg, ::std::map<int64_t, int64_t> sizeDist__arg) :
+    version(std::move(version__arg)),
+    generationTime(std::move(generationTime__arg)),
+    itemCount(std::move(itemCount__arg)),
+    bucketSize(std::move(bucketSize__arg)),
+    cacheBaseOffset(std::move(cacheBaseOffset__arg)),
+    numBuckets(std::move(numBuckets__arg)),
+    sizeDist(std::move(sizeDist__arg)) {
+  __isset.sizeDist = true;
+}
+
+void BigHashPersistentData::__clear() {
+  // clear all fields
+  version = 0;
+  generationTime = 0LL;
+  itemCount = 0LL;
+  bucketSize = 0LL;
+  cacheBaseOffset = 0LL;
+  numBuckets = 0LL;
+  sizeDist.clear();
+  __isset = {};
+}
+
+bool BigHashPersistentData::operator==(const BigHashPersistentData& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.version == rhs.version)) {
+    return false;
+  }
+  if (!(lhs.generationTime == rhs.generationTime)) {
+    return false;
+  }
+  if (!(lhs.itemCount == rhs.itemCount)) {
+    return false;
+  }
+  if (!(lhs.bucketSize == rhs.bucketSize)) {
+    return false;
+  }
+  if (!(lhs.cacheBaseOffset == rhs.cacheBaseOffset)) {
+    return false;
+  }
+  if (!(lhs.numBuckets == rhs.numBuckets)) {
+    return false;
+  }
+  if (!(lhs.sizeDist == rhs.sizeDist)) {
+    return false;
+  }
+  return true;
+}
+
+bool BigHashPersistentData::operator<(const BigHashPersistentData& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.version == rhs.version)) {
+    return lhs.version < rhs.version;
+  }
+  if (!(lhs.generationTime == rhs.generationTime)) {
+    return lhs.generationTime < rhs.generationTime;
+  }
+  if (!(lhs.itemCount == rhs.itemCount)) {
+    return lhs.itemCount < rhs.itemCount;
+  }
+  if (!(lhs.bucketSize == rhs.bucketSize)) {
+    return lhs.bucketSize < rhs.bucketSize;
+  }
+  if (!(lhs.cacheBaseOffset == rhs.cacheBaseOffset)) {
+    return lhs.cacheBaseOffset < rhs.cacheBaseOffset;
+  }
+  if (!(lhs.numBuckets == rhs.numBuckets)) {
+    return lhs.numBuckets < rhs.numBuckets;
+  }
+  if (!(lhs.sizeDist == rhs.sizeDist)) {
+    return lhs.sizeDist < rhs.sizeDist;
+  }
+  return false;
+}
+
+const ::std::map<int64_t, int64_t>& BigHashPersistentData::get_sizeDist() const& {
+  return sizeDist;
+}
+
+::std::map<int64_t, int64_t> BigHashPersistentData::get_sizeDist() && {
+  return std::move(sizeDist);
+}
+
+
+void swap(BigHashPersistentData& a, BigHashPersistentData& b) {
+  using ::std::swap;
+  swap(a.version, b.version);
+  swap(a.generationTime, b.generationTime);
+  swap(a.itemCount, b.itemCount);
+  swap(a.bucketSize, b.bucketSize);
+  swap(a.cacheBaseOffset, b.cacheBaseOffset);
+  swap(a.numBuckets, b.numBuckets);
+  swap(a.sizeDist, b.sizeDist);
+  swap(a.__isset, b.__isset);
+}
+
+template void BigHashPersistentData::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+template uint32_t BigHashPersistentData::write<>(apache::thrift::BinaryProtocolWriter*) const;
+template uint32_t BigHashPersistentData::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+template uint32_t BigHashPersistentData::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+template void BigHashPersistentData::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+template uint32_t BigHashPersistentData::write<>(apache::thrift::CompactProtocolWriter*) const;
+template uint32_t BigHashPersistentData::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+template uint32_t BigHashPersistentData::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+BloomFilterPersistentData::BloomFilterPersistentData() :
+      numFilters(0),
+      hashTableBitSize(0LL),
+      filterByteSize(0LL),
+      fragmentSize(0) {}
+
+
+BloomFilterPersistentData::~BloomFilterPersistentData() {}
+
+BloomFilterPersistentData::BloomFilterPersistentData(apache::thrift::FragileConstructor, int32_t numFilters__arg, int64_t hashTableBitSize__arg, int64_t filterByteSize__arg, int32_t fragmentSize__arg, ::std::vector<int64_t> seeds__arg) :
+    numFilters(std::move(numFilters__arg)),
+    hashTableBitSize(std::move(hashTableBitSize__arg)),
+    filterByteSize(std::move(filterByteSize__arg)),
+    fragmentSize(std::move(fragmentSize__arg)),
+    seeds(std::move(seeds__arg)) {}
+
+void BloomFilterPersistentData::__clear() {
+  // clear all fields
+  numFilters = 0;
+  hashTableBitSize = 0LL;
+  filterByteSize = 0LL;
+  fragmentSize = 0;
+  seeds.clear();
+}
+
+bool BloomFilterPersistentData::operator==(const BloomFilterPersistentData& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.numFilters == rhs.numFilters)) {
+    return false;
+  }
+  if (!(lhs.hashTableBitSize == rhs.hashTableBitSize)) {
+    return false;
+  }
+  if (!(lhs.filterByteSize == rhs.filterByteSize)) {
+    return false;
+  }
+  if (!(lhs.fragmentSize == rhs.fragmentSize)) {
+    return false;
+  }
+  if (!(lhs.seeds == rhs.seeds)) {
+    return false;
+  }
+  return true;
+}
+
+bool BloomFilterPersistentData::operator<(const BloomFilterPersistentData& rhs) const {
+  (void)rhs;
+  auto& lhs = *this;
+  (void)lhs;
+  if (!(lhs.numFilters == rhs.numFilters)) {
+    return lhs.numFilters < rhs.numFilters;
+  }
+  if (!(lhs.hashTableBitSize == rhs.hashTableBitSize)) {
+    return lhs.hashTableBitSize < rhs.hashTableBitSize;
+  }
+  if (!(lhs.filterByteSize == rhs.filterByteSize)) {
+    return lhs.filterByteSize < rhs.filterByteSize;
+  }
+  if (!(lhs.fragmentSize == rhs.fragmentSize)) {
+    return lhs.fragmentSize < rhs.fragmentSize;
+  }
+  if (!(lhs.seeds == rhs.seeds)) {
+    return lhs.seeds < rhs.seeds;
+  }
+  return false;
+}
+
+const ::std::vector<int64_t>& BloomFilterPersistentData::get_seeds() const& {
+  return seeds;
+}
+
+::std::vector<int64_t> BloomFilterPersistentData::get_seeds() && {
+  return std::move(seeds);
+}
+
+
+void swap(BloomFilterPersistentData& a, BloomFilterPersistentData& b) {
+  using ::std::swap;
+  swap(a.numFilters, b.numFilters);
+  swap(a.hashTableBitSize, b.hashTableBitSize);
+  swap(a.filterByteSize, b.filterByteSize);
+  swap(a.fragmentSize, b.fragmentSize);
+  swap(a.seeds, b.seeds);
+}
+
+template void BloomFilterPersistentData::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+template uint32_t BloomFilterPersistentData::write<>(apache::thrift::BinaryProtocolWriter*) const;
+template uint32_t BloomFilterPersistentData::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+template uint32_t BloomFilterPersistentData::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+template void BloomFilterPersistentData::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+template uint32_t BloomFilterPersistentData::write<>(apache::thrift::CompactProtocolWriter*) const;
+template uint32_t BloomFilterPersistentData::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+template uint32_t BloomFilterPersistentData::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_types.h b/cachelib/navy/serialization/gen-cpp2/objects_types.h
new file mode 100644
index 0000000000..ed96d990db
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_types.h
@@ -0,0 +1,1292 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+#pragma once
+
+#include <thrift/lib/cpp2/gen/module_types_h.h>
+
+
+
+namespace apache {
+namespace thrift {
+namespace tag {
+struct key;
+struct value;
+struct bucketId;
+struct entries;
+struct regionId;
+struct lastEntryEndOffset;
+struct classId;
+struct numItems;
+struct pinned;
+struct regions;
+struct regionSize;
+struct totalHits;
+struct currHits;
+struct numReinsertions;
+struct data;
+struct version;
+struct cacheBaseOffset;
+struct cacheSize;
+struct blockSize;
+struct sizeClasses;
+struct checksum;
+struct sizeDist;
+struct holeCount;
+struct holeSizeTotal;
+struct reinsertionPolicyEnabled;
+struct version;
+struct generationTime;
+struct itemCount;
+struct bucketSize;
+struct cacheBaseOffset;
+struct numBuckets;
+struct sizeDist;
+struct numFilters;
+struct hashTableBitSize;
+struct filterByteSize;
+struct fragmentSize;
+struct seeds;
+} // namespace tag
+namespace detail {
+#ifndef APACHE_THRIFT_ACCESSOR_key
+#define APACHE_THRIFT_ACCESSOR_key
+APACHE_THRIFT_DEFINE_ACCESSOR(key);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_value
+#define APACHE_THRIFT_ACCESSOR_value
+APACHE_THRIFT_DEFINE_ACCESSOR(value);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_bucketId
+#define APACHE_THRIFT_ACCESSOR_bucketId
+APACHE_THRIFT_DEFINE_ACCESSOR(bucketId);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_entries
+#define APACHE_THRIFT_ACCESSOR_entries
+APACHE_THRIFT_DEFINE_ACCESSOR(entries);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_regionId
+#define APACHE_THRIFT_ACCESSOR_regionId
+APACHE_THRIFT_DEFINE_ACCESSOR(regionId);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_lastEntryEndOffset
+#define APACHE_THRIFT_ACCESSOR_lastEntryEndOffset
+APACHE_THRIFT_DEFINE_ACCESSOR(lastEntryEndOffset);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_classId
+#define APACHE_THRIFT_ACCESSOR_classId
+APACHE_THRIFT_DEFINE_ACCESSOR(classId);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_numItems
+#define APACHE_THRIFT_ACCESSOR_numItems
+APACHE_THRIFT_DEFINE_ACCESSOR(numItems);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_pinned
+#define APACHE_THRIFT_ACCESSOR_pinned
+APACHE_THRIFT_DEFINE_ACCESSOR(pinned);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_regions
+#define APACHE_THRIFT_ACCESSOR_regions
+APACHE_THRIFT_DEFINE_ACCESSOR(regions);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_regionSize
+#define APACHE_THRIFT_ACCESSOR_regionSize
+APACHE_THRIFT_DEFINE_ACCESSOR(regionSize);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_totalHits
+#define APACHE_THRIFT_ACCESSOR_totalHits
+APACHE_THRIFT_DEFINE_ACCESSOR(totalHits);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_currHits
+#define APACHE_THRIFT_ACCESSOR_currHits
+APACHE_THRIFT_DEFINE_ACCESSOR(currHits);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_numReinsertions
+#define APACHE_THRIFT_ACCESSOR_numReinsertions
+APACHE_THRIFT_DEFINE_ACCESSOR(numReinsertions);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_data
+#define APACHE_THRIFT_ACCESSOR_data
+APACHE_THRIFT_DEFINE_ACCESSOR(data);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_version
+#define APACHE_THRIFT_ACCESSOR_version
+APACHE_THRIFT_DEFINE_ACCESSOR(version);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_cacheBaseOffset
+#define APACHE_THRIFT_ACCESSOR_cacheBaseOffset
+APACHE_THRIFT_DEFINE_ACCESSOR(cacheBaseOffset);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_cacheSize
+#define APACHE_THRIFT_ACCESSOR_cacheSize
+APACHE_THRIFT_DEFINE_ACCESSOR(cacheSize);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_blockSize
+#define APACHE_THRIFT_ACCESSOR_blockSize
+APACHE_THRIFT_DEFINE_ACCESSOR(blockSize);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_sizeClasses
+#define APACHE_THRIFT_ACCESSOR_sizeClasses
+APACHE_THRIFT_DEFINE_ACCESSOR(sizeClasses);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_checksum
+#define APACHE_THRIFT_ACCESSOR_checksum
+APACHE_THRIFT_DEFINE_ACCESSOR(checksum);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_sizeDist
+#define APACHE_THRIFT_ACCESSOR_sizeDist
+APACHE_THRIFT_DEFINE_ACCESSOR(sizeDist);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_holeCount
+#define APACHE_THRIFT_ACCESSOR_holeCount
+APACHE_THRIFT_DEFINE_ACCESSOR(holeCount);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_holeSizeTotal
+#define APACHE_THRIFT_ACCESSOR_holeSizeTotal
+APACHE_THRIFT_DEFINE_ACCESSOR(holeSizeTotal);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_reinsertionPolicyEnabled
+#define APACHE_THRIFT_ACCESSOR_reinsertionPolicyEnabled
+APACHE_THRIFT_DEFINE_ACCESSOR(reinsertionPolicyEnabled);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_version
+#define APACHE_THRIFT_ACCESSOR_version
+APACHE_THRIFT_DEFINE_ACCESSOR(version);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_generationTime
+#define APACHE_THRIFT_ACCESSOR_generationTime
+APACHE_THRIFT_DEFINE_ACCESSOR(generationTime);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_itemCount
+#define APACHE_THRIFT_ACCESSOR_itemCount
+APACHE_THRIFT_DEFINE_ACCESSOR(itemCount);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_bucketSize
+#define APACHE_THRIFT_ACCESSOR_bucketSize
+APACHE_THRIFT_DEFINE_ACCESSOR(bucketSize);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_cacheBaseOffset
+#define APACHE_THRIFT_ACCESSOR_cacheBaseOffset
+APACHE_THRIFT_DEFINE_ACCESSOR(cacheBaseOffset);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_numBuckets
+#define APACHE_THRIFT_ACCESSOR_numBuckets
+APACHE_THRIFT_DEFINE_ACCESSOR(numBuckets);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_sizeDist
+#define APACHE_THRIFT_ACCESSOR_sizeDist
+APACHE_THRIFT_DEFINE_ACCESSOR(sizeDist);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_numFilters
+#define APACHE_THRIFT_ACCESSOR_numFilters
+APACHE_THRIFT_DEFINE_ACCESSOR(numFilters);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_hashTableBitSize
+#define APACHE_THRIFT_ACCESSOR_hashTableBitSize
+APACHE_THRIFT_DEFINE_ACCESSOR(hashTableBitSize);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_filterByteSize
+#define APACHE_THRIFT_ACCESSOR_filterByteSize
+APACHE_THRIFT_DEFINE_ACCESSOR(filterByteSize);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_fragmentSize
+#define APACHE_THRIFT_ACCESSOR_fragmentSize
+APACHE_THRIFT_DEFINE_ACCESSOR(fragmentSize);
+#endif
+#ifndef APACHE_THRIFT_ACCESSOR_seeds
+#define APACHE_THRIFT_ACCESSOR_seeds
+APACHE_THRIFT_DEFINE_ACCESSOR(seeds);
+#endif
+} // namespace detail
+} // namespace thrift
+} // namespace apache
+
+// BEGIN declare_enums
+
+// END declare_enums
+// BEGIN struct_indirection
+
+// END struct_indirection
+// BEGIN forward_declare
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class IndexEntry;
+class IndexBucket;
+class Region;
+class RegionData;
+class AccessStats;
+class AccessTracker;
+class BlockCacheConfig;
+class BigHashPersistentData;
+class BloomFilterPersistentData;
+}}}} // facebook::cachelib::navy::serialization
+// END forward_declare
+// BEGIN typedefs
+
+// END typedefs
+// BEGIN hash_and_equal_to
+// END hash_and_equal_to
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class IndexEntry final : private apache::thrift::detail::st::ComparisonOperators<IndexEntry> {
+ public:
+
+  IndexEntry() :
+      key(0),
+      value(0) {}
+  // FragileConstructor for use in initialization lists only.
+  [[deprecated("This constructor is deprecated")]]
+  IndexEntry(apache::thrift::FragileConstructor, int32_t key__arg, int32_t value__arg);
+
+  IndexEntry(IndexEntry&&) = default;
+
+  IndexEntry(const IndexEntry&) = default;
+
+  IndexEntry& operator=(IndexEntry&&) = default;
+
+  IndexEntry& operator=(const IndexEntry&) = default;
+  void __clear();
+ public:
+  int32_t key;
+ public:
+  int32_t value;
+
+ public:
+  bool operator==(const IndexEntry& rhs) const;
+  bool operator<(const IndexEntry& rhs) const;
+
+  int32_t get_key() const {
+    return key;
+  }
+
+  int32_t& set_key(int32_t key_) {
+    key = key_;
+    return key;
+  }
+
+  int32_t get_value() const {
+    return value;
+  }
+
+  int32_t& set_value(int32_t value_) {
+    value = value_;
+    return value;
+  }
+
+  template <class Protocol_>
+  uint32_t read(Protocol_* iprot);
+  template <class Protocol_>
+  uint32_t serializedSize(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t serializedSizeZC(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t write(Protocol_* prot_) const;
+
+ private:
+  template <class Protocol_>
+  void readNoXfer(Protocol_* iprot);
+
+  friend class ::apache::thrift::Cpp2Ops< IndexEntry >;
+};
+
+void swap(IndexEntry& a, IndexEntry& b);
+
+template <class Protocol_>
+uint32_t IndexEntry::read(Protocol_* iprot) {
+  auto _xferStart = iprot->getCursorPosition();
+  readNoXfer(iprot);
+  return iprot->getCursorPosition() - _xferStart;
+}
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class IndexBucket final : private apache::thrift::detail::st::ComparisonOperators<IndexBucket> {
+ public:
+
+  IndexBucket() :
+      bucketId(0) {}
+  // FragileConstructor for use in initialization lists only.
+  [[deprecated("This constructor is deprecated")]]
+  IndexBucket(apache::thrift::FragileConstructor, int32_t bucketId__arg, ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry> entries__arg);
+
+  IndexBucket(IndexBucket&&) = default;
+
+  IndexBucket(const IndexBucket&) = default;
+
+  IndexBucket& operator=(IndexBucket&&) = default;
+
+  IndexBucket& operator=(const IndexBucket&) = default;
+  void __clear();
+ public:
+  int32_t bucketId;
+ public:
+  ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry> entries;
+
+ public:
+  bool operator==(const IndexBucket& rhs) const;
+  bool operator<(const IndexBucket& rhs) const;
+
+  int32_t get_bucketId() const {
+    return bucketId;
+  }
+
+  int32_t& set_bucketId(int32_t bucketId_) {
+    bucketId = bucketId_;
+    return bucketId;
+  }
+  const ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry>& get_entries() const&;
+  ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry> get_entries() &&;
+
+  template <typename T_IndexBucket_entries_struct_setter = ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry>>
+  ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry>& set_entries(T_IndexBucket_entries_struct_setter&& entries_) {
+    entries = std::forward<T_IndexBucket_entries_struct_setter>(entries_);
+    return entries;
+  }
+
+  template <class Protocol_>
+  uint32_t read(Protocol_* iprot);
+  template <class Protocol_>
+  uint32_t serializedSize(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t serializedSizeZC(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t write(Protocol_* prot_) const;
+
+ private:
+  template <class Protocol_>
+  void readNoXfer(Protocol_* iprot);
+
+  friend class ::apache::thrift::Cpp2Ops< IndexBucket >;
+};
+
+void swap(IndexBucket& a, IndexBucket& b);
+
+template <class Protocol_>
+uint32_t IndexBucket::read(Protocol_* iprot) {
+  auto _xferStart = iprot->getCursorPosition();
+  readNoXfer(iprot);
+  return iprot->getCursorPosition() - _xferStart;
+}
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class Region final : private apache::thrift::detail::st::ComparisonOperators<Region> {
+ public:
+
+  Region() :
+      regionId(0),
+      lastEntryEndOffset(0),
+      classId(0),
+      numItems(0),
+      pinned(false) {}
+  // FragileConstructor for use in initialization lists only.
+  [[deprecated("This constructor is deprecated")]]
+  Region(apache::thrift::FragileConstructor, int32_t regionId__arg, int32_t lastEntryEndOffset__arg, int32_t classId__arg, int32_t numItems__arg, bool pinned__arg);
+
+  Region(Region&&) = default;
+
+  Region(const Region&) = default;
+
+  Region& operator=(Region&&) = default;
+
+  Region& operator=(const Region&) = default;
+  void __clear();
+ public:
+  int32_t regionId;
+ public:
+  int32_t lastEntryEndOffset;
+ public:
+  int32_t classId;
+ public:
+  int32_t numItems;
+ public:
+  bool pinned;
+
+ public:
+  bool operator==(const Region& rhs) const;
+  bool operator<(const Region& rhs) const;
+
+  int32_t get_regionId() const {
+    return regionId;
+  }
+
+  int32_t& set_regionId(int32_t regionId_) {
+    regionId = regionId_;
+    return regionId;
+  }
+
+  int32_t get_lastEntryEndOffset() const {
+    return lastEntryEndOffset;
+  }
+
+  int32_t& set_lastEntryEndOffset(int32_t lastEntryEndOffset_) {
+    lastEntryEndOffset = lastEntryEndOffset_;
+    return lastEntryEndOffset;
+  }
+
+  int32_t get_classId() const {
+    return classId;
+  }
+
+  int32_t& set_classId(int32_t classId_) {
+    classId = classId_;
+    return classId;
+  }
+
+  int32_t get_numItems() const {
+    return numItems;
+  }
+
+  int32_t& set_numItems(int32_t numItems_) {
+    numItems = numItems_;
+    return numItems;
+  }
+
+  bool get_pinned() const {
+    return pinned;
+  }
+
+  bool& set_pinned(bool pinned_) {
+    pinned = pinned_;
+    return pinned;
+  }
+
+  template <class Protocol_>
+  uint32_t read(Protocol_* iprot);
+  template <class Protocol_>
+  uint32_t serializedSize(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t serializedSizeZC(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t write(Protocol_* prot_) const;
+
+ private:
+  template <class Protocol_>
+  void readNoXfer(Protocol_* iprot);
+
+  friend class ::apache::thrift::Cpp2Ops< Region >;
+};
+
+void swap(Region& a, Region& b);
+
+template <class Protocol_>
+uint32_t Region::read(Protocol_* iprot) {
+  auto _xferStart = iprot->getCursorPosition();
+  readNoXfer(iprot);
+  return iprot->getCursorPosition() - _xferStart;
+}
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class RegionData final : private apache::thrift::detail::st::ComparisonOperators<RegionData> {
+ public:
+
+  RegionData() :
+      regionSize(0) {}
+  // FragileConstructor for use in initialization lists only.
+  [[deprecated("This constructor is deprecated")]]
+  RegionData(apache::thrift::FragileConstructor, ::std::vector< ::facebook::cachelib::navy::serialization::Region> regions__arg, int32_t regionSize__arg);
+
+  RegionData(RegionData&&) = default;
+
+  RegionData(const RegionData&) = default;
+
+  RegionData& operator=(RegionData&&) = default;
+
+  RegionData& operator=(const RegionData&) = default;
+  void __clear();
+ public:
+  ::std::vector< ::facebook::cachelib::navy::serialization::Region> regions;
+ public:
+  int32_t regionSize;
+
+ public:
+  bool operator==(const RegionData& rhs) const;
+  bool operator<(const RegionData& rhs) const;
+  const ::std::vector< ::facebook::cachelib::navy::serialization::Region>& get_regions() const&;
+  ::std::vector< ::facebook::cachelib::navy::serialization::Region> get_regions() &&;
+
+  template <typename T_RegionData_regions_struct_setter = ::std::vector< ::facebook::cachelib::navy::serialization::Region>>
+  ::std::vector< ::facebook::cachelib::navy::serialization::Region>& set_regions(T_RegionData_regions_struct_setter&& regions_) {
+    regions = std::forward<T_RegionData_regions_struct_setter>(regions_);
+    return regions;
+  }
+
+  int32_t get_regionSize() const {
+    return regionSize;
+  }
+
+  int32_t& set_regionSize(int32_t regionSize_) {
+    regionSize = regionSize_;
+    return regionSize;
+  }
+
+  template <class Protocol_>
+  uint32_t read(Protocol_* iprot);
+  template <class Protocol_>
+  uint32_t serializedSize(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t serializedSizeZC(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t write(Protocol_* prot_) const;
+
+ private:
+  template <class Protocol_>
+  void readNoXfer(Protocol_* iprot);
+
+  friend class ::apache::thrift::Cpp2Ops< RegionData >;
+};
+
+void swap(RegionData& a, RegionData& b);
+
+template <class Protocol_>
+uint32_t RegionData::read(Protocol_* iprot) {
+  auto _xferStart = iprot->getCursorPosition();
+  readNoXfer(iprot);
+  return iprot->getCursorPosition() - _xferStart;
+}
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class AccessStats final : private apache::thrift::detail::st::ComparisonOperators<AccessStats> {
+ public:
+
+  AccessStats() :
+      totalHits(static_cast<int8_t>(0)),
+      currHits(static_cast<int8_t>(0)),
+      numReinsertions(static_cast<int8_t>(0)) {}
+  // FragileConstructor for use in initialization lists only.
+  [[deprecated("This constructor is deprecated")]]
+  AccessStats(apache::thrift::FragileConstructor, int8_t totalHits__arg, int8_t currHits__arg, int8_t numReinsertions__arg);
+
+  AccessStats(AccessStats&&) = default;
+
+  AccessStats(const AccessStats&) = default;
+
+  AccessStats& operator=(AccessStats&&) = default;
+
+  AccessStats& operator=(const AccessStats&) = default;
+  void __clear();
+ public:
+  int8_t totalHits;
+ public:
+  int8_t currHits;
+ public:
+  int8_t numReinsertions;
+
+ public:
+  struct __isset {
+    bool totalHits;
+    bool currHits;
+    bool numReinsertions;
+  } __isset = {};
+  bool operator==(const AccessStats& rhs) const;
+  bool operator<(const AccessStats& rhs) const;
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&> totalHits_ref() const& {
+    return {this->totalHits, __isset.totalHits};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&&> totalHits_ref() const&& {
+    return {std::move(this->totalHits), __isset.totalHits};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&> totalHits_ref() & {
+    return {this->totalHits, __isset.totalHits};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&&> totalHits_ref() && {
+    return {std::move(this->totalHits), __isset.totalHits};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&> currHits_ref() const& {
+    return {this->currHits, __isset.currHits};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&&> currHits_ref() const&& {
+    return {std::move(this->currHits), __isset.currHits};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&> currHits_ref() & {
+    return {this->currHits, __isset.currHits};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&&> currHits_ref() && {
+    return {std::move(this->currHits), __isset.currHits};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&> numReinsertions_ref() const& {
+    return {this->numReinsertions, __isset.numReinsertions};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&&> numReinsertions_ref() const&& {
+    return {std::move(this->numReinsertions), __isset.numReinsertions};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&> numReinsertions_ref() & {
+    return {this->numReinsertions, __isset.numReinsertions};
+  }
+
+  template <typename..., typename T = int8_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&&> numReinsertions_ref() && {
+    return {std::move(this->numReinsertions), __isset.numReinsertions};
+  }
+
+  int8_t get_totalHits() const {
+    return totalHits;
+  }
+
+  int8_t& set_totalHits(int8_t totalHits_) {
+    totalHits = totalHits_;
+    __isset.totalHits = true;
+    return totalHits;
+  }
+
+  int8_t get_currHits() const {
+    return currHits;
+  }
+
+  int8_t& set_currHits(int8_t currHits_) {
+    currHits = currHits_;
+    __isset.currHits = true;
+    return currHits;
+  }
+
+  int8_t get_numReinsertions() const {
+    return numReinsertions;
+  }
+
+  int8_t& set_numReinsertions(int8_t numReinsertions_) {
+    numReinsertions = numReinsertions_;
+    __isset.numReinsertions = true;
+    return numReinsertions;
+  }
+
+  template <class Protocol_>
+  uint32_t read(Protocol_* iprot);
+  template <class Protocol_>
+  uint32_t serializedSize(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t serializedSizeZC(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t write(Protocol_* prot_) const;
+
+ private:
+  template <class Protocol_>
+  void readNoXfer(Protocol_* iprot);
+
+  friend class ::apache::thrift::Cpp2Ops< AccessStats >;
+};
+
+void swap(AccessStats& a, AccessStats& b);
+
+template <class Protocol_>
+uint32_t AccessStats::read(Protocol_* iprot) {
+  auto _xferStart = iprot->getCursorPosition();
+  readNoXfer(iprot);
+  return iprot->getCursorPosition() - _xferStart;
+}
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class AccessTracker final : private apache::thrift::detail::st::ComparisonOperators<AccessTracker> {
+ public:
+
+  AccessTracker() {}
+  // FragileConstructor for use in initialization lists only.
+  [[deprecated("This constructor is deprecated")]]
+  AccessTracker(apache::thrift::FragileConstructor, ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats> data__arg);
+
+  AccessTracker(AccessTracker&&) = default;
+
+  AccessTracker(const AccessTracker&) = default;
+
+  AccessTracker& operator=(AccessTracker&&) = default;
+
+  AccessTracker& operator=(const AccessTracker&) = default;
+  void __clear();
+ public:
+  ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats> data;
+
+ public:
+  struct __isset {
+    bool data;
+  } __isset = {};
+  bool operator==(const AccessTracker& rhs) const;
+  bool operator<(const AccessTracker& rhs) const;
+
+  template <typename..., typename T = ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&> data_ref() const& {
+    return {this->data, __isset.data};
+  }
+
+  template <typename..., typename T = ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&&> data_ref() const&& {
+    return {std::move(this->data), __isset.data};
+  }
+
+  template <typename..., typename T = ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&> data_ref() & {
+    return {this->data, __isset.data};
+  }
+
+  template <typename..., typename T = ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&&> data_ref() && {
+    return {std::move(this->data), __isset.data};
+  }
+  const ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>& get_data() const&;
+  ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats> get_data() &&;
+
+  template <typename T_AccessTracker_data_struct_setter = ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>>
+  ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>& set_data(T_AccessTracker_data_struct_setter&& data_) {
+    data = std::forward<T_AccessTracker_data_struct_setter>(data_);
+    __isset.data = true;
+    return data;
+  }
+
+  template <class Protocol_>
+  uint32_t read(Protocol_* iprot);
+  template <class Protocol_>
+  uint32_t serializedSize(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t serializedSizeZC(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t write(Protocol_* prot_) const;
+
+ private:
+  template <class Protocol_>
+  void readNoXfer(Protocol_* iprot);
+
+  friend class ::apache::thrift::Cpp2Ops< AccessTracker >;
+};
+
+void swap(AccessTracker& a, AccessTracker& b);
+
+template <class Protocol_>
+uint32_t AccessTracker::read(Protocol_* iprot) {
+  auto _xferStart = iprot->getCursorPosition();
+  readNoXfer(iprot);
+  return iprot->getCursorPosition() - _xferStart;
+}
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class BlockCacheConfig final : private apache::thrift::detail::st::ComparisonOperators<BlockCacheConfig> {
+ public:
+
+  BlockCacheConfig();
+
+  // FragileConstructor for use in initialization lists only.
+  [[deprecated("This constructor is deprecated")]]
+  BlockCacheConfig(apache::thrift::FragileConstructor, int64_t version__arg, int64_t cacheBaseOffset__arg, int64_t cacheSize__arg, int32_t blockSize__arg, ::std::set<int32_t> sizeClasses__arg, bool checksum__arg, ::std::map<int64_t, int64_t> sizeDist__arg, int64_t holeCount__arg, int64_t holeSizeTotal__arg, bool reinsertionPolicyEnabled__arg);
+
+  BlockCacheConfig(BlockCacheConfig&&) = default;
+
+  BlockCacheConfig(const BlockCacheConfig&) = default;
+
+  BlockCacheConfig& operator=(BlockCacheConfig&&) = default;
+
+  BlockCacheConfig& operator=(const BlockCacheConfig&) = default;
+  void __clear();
+
+  ~BlockCacheConfig();
+
+ public:
+  int64_t version;
+ public:
+  int64_t cacheBaseOffset;
+ public:
+  int64_t cacheSize;
+ public:
+  int32_t blockSize;
+ public:
+  ::std::set<int32_t> sizeClasses;
+ public:
+  bool checksum;
+ public:
+  ::std::map<int64_t, int64_t> sizeDist;
+ public:
+  int64_t holeCount;
+ public:
+  int64_t holeSizeTotal;
+ public:
+  bool reinsertionPolicyEnabled;
+
+ public:
+  struct __isset {
+    bool sizeDist;
+    bool holeCount;
+    bool holeSizeTotal;
+    bool reinsertionPolicyEnabled;
+  } __isset = {};
+  bool operator==(const BlockCacheConfig& rhs) const;
+  bool operator<(const BlockCacheConfig& rhs) const;
+
+  template <typename..., typename T = ::std::map<int64_t, int64_t>>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&> sizeDist_ref() const& {
+    return {this->sizeDist, __isset.sizeDist};
+  }
+
+  template <typename..., typename T = ::std::map<int64_t, int64_t>>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&&> sizeDist_ref() const&& {
+    return {std::move(this->sizeDist), __isset.sizeDist};
+  }
+
+  template <typename..., typename T = ::std::map<int64_t, int64_t>>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&> sizeDist_ref() & {
+    return {this->sizeDist, __isset.sizeDist};
+  }
+
+  template <typename..., typename T = ::std::map<int64_t, int64_t>>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&&> sizeDist_ref() && {
+    return {std::move(this->sizeDist), __isset.sizeDist};
+  }
+
+  template <typename..., typename T = int64_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&> holeCount_ref() const& {
+    return {this->holeCount, __isset.holeCount};
+  }
+
+  template <typename..., typename T = int64_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&&> holeCount_ref() const&& {
+    return {std::move(this->holeCount), __isset.holeCount};
+  }
+
+  template <typename..., typename T = int64_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&> holeCount_ref() & {
+    return {this->holeCount, __isset.holeCount};
+  }
+
+  template <typename..., typename T = int64_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&&> holeCount_ref() && {
+    return {std::move(this->holeCount), __isset.holeCount};
+  }
+
+  template <typename..., typename T = int64_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&> holeSizeTotal_ref() const& {
+    return {this->holeSizeTotal, __isset.holeSizeTotal};
+  }
+
+  template <typename..., typename T = int64_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&&> holeSizeTotal_ref() const&& {
+    return {std::move(this->holeSizeTotal), __isset.holeSizeTotal};
+  }
+
+  template <typename..., typename T = int64_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&> holeSizeTotal_ref() & {
+    return {this->holeSizeTotal, __isset.holeSizeTotal};
+  }
+
+  template <typename..., typename T = int64_t>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&&> holeSizeTotal_ref() && {
+    return {std::move(this->holeSizeTotal), __isset.holeSizeTotal};
+  }
+
+  template <typename..., typename T = bool>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&> reinsertionPolicyEnabled_ref() const& {
+    return {this->reinsertionPolicyEnabled, __isset.reinsertionPolicyEnabled};
+  }
+
+  template <typename..., typename T = bool>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&&> reinsertionPolicyEnabled_ref() const&& {
+    return {std::move(this->reinsertionPolicyEnabled), __isset.reinsertionPolicyEnabled};
+  }
+
+  template <typename..., typename T = bool>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&> reinsertionPolicyEnabled_ref() & {
+    return {this->reinsertionPolicyEnabled, __isset.reinsertionPolicyEnabled};
+  }
+
+  template <typename..., typename T = bool>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&&> reinsertionPolicyEnabled_ref() && {
+    return {std::move(this->reinsertionPolicyEnabled), __isset.reinsertionPolicyEnabled};
+  }
+
+  int64_t get_version() const {
+    return version;
+  }
+
+  int64_t& set_version(int64_t version_) {
+    version = version_;
+    return version;
+  }
+
+  int64_t get_cacheBaseOffset() const {
+    return cacheBaseOffset;
+  }
+
+  int64_t& set_cacheBaseOffset(int64_t cacheBaseOffset_) {
+    cacheBaseOffset = cacheBaseOffset_;
+    return cacheBaseOffset;
+  }
+
+  int64_t get_cacheSize() const {
+    return cacheSize;
+  }
+
+  int64_t& set_cacheSize(int64_t cacheSize_) {
+    cacheSize = cacheSize_;
+    return cacheSize;
+  }
+
+  int32_t get_blockSize() const {
+    return blockSize;
+  }
+
+  int32_t& set_blockSize(int32_t blockSize_) {
+    blockSize = blockSize_;
+    return blockSize;
+  }
+  const ::std::set<int32_t>& get_sizeClasses() const&;
+  ::std::set<int32_t> get_sizeClasses() &&;
+
+  template <typename T_BlockCacheConfig_sizeClasses_struct_setter = ::std::set<int32_t>>
+  ::std::set<int32_t>& set_sizeClasses(T_BlockCacheConfig_sizeClasses_struct_setter&& sizeClasses_) {
+    sizeClasses = std::forward<T_BlockCacheConfig_sizeClasses_struct_setter>(sizeClasses_);
+    return sizeClasses;
+  }
+
+  bool get_checksum() const {
+    return checksum;
+  }
+
+  bool& set_checksum(bool checksum_) {
+    checksum = checksum_;
+    return checksum;
+  }
+  const ::std::map<int64_t, int64_t>& get_sizeDist() const&;
+  ::std::map<int64_t, int64_t> get_sizeDist() &&;
+
+  template <typename T_BlockCacheConfig_sizeDist_struct_setter = ::std::map<int64_t, int64_t>>
+  ::std::map<int64_t, int64_t>& set_sizeDist(T_BlockCacheConfig_sizeDist_struct_setter&& sizeDist_) {
+    sizeDist = std::forward<T_BlockCacheConfig_sizeDist_struct_setter>(sizeDist_);
+    __isset.sizeDist = true;
+    return sizeDist;
+  }
+
+  int64_t get_holeCount() const {
+    return holeCount;
+  }
+
+  int64_t& set_holeCount(int64_t holeCount_) {
+    holeCount = holeCount_;
+    __isset.holeCount = true;
+    return holeCount;
+  }
+
+  int64_t get_holeSizeTotal() const {
+    return holeSizeTotal;
+  }
+
+  int64_t& set_holeSizeTotal(int64_t holeSizeTotal_) {
+    holeSizeTotal = holeSizeTotal_;
+    __isset.holeSizeTotal = true;
+    return holeSizeTotal;
+  }
+
+  bool get_reinsertionPolicyEnabled() const {
+    return reinsertionPolicyEnabled;
+  }
+
+  bool& set_reinsertionPolicyEnabled(bool reinsertionPolicyEnabled_) {
+    reinsertionPolicyEnabled = reinsertionPolicyEnabled_;
+    __isset.reinsertionPolicyEnabled = true;
+    return reinsertionPolicyEnabled;
+  }
+
+  template <class Protocol_>
+  uint32_t read(Protocol_* iprot);
+  template <class Protocol_>
+  uint32_t serializedSize(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t serializedSizeZC(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t write(Protocol_* prot_) const;
+
+ private:
+  template <class Protocol_>
+  void readNoXfer(Protocol_* iprot);
+
+  friend class ::apache::thrift::Cpp2Ops< BlockCacheConfig >;
+};
+
+void swap(BlockCacheConfig& a, BlockCacheConfig& b);
+
+template <class Protocol_>
+uint32_t BlockCacheConfig::read(Protocol_* iprot) {
+  auto _xferStart = iprot->getCursorPosition();
+  readNoXfer(iprot);
+  return iprot->getCursorPosition() - _xferStart;
+}
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class BigHashPersistentData final : private apache::thrift::detail::st::ComparisonOperators<BigHashPersistentData> {
+ public:
+
+  BigHashPersistentData();
+
+  // FragileConstructor for use in initialization lists only.
+  [[deprecated("This constructor is deprecated")]]
+  BigHashPersistentData(apache::thrift::FragileConstructor, int32_t version__arg, int64_t generationTime__arg, int64_t itemCount__arg, int64_t bucketSize__arg, int64_t cacheBaseOffset__arg, int64_t numBuckets__arg, ::std::map<int64_t, int64_t> sizeDist__arg);
+
+  BigHashPersistentData(BigHashPersistentData&&) = default;
+
+  BigHashPersistentData(const BigHashPersistentData&) = default;
+
+  BigHashPersistentData& operator=(BigHashPersistentData&&) = default;
+
+  BigHashPersistentData& operator=(const BigHashPersistentData&) = default;
+  void __clear();
+
+  ~BigHashPersistentData();
+
+ public:
+  int32_t version;
+ public:
+  int64_t generationTime;
+ public:
+  int64_t itemCount;
+ public:
+  int64_t bucketSize;
+ public:
+  int64_t cacheBaseOffset;
+ public:
+  int64_t numBuckets;
+ public:
+  ::std::map<int64_t, int64_t> sizeDist;
+
+ public:
+  struct __isset {
+    bool sizeDist;
+  } __isset = {};
+  bool operator==(const BigHashPersistentData& rhs) const;
+  bool operator<(const BigHashPersistentData& rhs) const;
+
+  template <typename..., typename T = ::std::map<int64_t, int64_t>>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&> sizeDist_ref() const& {
+    return {this->sizeDist, __isset.sizeDist};
+  }
+
+  template <typename..., typename T = ::std::map<int64_t, int64_t>>
+  FOLLY_ERASE ::apache::thrift::field_ref<const T&&> sizeDist_ref() const&& {
+    return {std::move(this->sizeDist), __isset.sizeDist};
+  }
+
+  template <typename..., typename T = ::std::map<int64_t, int64_t>>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&> sizeDist_ref() & {
+    return {this->sizeDist, __isset.sizeDist};
+  }
+
+  template <typename..., typename T = ::std::map<int64_t, int64_t>>
+  FOLLY_ERASE ::apache::thrift::field_ref<T&&> sizeDist_ref() && {
+    return {std::move(this->sizeDist), __isset.sizeDist};
+  }
+
+  int32_t get_version() const {
+    return version;
+  }
+
+  int32_t& set_version(int32_t version_) {
+    version = version_;
+    return version;
+  }
+
+  int64_t get_generationTime() const {
+    return generationTime;
+  }
+
+  int64_t& set_generationTime(int64_t generationTime_) {
+    generationTime = generationTime_;
+    return generationTime;
+  }
+
+  int64_t get_itemCount() const {
+    return itemCount;
+  }
+
+  int64_t& set_itemCount(int64_t itemCount_) {
+    itemCount = itemCount_;
+    return itemCount;
+  }
+
+  int64_t get_bucketSize() const {
+    return bucketSize;
+  }
+
+  int64_t& set_bucketSize(int64_t bucketSize_) {
+    bucketSize = bucketSize_;
+    return bucketSize;
+  }
+
+  int64_t get_cacheBaseOffset() const {
+    return cacheBaseOffset;
+  }
+
+  int64_t& set_cacheBaseOffset(int64_t cacheBaseOffset_) {
+    cacheBaseOffset = cacheBaseOffset_;
+    return cacheBaseOffset;
+  }
+
+  int64_t get_numBuckets() const {
+    return numBuckets;
+  }
+
+  int64_t& set_numBuckets(int64_t numBuckets_) {
+    numBuckets = numBuckets_;
+    return numBuckets;
+  }
+  const ::std::map<int64_t, int64_t>& get_sizeDist() const&;
+  ::std::map<int64_t, int64_t> get_sizeDist() &&;
+
+  template <typename T_BigHashPersistentData_sizeDist_struct_setter = ::std::map<int64_t, int64_t>>
+  ::std::map<int64_t, int64_t>& set_sizeDist(T_BigHashPersistentData_sizeDist_struct_setter&& sizeDist_) {
+    sizeDist = std::forward<T_BigHashPersistentData_sizeDist_struct_setter>(sizeDist_);
+    __isset.sizeDist = true;
+    return sizeDist;
+  }
+
+  template <class Protocol_>
+  uint32_t read(Protocol_* iprot);
+  template <class Protocol_>
+  uint32_t serializedSize(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t serializedSizeZC(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t write(Protocol_* prot_) const;
+
+ private:
+  template <class Protocol_>
+  void readNoXfer(Protocol_* iprot);
+
+  friend class ::apache::thrift::Cpp2Ops< BigHashPersistentData >;
+};
+
+void swap(BigHashPersistentData& a, BigHashPersistentData& b);
+
+template <class Protocol_>
+uint32_t BigHashPersistentData::read(Protocol_* iprot) {
+  auto _xferStart = iprot->getCursorPosition();
+  readNoXfer(iprot);
+  return iprot->getCursorPosition() - _xferStart;
+}
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+class BloomFilterPersistentData final : private apache::thrift::detail::st::ComparisonOperators<BloomFilterPersistentData> {
+ public:
+
+  BloomFilterPersistentData();
+
+  // FragileConstructor for use in initialization lists only.
+  [[deprecated("This constructor is deprecated")]]
+  BloomFilterPersistentData(apache::thrift::FragileConstructor, int32_t numFilters__arg, int64_t hashTableBitSize__arg, int64_t filterByteSize__arg, int32_t fragmentSize__arg, ::std::vector<int64_t> seeds__arg);
+
+  BloomFilterPersistentData(BloomFilterPersistentData&&) = default;
+
+  BloomFilterPersistentData(const BloomFilterPersistentData&) = default;
+
+  BloomFilterPersistentData& operator=(BloomFilterPersistentData&&) = default;
+
+  BloomFilterPersistentData& operator=(const BloomFilterPersistentData&) = default;
+  void __clear();
+
+  ~BloomFilterPersistentData();
+
+ public:
+  int32_t numFilters;
+ public:
+  int64_t hashTableBitSize;
+ public:
+  int64_t filterByteSize;
+ public:
+  int32_t fragmentSize;
+ public:
+  ::std::vector<int64_t> seeds;
+
+ public:
+  bool operator==(const BloomFilterPersistentData& rhs) const;
+  bool operator<(const BloomFilterPersistentData& rhs) const;
+
+  int32_t get_numFilters() const {
+    return numFilters;
+  }
+
+  int32_t& set_numFilters(int32_t numFilters_) {
+    numFilters = numFilters_;
+    return numFilters;
+  }
+
+  int64_t get_hashTableBitSize() const {
+    return hashTableBitSize;
+  }
+
+  int64_t& set_hashTableBitSize(int64_t hashTableBitSize_) {
+    hashTableBitSize = hashTableBitSize_;
+    return hashTableBitSize;
+  }
+
+  int64_t get_filterByteSize() const {
+    return filterByteSize;
+  }
+
+  int64_t& set_filterByteSize(int64_t filterByteSize_) {
+    filterByteSize = filterByteSize_;
+    return filterByteSize;
+  }
+
+  int32_t get_fragmentSize() const {
+    return fragmentSize;
+  }
+
+  int32_t& set_fragmentSize(int32_t fragmentSize_) {
+    fragmentSize = fragmentSize_;
+    return fragmentSize;
+  }
+  const ::std::vector<int64_t>& get_seeds() const&;
+  ::std::vector<int64_t> get_seeds() &&;
+
+  template <typename T_BloomFilterPersistentData_seeds_struct_setter = ::std::vector<int64_t>>
+  ::std::vector<int64_t>& set_seeds(T_BloomFilterPersistentData_seeds_struct_setter&& seeds_) {
+    seeds = std::forward<T_BloomFilterPersistentData_seeds_struct_setter>(seeds_);
+    return seeds;
+  }
+
+  template <class Protocol_>
+  uint32_t read(Protocol_* iprot);
+  template <class Protocol_>
+  uint32_t serializedSize(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t serializedSizeZC(Protocol_ const* prot_) const;
+  template <class Protocol_>
+  uint32_t write(Protocol_* prot_) const;
+
+ private:
+  template <class Protocol_>
+  void readNoXfer(Protocol_* iprot);
+
+  friend class ::apache::thrift::Cpp2Ops< BloomFilterPersistentData >;
+};
+
+void swap(BloomFilterPersistentData& a, BloomFilterPersistentData& b);
+
+template <class Protocol_>
+uint32_t BloomFilterPersistentData::read(Protocol_* iprot) {
+  auto _xferStart = iprot->getCursorPosition();
+  readNoXfer(iprot);
+  return iprot->getCursorPosition() - _xferStart;
+}
+
+}}}} // facebook::cachelib::navy::serialization
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_types.tcc b/cachelib/navy/serialization/gen-cpp2/objects_types.tcc
new file mode 100644
index 0000000000..dfe1ad5950
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_types.tcc
@@ -0,0 +1,1841 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+#pragma once
+
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_types.h"
+
+#include <thrift/lib/cpp2/gen/module_types_tcc.h>
+
+
+namespace apache {
+namespace thrift {
+namespace detail {
+
+template <>
+struct TccStructTraits<::facebook::cachelib::navy::serialization::IndexEntry> {
+  static void translateFieldName(
+      folly::StringPiece _fname,
+      int16_t& fid,
+      apache::thrift::protocol::TType& _ftype);
+};
+template <>
+struct TccStructTraits<::facebook::cachelib::navy::serialization::IndexBucket> {
+  static void translateFieldName(
+      folly::StringPiece _fname,
+      int16_t& fid,
+      apache::thrift::protocol::TType& _ftype);
+};
+template <>
+struct TccStructTraits<::facebook::cachelib::navy::serialization::Region> {
+  static void translateFieldName(
+      folly::StringPiece _fname,
+      int16_t& fid,
+      apache::thrift::protocol::TType& _ftype);
+};
+template <>
+struct TccStructTraits<::facebook::cachelib::navy::serialization::RegionData> {
+  static void translateFieldName(
+      folly::StringPiece _fname,
+      int16_t& fid,
+      apache::thrift::protocol::TType& _ftype);
+};
+template <>
+struct TccStructTraits<::facebook::cachelib::navy::serialization::AccessStats> {
+  static void translateFieldName(
+      folly::StringPiece _fname,
+      int16_t& fid,
+      apache::thrift::protocol::TType& _ftype);
+};
+template <>
+struct TccStructTraits<::facebook::cachelib::navy::serialization::AccessTracker> {
+  static void translateFieldName(
+      folly::StringPiece _fname,
+      int16_t& fid,
+      apache::thrift::protocol::TType& _ftype);
+};
+template <>
+struct TccStructTraits<::facebook::cachelib::navy::serialization::BlockCacheConfig> {
+  static void translateFieldName(
+      folly::StringPiece _fname,
+      int16_t& fid,
+      apache::thrift::protocol::TType& _ftype);
+};
+template <>
+struct TccStructTraits<::facebook::cachelib::navy::serialization::BigHashPersistentData> {
+  static void translateFieldName(
+      folly::StringPiece _fname,
+      int16_t& fid,
+      apache::thrift::protocol::TType& _ftype);
+};
+template <>
+struct TccStructTraits<::facebook::cachelib::navy::serialization::BloomFilterPersistentData> {
+  static void translateFieldName(
+      folly::StringPiece _fname,
+      int16_t& fid,
+      apache::thrift::protocol::TType& _ftype);
+};
+
+} // namespace detail
+} // namespace thrift
+} // namespace apache
+
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+template <class Protocol_>
+void IndexEntry::readNoXfer(Protocol_* iprot) {
+  apache::thrift::detail::ProtocolReaderStructReadState<Protocol_> _readState;
+
+  _readState.readStructBegin(iprot);
+
+  using apache::thrift::TProtocolException;
+
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          0,
+          1,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_key:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->key, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          1,
+          2,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_value:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->value, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          2,
+          0,
+          apache::thrift::protocol::T_STOP))) {
+    goto _loop;
+  }
+
+_end:
+  _readState.readStructEnd(iprot);
+
+  return;
+
+_loop:
+  _readState.afterAdvanceFailure(iprot);
+  if (_readState.atStop()) {
+    goto _end;
+  }
+  if (iprot->kUsesFieldNames()) {
+    _readState.template fillFieldTraitsFromName<apache::thrift::detail::TccStructTraits<IndexEntry>>();
+  }
+
+  switch (_readState.fieldId) {
+    case 1:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_key;
+      } else {
+        goto _skip;
+      }
+    }
+    case 2:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_value;
+      } else {
+        goto _skip;
+      }
+    }
+    default:
+    {
+_skip:
+      _readState.skip(iprot);
+      _readState.readFieldEnd(iprot);
+      _readState.readFieldBeginNoInline(iprot);
+      goto _loop;
+    }
+  }
+}
+
+template <class Protocol_>
+uint32_t IndexEntry::serializedSize(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("IndexEntry");
+  xfer += prot_->serializedFieldSize("key", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->key);
+  xfer += prot_->serializedFieldSize("value", apache::thrift::protocol::T_I32, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->value);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t IndexEntry::serializedSizeZC(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("IndexEntry");
+  xfer += prot_->serializedFieldSize("key", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->key);
+  xfer += prot_->serializedFieldSize("value", apache::thrift::protocol::T_I32, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->value);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t IndexEntry::write(Protocol_* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->writeStructBegin("IndexEntry");
+  xfer += prot_->writeFieldBegin("key", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->key);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("value", apache::thrift::protocol::T_I32, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->value);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldStop();
+  xfer += prot_->writeStructEnd();
+  return xfer;
+}
+
+extern template void IndexEntry::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+extern template uint32_t IndexEntry::write<>(apache::thrift::BinaryProtocolWriter*) const;
+extern template uint32_t IndexEntry::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template uint32_t IndexEntry::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template void IndexEntry::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+extern template uint32_t IndexEntry::write<>(apache::thrift::CompactProtocolWriter*) const;
+extern template uint32_t IndexEntry::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+extern template uint32_t IndexEntry::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+template <class Protocol_>
+void IndexBucket::readNoXfer(Protocol_* iprot) {
+  apache::thrift::detail::ProtocolReaderStructReadState<Protocol_> _readState;
+
+  _readState.readStructBegin(iprot);
+
+  using apache::thrift::TProtocolException;
+
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          0,
+          1,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_bucketId:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->bucketId, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          1,
+          2,
+          apache::thrift::protocol::T_LIST))) {
+    goto _loop;
+  }
+_readField_entries:
+  {
+    _readState.beforeSubobject(iprot);
+    
+    this->entries = ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry>();
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::structure>, ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry>>::read(*iprot, this->entries);
+    _readState.afterSubobject(iprot);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          2,
+          0,
+          apache::thrift::protocol::T_STOP))) {
+    goto _loop;
+  }
+
+_end:
+  _readState.readStructEnd(iprot);
+
+  return;
+
+_loop:
+  _readState.afterAdvanceFailure(iprot);
+  if (_readState.atStop()) {
+    goto _end;
+  }
+  if (iprot->kUsesFieldNames()) {
+    _readState.template fillFieldTraitsFromName<apache::thrift::detail::TccStructTraits<IndexBucket>>();
+  }
+
+  switch (_readState.fieldId) {
+    case 1:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_bucketId;
+      } else {
+        goto _skip;
+      }
+    }
+    case 2:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_LIST))) {
+        goto _readField_entries;
+      } else {
+        goto _skip;
+      }
+    }
+    default:
+    {
+_skip:
+      _readState.skip(iprot);
+      _readState.readFieldEnd(iprot);
+      _readState.readFieldBeginNoInline(iprot);
+      goto _loop;
+    }
+  }
+}
+
+template <class Protocol_>
+uint32_t IndexBucket::serializedSize(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("IndexBucket");
+  xfer += prot_->serializedFieldSize("bucketId", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->bucketId);
+  xfer += prot_->serializedFieldSize("entries", apache::thrift::protocol::T_LIST, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::structure>, ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry>>::serializedSize<false>(*prot_, this->entries);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t IndexBucket::serializedSizeZC(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("IndexBucket");
+  xfer += prot_->serializedFieldSize("bucketId", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->bucketId);
+  xfer += prot_->serializedFieldSize("entries", apache::thrift::protocol::T_LIST, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::structure>, ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry>>::serializedSize<false>(*prot_, this->entries);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t IndexBucket::write(Protocol_* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->writeStructBegin("IndexBucket");
+  xfer += prot_->writeFieldBegin("bucketId", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->bucketId);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("entries", apache::thrift::protocol::T_LIST, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::structure>, ::std::vector< ::facebook::cachelib::navy::serialization::IndexEntry>>::write(*prot_, this->entries);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldStop();
+  xfer += prot_->writeStructEnd();
+  return xfer;
+}
+
+extern template void IndexBucket::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+extern template uint32_t IndexBucket::write<>(apache::thrift::BinaryProtocolWriter*) const;
+extern template uint32_t IndexBucket::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template uint32_t IndexBucket::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template void IndexBucket::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+extern template uint32_t IndexBucket::write<>(apache::thrift::CompactProtocolWriter*) const;
+extern template uint32_t IndexBucket::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+extern template uint32_t IndexBucket::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+template <class Protocol_>
+void Region::readNoXfer(Protocol_* iprot) {
+  apache::thrift::detail::ProtocolReaderStructReadState<Protocol_> _readState;
+
+  _readState.readStructBegin(iprot);
+
+  using apache::thrift::TProtocolException;
+
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          0,
+          1,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_regionId:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->regionId, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          1,
+          2,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_lastEntryEndOffset:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->lastEntryEndOffset, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          2,
+          3,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_classId:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->classId, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          3,
+          4,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_numItems:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->numItems, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          4,
+          5,
+          apache::thrift::protocol::T_BOOL))) {
+    goto _loop;
+  }
+_readField_pinned:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, bool>::readWithContext(*iprot, this->pinned, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          5,
+          0,
+          apache::thrift::protocol::T_STOP))) {
+    goto _loop;
+  }
+
+_end:
+  _readState.readStructEnd(iprot);
+
+  return;
+
+_loop:
+  _readState.afterAdvanceFailure(iprot);
+  if (_readState.atStop()) {
+    goto _end;
+  }
+  if (iprot->kUsesFieldNames()) {
+    _readState.template fillFieldTraitsFromName<apache::thrift::detail::TccStructTraits<Region>>();
+  }
+
+  switch (_readState.fieldId) {
+    case 1:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_regionId;
+      } else {
+        goto _skip;
+      }
+    }
+    case 2:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_lastEntryEndOffset;
+      } else {
+        goto _skip;
+      }
+    }
+    case 3:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_classId;
+      } else {
+        goto _skip;
+      }
+    }
+    case 4:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_numItems;
+      } else {
+        goto _skip;
+      }
+    }
+    case 5:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_BOOL))) {
+        goto _readField_pinned;
+      } else {
+        goto _skip;
+      }
+    }
+    default:
+    {
+_skip:
+      _readState.skip(iprot);
+      _readState.readFieldEnd(iprot);
+      _readState.readFieldBeginNoInline(iprot);
+      goto _loop;
+    }
+  }
+}
+
+template <class Protocol_>
+uint32_t Region::serializedSize(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("Region");
+  xfer += prot_->serializedFieldSize("regionId", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->regionId);
+  xfer += prot_->serializedFieldSize("lastEntryEndOffset", apache::thrift::protocol::T_I32, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->lastEntryEndOffset);
+  xfer += prot_->serializedFieldSize("classId", apache::thrift::protocol::T_I32, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->classId);
+  xfer += prot_->serializedFieldSize("numItems", apache::thrift::protocol::T_I32, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->numItems);
+  xfer += prot_->serializedFieldSize("pinned", apache::thrift::protocol::T_BOOL, 5);
+  xfer += prot_->serializedSizeBool(this->pinned);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t Region::serializedSizeZC(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("Region");
+  xfer += prot_->serializedFieldSize("regionId", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->regionId);
+  xfer += prot_->serializedFieldSize("lastEntryEndOffset", apache::thrift::protocol::T_I32, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->lastEntryEndOffset);
+  xfer += prot_->serializedFieldSize("classId", apache::thrift::protocol::T_I32, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->classId);
+  xfer += prot_->serializedFieldSize("numItems", apache::thrift::protocol::T_I32, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->numItems);
+  xfer += prot_->serializedFieldSize("pinned", apache::thrift::protocol::T_BOOL, 5);
+  xfer += prot_->serializedSizeBool(this->pinned);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t Region::write(Protocol_* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->writeStructBegin("Region");
+  xfer += prot_->writeFieldBegin("regionId", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->regionId);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("lastEntryEndOffset", apache::thrift::protocol::T_I32, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->lastEntryEndOffset);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("classId", apache::thrift::protocol::T_I32, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->classId);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("numItems", apache::thrift::protocol::T_I32, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->numItems);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("pinned", apache::thrift::protocol::T_BOOL, 5);
+  xfer += prot_->writeBool(this->pinned);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldStop();
+  xfer += prot_->writeStructEnd();
+  return xfer;
+}
+
+extern template void Region::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+extern template uint32_t Region::write<>(apache::thrift::BinaryProtocolWriter*) const;
+extern template uint32_t Region::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template uint32_t Region::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template void Region::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+extern template uint32_t Region::write<>(apache::thrift::CompactProtocolWriter*) const;
+extern template uint32_t Region::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+extern template uint32_t Region::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+template <class Protocol_>
+void RegionData::readNoXfer(Protocol_* iprot) {
+  apache::thrift::detail::ProtocolReaderStructReadState<Protocol_> _readState;
+
+  _readState.readStructBegin(iprot);
+
+  using apache::thrift::TProtocolException;
+
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          0,
+          1,
+          apache::thrift::protocol::T_LIST))) {
+    goto _loop;
+  }
+_readField_regions:
+  {
+    _readState.beforeSubobject(iprot);
+    
+    this->regions = ::std::vector< ::facebook::cachelib::navy::serialization::Region>();
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::structure>, ::std::vector< ::facebook::cachelib::navy::serialization::Region>>::read(*iprot, this->regions);
+    _readState.afterSubobject(iprot);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          1,
+          2,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_regionSize:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->regionSize, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          2,
+          0,
+          apache::thrift::protocol::T_STOP))) {
+    goto _loop;
+  }
+
+_end:
+  _readState.readStructEnd(iprot);
+
+  return;
+
+_loop:
+  _readState.afterAdvanceFailure(iprot);
+  if (_readState.atStop()) {
+    goto _end;
+  }
+  if (iprot->kUsesFieldNames()) {
+    _readState.template fillFieldTraitsFromName<apache::thrift::detail::TccStructTraits<RegionData>>();
+  }
+
+  switch (_readState.fieldId) {
+    case 1:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_LIST))) {
+        goto _readField_regions;
+      } else {
+        goto _skip;
+      }
+    }
+    case 2:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_regionSize;
+      } else {
+        goto _skip;
+      }
+    }
+    default:
+    {
+_skip:
+      _readState.skip(iprot);
+      _readState.readFieldEnd(iprot);
+      _readState.readFieldBeginNoInline(iprot);
+      goto _loop;
+    }
+  }
+}
+
+template <class Protocol_>
+uint32_t RegionData::serializedSize(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("RegionData");
+  xfer += prot_->serializedFieldSize("regions", apache::thrift::protocol::T_LIST, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::structure>, ::std::vector< ::facebook::cachelib::navy::serialization::Region>>::serializedSize<false>(*prot_, this->regions);
+  xfer += prot_->serializedFieldSize("regionSize", apache::thrift::protocol::T_I32, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->regionSize);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t RegionData::serializedSizeZC(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("RegionData");
+  xfer += prot_->serializedFieldSize("regions", apache::thrift::protocol::T_LIST, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::structure>, ::std::vector< ::facebook::cachelib::navy::serialization::Region>>::serializedSize<false>(*prot_, this->regions);
+  xfer += prot_->serializedFieldSize("regionSize", apache::thrift::protocol::T_I32, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->regionSize);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t RegionData::write(Protocol_* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->writeStructBegin("RegionData");
+  xfer += prot_->writeFieldBegin("regions", apache::thrift::protocol::T_LIST, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::structure>, ::std::vector< ::facebook::cachelib::navy::serialization::Region>>::write(*prot_, this->regions);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("regionSize", apache::thrift::protocol::T_I32, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->regionSize);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldStop();
+  xfer += prot_->writeStructEnd();
+  return xfer;
+}
+
+extern template void RegionData::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+extern template uint32_t RegionData::write<>(apache::thrift::BinaryProtocolWriter*) const;
+extern template uint32_t RegionData::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template uint32_t RegionData::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template void RegionData::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+extern template uint32_t RegionData::write<>(apache::thrift::CompactProtocolWriter*) const;
+extern template uint32_t RegionData::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+extern template uint32_t RegionData::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+template <class Protocol_>
+void AccessStats::readNoXfer(Protocol_* iprot) {
+  apache::thrift::detail::ProtocolReaderStructReadState<Protocol_> _readState;
+
+  _readState.readStructBegin(iprot);
+
+  using apache::thrift::TProtocolException;
+
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          0,
+          1,
+          apache::thrift::protocol::T_BYTE))) {
+    goto _loop;
+  }
+_readField_totalHits:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::readWithContext(*iprot, this->totalHits, _readState);
+    this->__isset.totalHits = true;
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          1,
+          2,
+          apache::thrift::protocol::T_BYTE))) {
+    goto _loop;
+  }
+_readField_currHits:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::readWithContext(*iprot, this->currHits, _readState);
+    this->__isset.currHits = true;
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          2,
+          3,
+          apache::thrift::protocol::T_BYTE))) {
+    goto _loop;
+  }
+_readField_numReinsertions:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::readWithContext(*iprot, this->numReinsertions, _readState);
+    this->__isset.numReinsertions = true;
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          3,
+          0,
+          apache::thrift::protocol::T_STOP))) {
+    goto _loop;
+  }
+
+_end:
+  _readState.readStructEnd(iprot);
+
+  return;
+
+_loop:
+  _readState.afterAdvanceFailure(iprot);
+  if (_readState.atStop()) {
+    goto _end;
+  }
+  if (iprot->kUsesFieldNames()) {
+    _readState.template fillFieldTraitsFromName<apache::thrift::detail::TccStructTraits<AccessStats>>();
+  }
+
+  switch (_readState.fieldId) {
+    case 1:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_BYTE))) {
+        goto _readField_totalHits;
+      } else {
+        goto _skip;
+      }
+    }
+    case 2:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_BYTE))) {
+        goto _readField_currHits;
+      } else {
+        goto _skip;
+      }
+    }
+    case 3:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_BYTE))) {
+        goto _readField_numReinsertions;
+      } else {
+        goto _skip;
+      }
+    }
+    default:
+    {
+_skip:
+      _readState.skip(iprot);
+      _readState.readFieldEnd(iprot);
+      _readState.readFieldBeginNoInline(iprot);
+      goto _loop;
+    }
+  }
+}
+
+template <class Protocol_>
+uint32_t AccessStats::serializedSize(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("AccessStats");
+  xfer += prot_->serializedFieldSize("totalHits", apache::thrift::protocol::T_BYTE, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::serializedSize<false>(*prot_, this->totalHits);
+  xfer += prot_->serializedFieldSize("currHits", apache::thrift::protocol::T_BYTE, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::serializedSize<false>(*prot_, this->currHits);
+  xfer += prot_->serializedFieldSize("numReinsertions", apache::thrift::protocol::T_BYTE, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::serializedSize<false>(*prot_, this->numReinsertions);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t AccessStats::serializedSizeZC(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("AccessStats");
+  xfer += prot_->serializedFieldSize("totalHits", apache::thrift::protocol::T_BYTE, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::serializedSize<false>(*prot_, this->totalHits);
+  xfer += prot_->serializedFieldSize("currHits", apache::thrift::protocol::T_BYTE, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::serializedSize<false>(*prot_, this->currHits);
+  xfer += prot_->serializedFieldSize("numReinsertions", apache::thrift::protocol::T_BYTE, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::serializedSize<false>(*prot_, this->numReinsertions);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t AccessStats::write(Protocol_* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->writeStructBegin("AccessStats");
+  xfer += prot_->writeFieldBegin("totalHits", apache::thrift::protocol::T_BYTE, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::write(*prot_, this->totalHits);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("currHits", apache::thrift::protocol::T_BYTE, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::write(*prot_, this->currHits);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("numReinsertions", apache::thrift::protocol::T_BYTE, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int8_t>::write(*prot_, this->numReinsertions);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldStop();
+  xfer += prot_->writeStructEnd();
+  return xfer;
+}
+
+extern template void AccessStats::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+extern template uint32_t AccessStats::write<>(apache::thrift::BinaryProtocolWriter*) const;
+extern template uint32_t AccessStats::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template uint32_t AccessStats::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template void AccessStats::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+extern template uint32_t AccessStats::write<>(apache::thrift::CompactProtocolWriter*) const;
+extern template uint32_t AccessStats::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+extern template uint32_t AccessStats::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+template <class Protocol_>
+void AccessTracker::readNoXfer(Protocol_* iprot) {
+  apache::thrift::detail::ProtocolReaderStructReadState<Protocol_> _readState;
+
+  _readState.readStructBegin(iprot);
+
+  using apache::thrift::TProtocolException;
+
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          0,
+          1,
+          apache::thrift::protocol::T_MAP))) {
+    goto _loop;
+  }
+_readField_data:
+  {
+    _readState.beforeSubobject(iprot);
+    
+    this->data = ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>();
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::structure>, ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>>::read(*iprot, this->data);
+    this->__isset.data = true;
+    _readState.afterSubobject(iprot);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          1,
+          0,
+          apache::thrift::protocol::T_STOP))) {
+    goto _loop;
+  }
+
+_end:
+  _readState.readStructEnd(iprot);
+
+  return;
+
+_loop:
+  _readState.afterAdvanceFailure(iprot);
+  if (_readState.atStop()) {
+    goto _end;
+  }
+  if (iprot->kUsesFieldNames()) {
+    _readState.template fillFieldTraitsFromName<apache::thrift::detail::TccStructTraits<AccessTracker>>();
+  }
+
+  switch (_readState.fieldId) {
+    case 1:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_MAP))) {
+        goto _readField_data;
+      } else {
+        goto _skip;
+      }
+    }
+    default:
+    {
+_skip:
+      _readState.skip(iprot);
+      _readState.readFieldEnd(iprot);
+      _readState.readFieldBeginNoInline(iprot);
+      goto _loop;
+    }
+  }
+}
+
+template <class Protocol_>
+uint32_t AccessTracker::serializedSize(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("AccessTracker");
+  xfer += prot_->serializedFieldSize("data", apache::thrift::protocol::T_MAP, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::structure>, ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>>::serializedSize<false>(*prot_, this->data);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t AccessTracker::serializedSizeZC(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("AccessTracker");
+  xfer += prot_->serializedFieldSize("data", apache::thrift::protocol::T_MAP, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::structure>, ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>>::serializedSize<false>(*prot_, this->data);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t AccessTracker::write(Protocol_* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->writeStructBegin("AccessTracker");
+  xfer += prot_->writeFieldBegin("data", apache::thrift::protocol::T_MAP, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::structure>, ::std::map<int64_t,  ::facebook::cachelib::navy::serialization::AccessStats>>::write(*prot_, this->data);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldStop();
+  xfer += prot_->writeStructEnd();
+  return xfer;
+}
+
+extern template void AccessTracker::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+extern template uint32_t AccessTracker::write<>(apache::thrift::BinaryProtocolWriter*) const;
+extern template uint32_t AccessTracker::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template uint32_t AccessTracker::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template void AccessTracker::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+extern template uint32_t AccessTracker::write<>(apache::thrift::CompactProtocolWriter*) const;
+extern template uint32_t AccessTracker::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+extern template uint32_t AccessTracker::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+template <class Protocol_>
+void BlockCacheConfig::readNoXfer(Protocol_* iprot) {
+  apache::thrift::detail::ProtocolReaderStructReadState<Protocol_> _readState;
+
+  _readState.readStructBegin(iprot);
+
+  using apache::thrift::TProtocolException;
+
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          0,
+          1,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_version:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->version, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          1,
+          2,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_cacheBaseOffset:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->cacheBaseOffset, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          2,
+          3,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_cacheSize:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->cacheSize, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          3,
+          4,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_blockSize:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->blockSize, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          4,
+          5,
+          apache::thrift::protocol::T_SET))) {
+    goto _loop;
+  }
+_readField_sizeClasses:
+  {
+    _readState.beforeSubobject(iprot);
+    
+    this->sizeClasses = ::std::set<int32_t>();
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::set<::apache::thrift::type_class::integral>, ::std::set<int32_t>>::read(*iprot, this->sizeClasses);
+    _readState.afterSubobject(iprot);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          5,
+          6,
+          apache::thrift::protocol::T_BOOL))) {
+    goto _loop;
+  }
+_readField_checksum:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, bool>::readWithContext(*iprot, this->checksum, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          6,
+          7,
+          apache::thrift::protocol::T_MAP))) {
+    goto _loop;
+  }
+_readField_sizeDist:
+  {
+    _readState.beforeSubobject(iprot);
+    
+    this->sizeDist = ::std::map<int64_t, int64_t>();
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::integral>, ::std::map<int64_t, int64_t>>::read(*iprot, this->sizeDist);
+    this->__isset.sizeDist = true;
+    _readState.afterSubobject(iprot);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          7,
+          8,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_holeCount:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->holeCount, _readState);
+    this->__isset.holeCount = true;
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          8,
+          9,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_holeSizeTotal:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->holeSizeTotal, _readState);
+    this->__isset.holeSizeTotal = true;
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          9,
+          10,
+          apache::thrift::protocol::T_BOOL))) {
+    goto _loop;
+  }
+_readField_reinsertionPolicyEnabled:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, bool>::readWithContext(*iprot, this->reinsertionPolicyEnabled, _readState);
+    this->__isset.reinsertionPolicyEnabled = true;
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          10,
+          0,
+          apache::thrift::protocol::T_STOP))) {
+    goto _loop;
+  }
+
+_end:
+  _readState.readStructEnd(iprot);
+
+  return;
+
+_loop:
+  _readState.afterAdvanceFailure(iprot);
+  if (_readState.atStop()) {
+    goto _end;
+  }
+  if (iprot->kUsesFieldNames()) {
+    _readState.template fillFieldTraitsFromName<apache::thrift::detail::TccStructTraits<BlockCacheConfig>>();
+  }
+
+  switch (_readState.fieldId) {
+    case 1:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_version;
+      } else {
+        goto _skip;
+      }
+    }
+    case 2:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_cacheBaseOffset;
+      } else {
+        goto _skip;
+      }
+    }
+    case 3:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_cacheSize;
+      } else {
+        goto _skip;
+      }
+    }
+    case 4:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_blockSize;
+      } else {
+        goto _skip;
+      }
+    }
+    case 5:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_SET))) {
+        goto _readField_sizeClasses;
+      } else {
+        goto _skip;
+      }
+    }
+    case 6:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_BOOL))) {
+        goto _readField_checksum;
+      } else {
+        goto _skip;
+      }
+    }
+    case 7:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_MAP))) {
+        goto _readField_sizeDist;
+      } else {
+        goto _skip;
+      }
+    }
+    case 8:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_holeCount;
+      } else {
+        goto _skip;
+      }
+    }
+    case 9:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_holeSizeTotal;
+      } else {
+        goto _skip;
+      }
+    }
+    case 10:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_BOOL))) {
+        goto _readField_reinsertionPolicyEnabled;
+      } else {
+        goto _skip;
+      }
+    }
+    default:
+    {
+_skip:
+      _readState.skip(iprot);
+      _readState.readFieldEnd(iprot);
+      _readState.readFieldBeginNoInline(iprot);
+      goto _loop;
+    }
+  }
+}
+
+template <class Protocol_>
+uint32_t BlockCacheConfig::serializedSize(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("BlockCacheConfig");
+  xfer += prot_->serializedFieldSize("version", apache::thrift::protocol::T_I64, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->version);
+  xfer += prot_->serializedFieldSize("cacheBaseOffset", apache::thrift::protocol::T_I64, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->cacheBaseOffset);
+  xfer += prot_->serializedFieldSize("cacheSize", apache::thrift::protocol::T_I64, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->cacheSize);
+  xfer += prot_->serializedFieldSize("blockSize", apache::thrift::protocol::T_I32, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->blockSize);
+  xfer += prot_->serializedFieldSize("sizeClasses", apache::thrift::protocol::T_SET, 5);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::set<::apache::thrift::type_class::integral>, ::std::set<int32_t>>::serializedSize<false>(*prot_, this->sizeClasses);
+  xfer += prot_->serializedFieldSize("checksum", apache::thrift::protocol::T_BOOL, 6);
+  xfer += prot_->serializedSizeBool(this->checksum);
+  xfer += prot_->serializedFieldSize("sizeDist", apache::thrift::protocol::T_MAP, 7);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::integral>, ::std::map<int64_t, int64_t>>::serializedSize<false>(*prot_, this->sizeDist);
+  xfer += prot_->serializedFieldSize("holeCount", apache::thrift::protocol::T_I64, 8);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->holeCount);
+  xfer += prot_->serializedFieldSize("holeSizeTotal", apache::thrift::protocol::T_I64, 9);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->holeSizeTotal);
+  xfer += prot_->serializedFieldSize("reinsertionPolicyEnabled", apache::thrift::protocol::T_BOOL, 10);
+  xfer += prot_->serializedSizeBool(this->reinsertionPolicyEnabled);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t BlockCacheConfig::serializedSizeZC(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("BlockCacheConfig");
+  xfer += prot_->serializedFieldSize("version", apache::thrift::protocol::T_I64, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->version);
+  xfer += prot_->serializedFieldSize("cacheBaseOffset", apache::thrift::protocol::T_I64, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->cacheBaseOffset);
+  xfer += prot_->serializedFieldSize("cacheSize", apache::thrift::protocol::T_I64, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->cacheSize);
+  xfer += prot_->serializedFieldSize("blockSize", apache::thrift::protocol::T_I32, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->blockSize);
+  xfer += prot_->serializedFieldSize("sizeClasses", apache::thrift::protocol::T_SET, 5);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::set<::apache::thrift::type_class::integral>, ::std::set<int32_t>>::serializedSize<false>(*prot_, this->sizeClasses);
+  xfer += prot_->serializedFieldSize("checksum", apache::thrift::protocol::T_BOOL, 6);
+  xfer += prot_->serializedSizeBool(this->checksum);
+  xfer += prot_->serializedFieldSize("sizeDist", apache::thrift::protocol::T_MAP, 7);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::integral>, ::std::map<int64_t, int64_t>>::serializedSize<false>(*prot_, this->sizeDist);
+  xfer += prot_->serializedFieldSize("holeCount", apache::thrift::protocol::T_I64, 8);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->holeCount);
+  xfer += prot_->serializedFieldSize("holeSizeTotal", apache::thrift::protocol::T_I64, 9);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->holeSizeTotal);
+  xfer += prot_->serializedFieldSize("reinsertionPolicyEnabled", apache::thrift::protocol::T_BOOL, 10);
+  xfer += prot_->serializedSizeBool(this->reinsertionPolicyEnabled);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t BlockCacheConfig::write(Protocol_* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->writeStructBegin("BlockCacheConfig");
+  xfer += prot_->writeFieldBegin("version", apache::thrift::protocol::T_I64, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->version);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("cacheBaseOffset", apache::thrift::protocol::T_I64, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->cacheBaseOffset);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("cacheSize", apache::thrift::protocol::T_I64, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->cacheSize);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("blockSize", apache::thrift::protocol::T_I32, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->blockSize);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("sizeClasses", apache::thrift::protocol::T_SET, 5);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::set<::apache::thrift::type_class::integral>, ::std::set<int32_t>>::write(*prot_, this->sizeClasses);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("checksum", apache::thrift::protocol::T_BOOL, 6);
+  xfer += prot_->writeBool(this->checksum);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("sizeDist", apache::thrift::protocol::T_MAP, 7);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::integral>, ::std::map<int64_t, int64_t>>::write(*prot_, this->sizeDist);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("holeCount", apache::thrift::protocol::T_I64, 8);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->holeCount);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("holeSizeTotal", apache::thrift::protocol::T_I64, 9);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->holeSizeTotal);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("reinsertionPolicyEnabled", apache::thrift::protocol::T_BOOL, 10);
+  xfer += prot_->writeBool(this->reinsertionPolicyEnabled);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldStop();
+  xfer += prot_->writeStructEnd();
+  return xfer;
+}
+
+extern template void BlockCacheConfig::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+extern template uint32_t BlockCacheConfig::write<>(apache::thrift::BinaryProtocolWriter*) const;
+extern template uint32_t BlockCacheConfig::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template uint32_t BlockCacheConfig::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template void BlockCacheConfig::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+extern template uint32_t BlockCacheConfig::write<>(apache::thrift::CompactProtocolWriter*) const;
+extern template uint32_t BlockCacheConfig::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+extern template uint32_t BlockCacheConfig::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+template <class Protocol_>
+void BigHashPersistentData::readNoXfer(Protocol_* iprot) {
+  apache::thrift::detail::ProtocolReaderStructReadState<Protocol_> _readState;
+
+  _readState.readStructBegin(iprot);
+
+  using apache::thrift::TProtocolException;
+
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          0,
+          1,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_version:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->version, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          1,
+          2,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_generationTime:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->generationTime, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          2,
+          3,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_itemCount:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->itemCount, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          3,
+          4,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_bucketSize:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->bucketSize, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          4,
+          5,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_cacheBaseOffset:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->cacheBaseOffset, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          5,
+          6,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_numBuckets:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->numBuckets, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          6,
+          7,
+          apache::thrift::protocol::T_MAP))) {
+    goto _loop;
+  }
+_readField_sizeDist:
+  {
+    _readState.beforeSubobject(iprot);
+    
+    this->sizeDist = ::std::map<int64_t, int64_t>();
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::integral>, ::std::map<int64_t, int64_t>>::read(*iprot, this->sizeDist);
+    this->__isset.sizeDist = true;
+    _readState.afterSubobject(iprot);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          7,
+          0,
+          apache::thrift::protocol::T_STOP))) {
+    goto _loop;
+  }
+
+_end:
+  _readState.readStructEnd(iprot);
+
+  return;
+
+_loop:
+  _readState.afterAdvanceFailure(iprot);
+  if (_readState.atStop()) {
+    goto _end;
+  }
+  if (iprot->kUsesFieldNames()) {
+    _readState.template fillFieldTraitsFromName<apache::thrift::detail::TccStructTraits<BigHashPersistentData>>();
+  }
+
+  switch (_readState.fieldId) {
+    case 1:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_version;
+      } else {
+        goto _skip;
+      }
+    }
+    case 2:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_generationTime;
+      } else {
+        goto _skip;
+      }
+    }
+    case 3:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_itemCount;
+      } else {
+        goto _skip;
+      }
+    }
+    case 4:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_bucketSize;
+      } else {
+        goto _skip;
+      }
+    }
+    case 5:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_cacheBaseOffset;
+      } else {
+        goto _skip;
+      }
+    }
+    case 6:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_numBuckets;
+      } else {
+        goto _skip;
+      }
+    }
+    case 7:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_MAP))) {
+        goto _readField_sizeDist;
+      } else {
+        goto _skip;
+      }
+    }
+    default:
+    {
+_skip:
+      _readState.skip(iprot);
+      _readState.readFieldEnd(iprot);
+      _readState.readFieldBeginNoInline(iprot);
+      goto _loop;
+    }
+  }
+}
+
+template <class Protocol_>
+uint32_t BigHashPersistentData::serializedSize(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("BigHashPersistentData");
+  xfer += prot_->serializedFieldSize("version", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->version);
+  xfer += prot_->serializedFieldSize("generationTime", apache::thrift::protocol::T_I64, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->generationTime);
+  xfer += prot_->serializedFieldSize("itemCount", apache::thrift::protocol::T_I64, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->itemCount);
+  xfer += prot_->serializedFieldSize("bucketSize", apache::thrift::protocol::T_I64, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->bucketSize);
+  xfer += prot_->serializedFieldSize("cacheBaseOffset", apache::thrift::protocol::T_I64, 5);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->cacheBaseOffset);
+  xfer += prot_->serializedFieldSize("numBuckets", apache::thrift::protocol::T_I64, 6);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->numBuckets);
+  xfer += prot_->serializedFieldSize("sizeDist", apache::thrift::protocol::T_MAP, 7);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::integral>, ::std::map<int64_t, int64_t>>::serializedSize<false>(*prot_, this->sizeDist);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t BigHashPersistentData::serializedSizeZC(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("BigHashPersistentData");
+  xfer += prot_->serializedFieldSize("version", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->version);
+  xfer += prot_->serializedFieldSize("generationTime", apache::thrift::protocol::T_I64, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->generationTime);
+  xfer += prot_->serializedFieldSize("itemCount", apache::thrift::protocol::T_I64, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->itemCount);
+  xfer += prot_->serializedFieldSize("bucketSize", apache::thrift::protocol::T_I64, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->bucketSize);
+  xfer += prot_->serializedFieldSize("cacheBaseOffset", apache::thrift::protocol::T_I64, 5);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->cacheBaseOffset);
+  xfer += prot_->serializedFieldSize("numBuckets", apache::thrift::protocol::T_I64, 6);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->numBuckets);
+  xfer += prot_->serializedFieldSize("sizeDist", apache::thrift::protocol::T_MAP, 7);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::integral>, ::std::map<int64_t, int64_t>>::serializedSize<false>(*prot_, this->sizeDist);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t BigHashPersistentData::write(Protocol_* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->writeStructBegin("BigHashPersistentData");
+  xfer += prot_->writeFieldBegin("version", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->version);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("generationTime", apache::thrift::protocol::T_I64, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->generationTime);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("itemCount", apache::thrift::protocol::T_I64, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->itemCount);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("bucketSize", apache::thrift::protocol::T_I64, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->bucketSize);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("cacheBaseOffset", apache::thrift::protocol::T_I64, 5);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->cacheBaseOffset);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("numBuckets", apache::thrift::protocol::T_I64, 6);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->numBuckets);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("sizeDist", apache::thrift::protocol::T_MAP, 7);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::map<::apache::thrift::type_class::integral, ::apache::thrift::type_class::integral>, ::std::map<int64_t, int64_t>>::write(*prot_, this->sizeDist);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldStop();
+  xfer += prot_->writeStructEnd();
+  return xfer;
+}
+
+extern template void BigHashPersistentData::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+extern template uint32_t BigHashPersistentData::write<>(apache::thrift::BinaryProtocolWriter*) const;
+extern template uint32_t BigHashPersistentData::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template uint32_t BigHashPersistentData::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template void BigHashPersistentData::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+extern template uint32_t BigHashPersistentData::write<>(apache::thrift::CompactProtocolWriter*) const;
+extern template uint32_t BigHashPersistentData::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+extern template uint32_t BigHashPersistentData::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
+namespace facebook { namespace cachelib { namespace navy { namespace serialization {
+
+template <class Protocol_>
+void BloomFilterPersistentData::readNoXfer(Protocol_* iprot) {
+  apache::thrift::detail::ProtocolReaderStructReadState<Protocol_> _readState;
+
+  _readState.readStructBegin(iprot);
+
+  using apache::thrift::TProtocolException;
+
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          0,
+          1,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_numFilters:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->numFilters, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          1,
+          2,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_hashTableBitSize:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->hashTableBitSize, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          2,
+          3,
+          apache::thrift::protocol::T_I64))) {
+    goto _loop;
+  }
+_readField_filterByteSize:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::readWithContext(*iprot, this->filterByteSize, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          3,
+          4,
+          apache::thrift::protocol::T_I32))) {
+    goto _loop;
+  }
+_readField_fragmentSize:
+  {
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::readWithContext(*iprot, this->fragmentSize, _readState);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          4,
+          5,
+          apache::thrift::protocol::T_LIST))) {
+    goto _loop;
+  }
+_readField_seeds:
+  {
+    _readState.beforeSubobject(iprot);
+    
+    this->seeds = ::std::vector<int64_t>();
+    ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::integral>, ::std::vector<int64_t>>::read(*iprot, this->seeds);
+    _readState.afterSubobject(iprot);
+  }
+
+  if (UNLIKELY(!_readState.advanceToNextField(
+          iprot,
+          5,
+          0,
+          apache::thrift::protocol::T_STOP))) {
+    goto _loop;
+  }
+
+_end:
+  _readState.readStructEnd(iprot);
+
+  return;
+
+_loop:
+  _readState.afterAdvanceFailure(iprot);
+  if (_readState.atStop()) {
+    goto _end;
+  }
+  if (iprot->kUsesFieldNames()) {
+    _readState.template fillFieldTraitsFromName<apache::thrift::detail::TccStructTraits<BloomFilterPersistentData>>();
+  }
+
+  switch (_readState.fieldId) {
+    case 1:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_numFilters;
+      } else {
+        goto _skip;
+      }
+    }
+    case 2:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_hashTableBitSize;
+      } else {
+        goto _skip;
+      }
+    }
+    case 3:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I64))) {
+        goto _readField_filterByteSize;
+      } else {
+        goto _skip;
+      }
+    }
+    case 4:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_I32))) {
+        goto _readField_fragmentSize;
+      } else {
+        goto _skip;
+      }
+    }
+    case 5:
+    {
+      if (LIKELY(_readState.isCompatibleWithType(iprot, apache::thrift::protocol::T_LIST))) {
+        goto _readField_seeds;
+      } else {
+        goto _skip;
+      }
+    }
+    default:
+    {
+_skip:
+      _readState.skip(iprot);
+      _readState.readFieldEnd(iprot);
+      _readState.readFieldBeginNoInline(iprot);
+      goto _loop;
+    }
+  }
+}
+
+template <class Protocol_>
+uint32_t BloomFilterPersistentData::serializedSize(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("BloomFilterPersistentData");
+  xfer += prot_->serializedFieldSize("numFilters", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->numFilters);
+  xfer += prot_->serializedFieldSize("hashTableBitSize", apache::thrift::protocol::T_I64, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->hashTableBitSize);
+  xfer += prot_->serializedFieldSize("filterByteSize", apache::thrift::protocol::T_I64, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->filterByteSize);
+  xfer += prot_->serializedFieldSize("fragmentSize", apache::thrift::protocol::T_I32, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->fragmentSize);
+  xfer += prot_->serializedFieldSize("seeds", apache::thrift::protocol::T_LIST, 5);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::integral>, ::std::vector<int64_t>>::serializedSize<false>(*prot_, this->seeds);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t BloomFilterPersistentData::serializedSizeZC(Protocol_ const* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->serializedStructSize("BloomFilterPersistentData");
+  xfer += prot_->serializedFieldSize("numFilters", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->numFilters);
+  xfer += prot_->serializedFieldSize("hashTableBitSize", apache::thrift::protocol::T_I64, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->hashTableBitSize);
+  xfer += prot_->serializedFieldSize("filterByteSize", apache::thrift::protocol::T_I64, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::serializedSize<false>(*prot_, this->filterByteSize);
+  xfer += prot_->serializedFieldSize("fragmentSize", apache::thrift::protocol::T_I32, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::serializedSize<false>(*prot_, this->fragmentSize);
+  xfer += prot_->serializedFieldSize("seeds", apache::thrift::protocol::T_LIST, 5);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::integral>, ::std::vector<int64_t>>::serializedSize<false>(*prot_, this->seeds);
+  xfer += prot_->serializedSizeStop();
+  return xfer;
+}
+
+template <class Protocol_>
+uint32_t BloomFilterPersistentData::write(Protocol_* prot_) const {
+  uint32_t xfer = 0;
+  xfer += prot_->writeStructBegin("BloomFilterPersistentData");
+  xfer += prot_->writeFieldBegin("numFilters", apache::thrift::protocol::T_I32, 1);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->numFilters);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("hashTableBitSize", apache::thrift::protocol::T_I64, 2);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->hashTableBitSize);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("filterByteSize", apache::thrift::protocol::T_I64, 3);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int64_t>::write(*prot_, this->filterByteSize);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("fragmentSize", apache::thrift::protocol::T_I32, 4);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::integral, int32_t>::write(*prot_, this->fragmentSize);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldBegin("seeds", apache::thrift::protocol::T_LIST, 5);
+  xfer += ::apache::thrift::detail::pm::protocol_methods< ::apache::thrift::type_class::list<::apache::thrift::type_class::integral>, ::std::vector<int64_t>>::write(*prot_, this->seeds);
+  xfer += prot_->writeFieldEnd();
+  xfer += prot_->writeFieldStop();
+  xfer += prot_->writeStructEnd();
+  return xfer;
+}
+
+extern template void BloomFilterPersistentData::readNoXfer<>(apache::thrift::BinaryProtocolReader*);
+extern template uint32_t BloomFilterPersistentData::write<>(apache::thrift::BinaryProtocolWriter*) const;
+extern template uint32_t BloomFilterPersistentData::serializedSize<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template uint32_t BloomFilterPersistentData::serializedSizeZC<>(apache::thrift::BinaryProtocolWriter const*) const;
+extern template void BloomFilterPersistentData::readNoXfer<>(apache::thrift::CompactProtocolReader*);
+extern template uint32_t BloomFilterPersistentData::write<>(apache::thrift::CompactProtocolWriter*) const;
+extern template uint32_t BloomFilterPersistentData::serializedSize<>(apache::thrift::CompactProtocolWriter const*) const;
+extern template uint32_t BloomFilterPersistentData::serializedSizeZC<>(apache::thrift::CompactProtocolWriter const*) const;
+
+}}}} // facebook::cachelib::navy::serialization
diff --git a/cachelib/navy/serialization/gen-cpp2/objects_types_custom_protocol.h b/cachelib/navy/serialization/gen-cpp2/objects_types_custom_protocol.h
new file mode 100644
index 0000000000..cfbdccfa7e
--- /dev/null
+++ b/cachelib/navy/serialization/gen-cpp2/objects_types_custom_protocol.h
@@ -0,0 +1,18 @@
+/**
+ * Autogenerated by Thrift
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+#pragma once
+
+
+/**
+ * This header file includes the tcc files of the corresponding header file
+ * and the header files of its dependent types. Include this header file
+ * only when you need to use custom protocols (e.g. DebugProtocol,
+ * VirtualProtocol) to read/write thrift structs.
+ */
+
+#include "/h/bsberg/CacheLib2/cachelib/navy/serialization/gen-cpp2/objects_types.tcc"
+
diff --git a/cachelib/navy/serialization/objects.thrift b/cachelib/navy/serialization/navy_objects.thrift
similarity index 100%
rename from cachelib/navy/serialization/objects.thrift
rename to cachelib/navy/serialization/navy_objects.thrift
diff --git a/cachelib/shm/CMakeLists.txt b/cachelib/shm/CMakeLists.txt
new file mode 100644
index 0000000000..d0b2cc55ea
--- /dev/null
+++ b/cachelib/shm/CMakeLists.txt
@@ -0,0 +1,58 @@
+#https://github.com/facebook/fbthrift/blob/master/ThriftLibrary.cmake lines 88 to 121
+thrift_object("shm" "" "cpp2" "" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" THRIFT_INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR})
+add_library(
+  cachelib_shm
+
+  PosixShmSegment.h
+  Shm.h
+  ShmCommon.h
+  ShmManager.h
+  SysVShmSegment.h
+  PosixShmSegment.cpp
+  ShmCommon.cpp
+  ShmManager.cpp
+  SysVShmSegment.cpp
+  CMakeLists.txt
+  $<TARGET_OBJECTS:shm-cpp2-obj> 
+  )
+
+target_link_libraries(
+  cachelib_shm
+
+  PUBLIC
+    cachelib_common
+    ${RT_LIBRARIES}
+    ${FOLLY_LIBRARIES}
+    ${FBTHRIFT_LIBRARIES}
+)
+
+add_executable(
+        cachelib_shm_test_bin
+
+        tests/common.h
+        tests/common.cpp
+        tests/test_page_size.cpp
+        tests/test_posix.cpp
+        tests/test_shm.cpp
+        tests/test_shm_manager.cpp
+        tests/test_sysv.cpp
+        )
+
+target_link_libraries(
+        cachelib_shm_test_bin
+
+        PUBLIC
+            navy
+            cachelib_allocator
+            cachelib_common
+            GTest::GTest
+            GTest::Main
+            ${GMOCK_LIBRARIES}
+            ${FOLLY_LIBRARIES}
+)
+
+add_test(
+        NAME cachelib_shm_tests
+        COMMAND cachelib_shm_test_bin
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
diff --git a/install.sh b/install.sh
new file mode 100755
index 0000000000..57f22bc0dc
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,159 @@
+#!/bin/bash
+BUILD_DIR=${1:-"$HOME"}
+
+pushd ${BUILD_DIR}
+#basic install
+apt -y update
+apt -y htop
+apt-get install -y software-properties-common
+add-apt-repository -y ppa:ubuntu-toolchain-r/test
+apt -y update
+apt -y install g++
+apt install g++-7 -y
+apt install -y make
+
+#cmake
+curl -O -L https://github.com/Kitware/CMake/releases/download/v3.17.0/cmake-3.17.0-Linux-x86_64.tar.gz
+curl -O -L https://github.com/Kitware/CMake/releases/download/v3.17.0/cmake-3.17.0-Linux-x86_64.sh 
+chmod +x cmake-3.17.0-Linux-x86_64.sh && ./cmake-3.17.0-Linux-x86_64.sh --skip-license --prefix=/usr/local
+
+#gtest
+wget https://github.com/google/googletest/archive/release-1.8.0.tar.gz && \
+tar zxf release-1.8.0.tar.gz && \
+rm -f release-1.8.0.tar.gz && \
+pushd googletest-release-1.8.0 && \
+cmake . && \
+make && \
+make install
+popd
+#secret incantation
+export GLOG_LIBRARYDIR="/usr/local/lib"
+
+#glog
+apt -y install bison libboost-all-dev flex zlib1g libgflags-dev
+[[ -d glog ]] || git clone https://github.com/google/glog.git || exit 1
+pushd glog
+[[ -d build ]] || mkdir build
+cd build
+cmake -DCMAKE_CXX_COMPILER=g++-7 ..
+make -j $(nproc)
+make install
+popd
+
+#folly deps
+sudo apt-get -y install \
+    g++ \
+    libboost-all-dev \
+    libevent-dev \
+    libdouble-conversion-dev \
+    libgflags-dev \
+    libiberty-dev \
+    liblz4-dev \
+    liblzma-dev \
+    libsnappy-dev \
+    make \
+    zlib1g-dev \
+    binutils-dev \
+    libjemalloc-dev \
+    libssl-dev \
+    pkg-config \
+    libunwind-dev
+
+#fmt
+##fmt depts
+apt -y install libsodium-dev
+##
+[[ -d fmt ]] || git clone https://github.com/fmtlib/fmt.git || exit 1
+pushd fmt
+
+[[ -d _build ]] || mkdir _build
+cd _build
+cmake ..
+
+make -j$(nproc)
+make install
+popd
+#rm -rf fmt
+
+#folly
+[[ -d folly ]] || git clone https://github.com/facebook/folly.git || exit 1
+pushd folly
+[[ -d _build ]] || mkdir _build
+cd _build
+cmake -DCMAKE_CXX_COMPILER=g++-7 -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=ON ..
+make -j $(nproc)
+make install
+popd
+
+#fbthrift deps
+
+
+#rsocket-cpp
+[[ -d rsocket-cpp ]] || git clone https://github.com/rsocket/rsocket-cpp.git | exit 1
+pushd rsocket-cpp
+mkdir -p build
+cd build
+cmake ..
+make -j $(nproc)
+make install
+popd
+
+## fizz
+sudo apt-get -y install \
+    g++ \
+    libboost-all-dev \
+    libevent-dev \
+    libdouble-conversion-dev \
+    libgflags-dev \
+    libiberty-dev \
+    liblz4-dev \
+    liblzma-dev \
+    libsnappy-dev \
+    make \
+    zlib1g-dev \
+    binutils-dev \
+    libjemalloc-dev \
+    libssl-dev \
+    pkg-config \
+    libsodium-dev
+
+[[ -d fizz ]] || git clone https://github.com/facebookincubator/fizz || exit 1
+pushd fizz
+[[ -d build_ ]] || mkdir build_
+cd build_
+cmake ../fizz
+make -j $(nproc)
+sudo make install
+popd
+
+##wangle
+[[ -d wangle ]] || git clone https://github.com/facebook/wangle.git || exit 1
+pushd wangle/wangle
+cmake -DCMAKE_CXX_COMPILER=g++-7 .
+make -j $(nproc)
+sudo make install
+popd
+
+##
+[[ -d zstd ]] || git clone https://github.com/facebook/zstd.git || exit 1
+pushd zstd
+make -j $(nproc)
+make install
+popd
+
+#fbthrift
+[[ -d fbthrift ]] || git clone https://github.com/facebook/fbthrift || exit 1
+pushd fbthrift
+cd build
+cmake -DCMAKE_CXX_COMPILER=g++-7 ..
+make -j $(nproc)
+make install
+popd
+
+#build cachelib!
+
+popd
+[[ -d build ]] || mkdir build
+pushd build
+cmake -Wno-dev -DCMAKE_CXX_COMPILER=g++-7 .. && make -j $(nproc)
+popd
