You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by vm...@apache.org on 2021/04/29 04:03:56 UTC

[trafficserver] branch master updated: Experimental Cache fill plugin (#7470)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 8ac0019  Experimental Cache fill plugin (#7470)
8ac0019 is described below

commit 8ac00199deef6c40073de61661bb131de5e8fd5e
Author: Vijay Mamidi <vi...@yahoo.com>
AuthorDate: Wed Apr 28 21:03:38 2021 -0700

    Experimental Cache fill plugin (#7470)
    
    * Experimental Cache fill plugin
    
    * remove trailing whitespace
    
    * Add documentation
    
    * Remove trailing whitespace
---
 doc/admin-guide/plugins/cache_fill.en.rst          |  46 ++++
 doc/admin-guide/plugins/index.en.rst               |   1 +
 plugins/Makefile.am                                |   1 +
 plugins/experimental/cache_fill/Makefile.inc       |  21 ++
 .../experimental/cache_fill/background_fetch.cc    | 299 +++++++++++++++++++++
 plugins/experimental/cache_fill/background_fetch.h | 181 +++++++++++++
 plugins/experimental/cache_fill/cache_fill.cc      | 185 +++++++++++++
 7 files changed, 734 insertions(+)

diff --git a/doc/admin-guide/plugins/cache_fill.en.rst b/doc/admin-guide/plugins/cache_fill.en.rst
new file mode 100644
index 0000000..2f22c38
--- /dev/null
+++ b/doc/admin-guide/plugins/cache_fill.en.rst
@@ -0,0 +1,46 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+   agreements.  See the NOTICE file distributed with this work for additional information regarding
+   copyright ownership.  The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with the License.  You may obtain
+   a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software distributed under the License
+   is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+   or implied.  See the License for the specific language governing permissions and limitations
+   under the License.
+
+.. _admin-plugins-cache-fill.so:
+.. include:: /common.defs
+
+Cache Fill Plugin
+***********************
+
+The speed of the response served from the cache depends on the cache speed and the client filling the object.
+This dependency could significantly impact all the clients requesting the object.
+This plugin tries to eliminate the dependence by making the original request spawn a background request to fill the cache.
+The initial version of this plugin relays the initial request to the origin server instead of waiting for the background request to start filling the cache as there is no easier way to find the wait time.
+This plugin doesn't provide any improvement for smaller objects but could also degrade the performance as two outgoing requests for every cache update.
+
+
+Using the plugin
+----------------
+
+This plugin functions as a per remap plugin.
+
+To activate the plugin, in :file:`remap.config`, simply append the
+below to the specific remap line::
+
+   @plugin=cache_fill.so @pparam=<config-file>
+
+Functionality
+-------------
+
+Plugin decides to trigger a background fetch of the original (Client) request if the request/response is cacheable and cache status is TS_CACHE_LOOKUP_MISS/TS_CACHE_LOOKUP_HIT_STALE.
+
+Future additions
+----------------
+
+*  Fetching the original request from the cache.
+
diff --git a/doc/admin-guide/plugins/index.en.rst b/doc/admin-guide/plugins/index.en.rst
index c9037c9..b214b04 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -147,6 +147,7 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi
    :hidden:
 
    Access Control <access_control.en>
+   Cache Fill <cache_fill.en>
    Certifier <certifier.en>
    Cert Reporting Tool <cert_reporting_tool.en>
    Collapsed-Forwarding <collapsed_forwarding.en>
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 76addc5..75876ae 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -61,6 +61,7 @@ if BUILD_EXPERIMENTAL_PLUGINS
 
 include experimental/access_control/Makefile.inc
 include experimental/acme/Makefile.inc
+include experimental/cache_fill/Makefile.inc
 include experimental/cert_reporting_tool/Makefile.inc
 include experimental/collapsed_forwarding/Makefile.inc
 include experimental/cookie_remap/Makefile.inc
diff --git a/plugins/experimental/cache_fill/Makefile.inc b/plugins/experimental/cache_fill/Makefile.inc
new file mode 100644
index 0000000..6d837d8
--- /dev/null
+++ b/plugins/experimental/cache_fill/Makefile.inc
@@ -0,0 +1,21 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+pkglib_LTLIBRARIES += experimental/cache_fill/cache_fill.la
+experimental_cache_fill_cache_fill_la_SOURCES = \
+	experimental/cache_fill/cache_fill.cc \
+	experimental/cache_fill/background_fetch.cc \
+	experimental/cache_fill/background_fetch.h
diff --git a/plugins/experimental/cache_fill/background_fetch.cc b/plugins/experimental/cache_fill/background_fetch.cc
new file mode 100644
index 0000000..99857e8
--- /dev/null
+++ b/plugins/experimental/cache_fill/background_fetch.cc
@@ -0,0 +1,299 @@
+/** @file
+
+    Plugin to perform background fetches of certain content that would
+    otherwise not be cached. For example, Range: requests / responses.
+
+    @section license License
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <cstdarg>
+
+#include <string>
+#include <iostream>
+#include <unordered_map>
+#include <cinttypes>
+#include <string_view>
+#include <array>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "ts/ts.h"
+#include "ts/remap.h"
+#include "background_fetch.h"
+typedef std::unordered_map<std::string, bool> OutstandingRequests;
+
+///////////////////////////////////////////////////////////////////////////
+// Set a header to a specific value. This will avoid going to through a
+// remove / add sequence in case of an existing header.
+// but clean.
+static bool
+set_header(TSMBuffer bufp, TSMLoc hdr_loc, const char *header, int len, const char *val, int val_len)
+{
+  if (!bufp || !hdr_loc || !header || len <= 0 || !val || val_len <= 0) {
+    return false;
+  }
+
+  bool ret         = false;
+  TSMLoc field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, header, len);
+
+  if (!field_loc) {
+    // No existing header, so create one
+    if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(bufp, hdr_loc, header, len, &field_loc)) {
+      if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, val, val_len)) {
+        TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc);
+        ret = true;
+      }
+      TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+    }
+  } else {
+    TSMLoc tmp = nullptr;
+    bool first = true;
+
+    while (field_loc) {
+      if (first) {
+        first = false;
+        if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, val, val_len)) {
+          ret = true;
+        }
+      } else {
+        TSMimeHdrFieldDestroy(bufp, hdr_loc, field_loc);
+      }
+      tmp = TSMimeHdrFieldNextDup(bufp, hdr_loc, field_loc);
+      TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+      field_loc = tmp;
+    }
+  }
+
+  return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Dump a header on stderr, useful together with TSDebug().
+static void
+dump_headers(TSMBuffer bufp, TSMLoc hdr_loc)
+{
+  TSIOBuffer output_buffer;
+  TSIOBufferReader reader;
+  TSIOBufferBlock block;
+  int64_t block_avail;
+
+  output_buffer = TSIOBufferCreate();
+  reader        = TSIOBufferReaderAlloc(output_buffer);
+
+  /* This will print  just MIMEFields and not the http request line */
+  TSMimeHdrPrint(bufp, hdr_loc, output_buffer);
+
+  /* We need to loop over all the buffer blocks, there can be more than 1 */
+  block = TSIOBufferReaderStart(reader);
+  do {
+    const char *block_start = TSIOBufferBlockReadStart(block, reader, &block_avail);
+    if (block_avail > 0) {
+      TSDebug(PLUGIN_NAME, "Headers are:\n%.*s", static_cast<int>(block_avail), block_start);
+    }
+    TSIOBufferReaderConsume(reader, block_avail);
+    block = TSIOBufferReaderStart(reader);
+  } while (block && block_avail != 0);
+
+  /* Free up the TSIOBuffer that we used to print out the header */
+  TSIOBufferReaderFree(reader);
+  TSIOBufferDestroy(output_buffer);
+}
+
+bool
+BgFetchData::initialize(TSMBuffer request, TSMLoc req_hdr, TSHttpTxn txnp)
+{
+  struct sockaddr const *ip = TSHttpTxnClientAddrGet(txnp);
+  bool ret                  = false;
+
+  TSAssert(TS_NULL_MLOC == hdr_loc);
+  TSAssert(TS_NULL_MLOC == url_loc);
+
+  if (ip) {
+    if (ip->sa_family == AF_INET) {
+      memcpy(&client_ip, ip, sizeof(sockaddr_in));
+    } else if (ip->sa_family == AF_INET6) {
+      memcpy(&client_ip, ip, sizeof(sockaddr_in6));
+    } else {
+      TSError("[%s] Unknown address family %d", PLUGIN_NAME, ip->sa_family);
+    }
+  } else {
+    TSError("[%s] Failed to get client host info", PLUGIN_NAME);
+    return false;
+  }
+
+  hdr_loc = TSHttpHdrCreate(mbuf);
+  if (TS_SUCCESS == TSHttpHdrCopy(mbuf, hdr_loc, request, req_hdr)) {
+    TSMLoc p_url;
+
+    // Now copy the pristine request URL into our MBuf
+    if (TS_SUCCESS == TSHttpTxnPristineUrlGet(txnp, &request, &p_url)) {
+      if (TS_SUCCESS == TSUrlClone(mbuf, request, p_url, &url_loc)) {
+        TSMLoc c_url = TS_NULL_MLOC;
+        int len;
+        char *url = nullptr;
+
+        // Get the cache key URL (for now), since this has better lookup behavior when using
+        // e.g. the cachekey plugin.
+        if (TS_SUCCESS == TSUrlCreate(request, &c_url)) {
+          if (TS_SUCCESS == TSHttpTxnCacheLookupUrlGet(txnp, request, c_url)) {
+            url = TSUrlStringGet(request, c_url, &len);
+            TSHandleMLocRelease(request, TS_NULL_MLOC, c_url);
+            TSDebug(PLUGIN_NAME, "Cache URL is %.*s", len, url);
+          }
+        }
+
+        if (url) {
+          _url.assign(url, len); // Save away the cache URL for later use when acquiring lock
+          TSfree(static_cast<void *>(url));
+
+          if (TS_SUCCESS == TSHttpHdrUrlSet(mbuf, hdr_loc, url_loc)) {
+            // Make sure we have the correct Host: header for this request.
+            const char *hostp = TSUrlHostGet(mbuf, url_loc, &len);
+
+            if (set_header(mbuf, hdr_loc, TS_MIME_FIELD_HOST, TS_MIME_LEN_HOST, hostp, len)) {
+              TSDebug(PLUGIN_NAME, "Set header Host: %.*s", len, hostp);
+            }
+            ret = true;
+          }
+        }
+      }
+      TSHandleMLocRelease(request, TS_NULL_MLOC, p_url);
+    }
+  }
+
+  return ret;
+}
+
+static int cont_bg_fetch(TSCont contp, TSEvent event, void *edata);
+
+// Create, setup and schedule the background fetch continuation.
+void
+BgFetchData::schedule()
+{
+  TSAssert(nullptr == _cont);
+
+  // Setup the continuation
+  _cont = TSContCreate(cont_bg_fetch, TSMutexCreate());
+  TSContDataSet(_cont, static_cast<void *>(this));
+
+  // Initialize the VIO stuff (for the fetch)
+  req_io_buf         = TSIOBufferCreate();
+  req_io_buf_reader  = TSIOBufferReaderAlloc(req_io_buf);
+  resp_io_buf        = TSIOBufferCreate();
+  resp_io_buf_reader = TSIOBufferReaderAlloc(resp_io_buf);
+
+  // Schedule
+  TSContScheduleOnPool(_cont, 0, TS_THREAD_POOL_NET);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Continuation to perform a background fill of a URL. This is pretty
+// expensive (memory allocations etc.), we could eliminate maybe the
+// std::string, but I think it's fine for now.
+static int
+cont_bg_fetch(TSCont contp, TSEvent event, void * /* edata ATS_UNUSED */)
+{
+  BgFetchData *data = static_cast<BgFetchData *>(TSContDataGet(contp));
+  int64_t avail;
+
+  switch (event) {
+  case TS_EVENT_IMMEDIATE:
+  case TS_EVENT_TIMEOUT:
+    // Debug info for this particular bg fetch (put all debug in here please)
+    if (TSIsDebugTagSet(PLUGIN_NAME)) {
+      char buf[INET6_ADDRSTRLEN];
+      const sockaddr *sockaddress = reinterpret_cast<const sockaddr *>(&data->client_ip);
+
+      switch (sockaddress->sa_family) {
+      case AF_INET:
+        inet_ntop(AF_INET, &(((struct sockaddr_in *)sockaddress)->sin_addr), buf, INET_ADDRSTRLEN);
+        TSDebug(PLUGIN_NAME, "Client IPv4 = %s", buf);
+        break;
+      case AF_INET6:
+        inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sockaddress)->sin6_addr), buf, INET6_ADDRSTRLEN);
+        TSDebug(PLUGIN_NAME, "Client IPv6 = %s", buf);
+        break;
+      default:
+        TSError("[%s] Unknown address family %d", PLUGIN_NAME, sockaddress->sa_family);
+        break;
+      }
+      TSDebug(PLUGIN_NAME, "Starting background fetch, replaying:");
+      dump_headers(data->mbuf, data->hdr_loc);
+    }
+
+    // Setup the NetVC for background fetch
+    TSAssert(nullptr == data->vc);
+    if ((data->vc = TSHttpConnectWithPluginId(reinterpret_cast<sockaddr *>(&data->client_ip), PLUGIN_NAME, 0)) != nullptr) {
+      TSHttpHdrPrint(data->mbuf, data->hdr_loc, data->req_io_buf);
+      // We never send a body with the request. ToDo: Do we ever need to support that ?
+      TSIOBufferWrite(data->req_io_buf, "\r\n", 2);
+
+      data->r_vio = TSVConnRead(data->vc, contp, data->resp_io_buf, INT64_MAX);
+      data->w_vio = TSVConnWrite(data->vc, contp, data->req_io_buf_reader, TSIOBufferReaderAvail(data->req_io_buf_reader));
+    } else {
+      delete data;
+      TSError("[%s] Failed to connect to internal process, major malfunction", PLUGIN_NAME);
+    }
+    break;
+
+  case TS_EVENT_VCONN_WRITE_COMPLETE:
+    // TSVConnShutdown(data->vc, 0, 1);
+    // TSVIOReenable(data->w_vio);
+    TSDebug(PLUGIN_NAME, "Write Complete");
+    break;
+
+  case TS_EVENT_VCONN_READ_READY:
+    avail = TSIOBufferReaderAvail(data->resp_io_buf_reader);
+    data->addBytes(avail);
+    TSIOBufferReaderConsume(data->resp_io_buf_reader, avail);
+    TSVIONDoneSet(data->r_vio, TSVIONDoneGet(data->r_vio) + avail);
+    TSVIOReenable(data->r_vio);
+    break;
+
+  case TS_EVENT_VCONN_READ_COMPLETE:
+  case TS_EVENT_VCONN_EOS:
+  case TS_EVENT_VCONN_INACTIVITY_TIMEOUT:
+  case TS_EVENT_ERROR:
+    if (event == TS_EVENT_VCONN_INACTIVITY_TIMEOUT) {
+      TSDebug(PLUGIN_NAME, "Encountered Inactivity Timeout");
+      TSVConnAbort(data->vc, TS_VC_CLOSE_ABORT);
+    } else {
+      TSVConnClose(data->vc);
+    }
+
+    TSDebug(PLUGIN_NAME, "Closing down background transaction, event= %s(%d)", TSHttpEventNameLookup(event), event);
+    avail = TSIOBufferReaderAvail(data->resp_io_buf_reader);
+    data->addBytes(avail);
+    TSIOBufferReaderConsume(data->resp_io_buf_reader, avail);
+    TSVIONDoneSet(data->r_vio, TSVIONDoneGet(data->r_vio) + avail);
+
+    // Close, release and cleanup
+    data->vc = nullptr;
+    delete data;
+    break;
+
+  default:
+    TSDebug(PLUGIN_NAME, "Unhandled event: %s (%d)", TSHttpEventNameLookup(event), event);
+    break;
+  }
+
+  return 0;
+}
diff --git a/plugins/experimental/cache_fill/background_fetch.h b/plugins/experimental/cache_fill/background_fetch.h
new file mode 100644
index 0000000..1a70ec9
--- /dev/null
+++ b/plugins/experimental/cache_fill/background_fetch.h
@@ -0,0 +1,181 @@
+/** @file
+
+    Plugin to perform background fetches of certain content that would
+    otherwise not be cached. For example, Range: requests / responses.
+
+    @section license License
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+#pragma once
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <cstdarg>
+
+#include <string>
+#include <iostream>
+#include <unordered_map>
+#include <cinttypes>
+#include <string_view>
+#include <array>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "ts/ts.h"
+#include "ts/remap.h"
+
+typedef std::unordered_map<std::string, bool> OutstandingRequests;
+const char PLUGIN_NAME[] = "cache_fill";
+
+class BgFetchState
+{
+public:
+  BgFetchState()                     = default;
+  BgFetchState(BgFetchState const &) = delete;
+  void operator=(BgFetchState const &) = delete;
+
+  static BgFetchState &
+  getInstance()
+  {
+    static BgFetchState _instance;
+    return _instance;
+  }
+
+  ~BgFetchState() { TSMutexDestroy(_lock); }
+
+  bool
+  acquire(const std::string &url)
+  {
+    bool ret;
+
+    TSMutexLock(_lock);
+    if (_urls.end() == _urls.find(url)) {
+      _urls[url] = true;
+      ret        = true;
+    } else {
+      ret = false;
+    }
+    TSMutexUnlock(_lock);
+
+    TSDebug(PLUGIN_NAME, "BgFetchState.acquire(): ret = %d, url = %s", ret, url.c_str());
+
+    return ret;
+  }
+
+  bool
+  release(const std::string &url)
+  {
+    bool ret;
+
+    TSMutexLock(_lock);
+    if (_urls.end() == _urls.find(url)) {
+      ret = false;
+    } else {
+      _urls.erase(url);
+      ret = true;
+    }
+    TSMutexUnlock(_lock);
+
+    return ret;
+  }
+
+private:
+  OutstandingRequests _urls;
+  TSMutex _lock = TSMutexCreate();
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Hold and manage some state for the TXN background fetch continuation.
+// This is necessary, because the TXN is likely to not be available
+// during the time we fetch from origin.
+struct BgFetchData {
+  BgFetchData() { memset(&client_ip, 0, sizeof(client_ip)); }
+
+  ~BgFetchData()
+  {
+    TSHandleMLocRelease(mbuf, TS_NULL_MLOC, hdr_loc);
+    TSHandleMLocRelease(mbuf, TS_NULL_MLOC, url_loc);
+
+    TSMBufferDestroy(mbuf);
+
+    if (vc) {
+      TSError("[%s] Destroyed BgFetchDATA while VC was alive", PLUGIN_NAME);
+      TSVConnClose(vc);
+      vc = nullptr;
+    }
+
+    // If we got schedule, also clean that up
+    if (_cont) {
+      releaseUrl();
+
+      TSContDestroy(_cont);
+      _cont = nullptr;
+      TSIOBufferReaderFree(req_io_buf_reader);
+      TSIOBufferDestroy(req_io_buf);
+      TSIOBufferReaderFree(resp_io_buf_reader);
+      TSIOBufferDestroy(resp_io_buf);
+    }
+  }
+
+  bool
+  acquireUrl() const
+  {
+    return BgFetchState::getInstance().acquire(_url);
+  }
+  bool
+  releaseUrl() const
+  {
+    return BgFetchState::getInstance().release(_url);
+  }
+
+  const char *
+  getUrl() const
+  {
+    return _url.c_str();
+  }
+
+  void
+  addBytes(int64_t b)
+  {
+    _bytes += b;
+  }
+
+  bool initialize(TSMBuffer request, TSMLoc req_hdr, TSHttpTxn txnp);
+  void schedule();
+
+  TSMBuffer mbuf = TSMBufferCreate();
+  TSMLoc hdr_loc = TS_NULL_MLOC;
+  TSMLoc url_loc = TS_NULL_MLOC;
+
+  struct sockaddr_storage client_ip;
+
+  // This is for the actual background fetch / NetVC
+  TSVConn vc                          = nullptr;
+  TSIOBuffer req_io_buf               = nullptr;
+  TSIOBuffer resp_io_buf              = nullptr;
+  TSIOBufferReader req_io_buf_reader  = nullptr;
+  TSIOBufferReader resp_io_buf_reader = nullptr;
+  TSVIO r_vio                         = nullptr;
+  TSVIO w_vio                         = nullptr;
+
+private:
+  std::string _url;
+  int64_t _bytes = 0;
+  TSCont _cont   = nullptr;
+};
diff --git a/plugins/experimental/cache_fill/cache_fill.cc b/plugins/experimental/cache_fill/cache_fill.cc
new file mode 100644
index 0000000..0165e5f
--- /dev/null
+++ b/plugins/experimental/cache_fill/cache_fill.cc
@@ -0,0 +1,185 @@
+/** @file
+
+    Plugin to perform background fetches of certain content that would
+    otherwise not be cached. For example, Range: requests / responses.
+
+    @section license License
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <cstdarg>
+
+#include <string>
+#include <iostream>
+#include <unordered_map>
+#include <cinttypes>
+#include <string_view>
+#include <array>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "ts/ts.h"
+#include "ts/remap.h"
+#include "background_fetch.h"
+
+static const char *
+getCacheLookupResultName(TSCacheLookupResult result)
+{
+  switch (result) {
+  case TS_CACHE_LOOKUP_MISS:
+    return "TS_CACHE_LOOKUP_MISS";
+    break;
+  case TS_CACHE_LOOKUP_HIT_STALE:
+    return "TS_CACHE_LOOKUP_HIT_STALE";
+    break;
+  case TS_CACHE_LOOKUP_HIT_FRESH:
+    return "TS_CACHE_LOOKUP_HIT_FRESH";
+    break;
+  case TS_CACHE_LOOKUP_SKIPPED:
+    return "TS_CACHE_LOOKUP_SKIPPED";
+    break;
+  default:
+    return "UNKNOWN_CACHE_LOOKUP_EVENT";
+    break;
+  }
+  return "UNKNOWN_CACHE_LOOKUP_EVENT";
+}
+
+///////////////////////////////////////////////////////////////////////////
+// create background fetch request if possible
+//
+static bool
+cont_check_cacheable(TSHttpTxn txnp)
+{
+  if (TSHttpTxnIsInternal(txnp))
+    return false;
+  int lookupStatus;
+  TSHttpTxnCacheLookupStatusGet(txnp, &lookupStatus);
+  TSDebug(PLUGIN_NAME, "lookup status: %s", getCacheLookupResultName((TSCacheLookupResult)lookupStatus));
+  bool ret = false;
+  if (TS_CACHE_LOOKUP_MISS == lookupStatus || TS_CACHE_LOOKUP_HIT_STALE == lookupStatus) {
+    bool const nostore = TSHttpTxnServerRespNoStoreGet(txnp);
+
+    TSDebug(PLUGIN_NAME, "is nostore set %d", nostore);
+    if (!nostore) {
+      TSMBuffer request;
+      TSMLoc req_hdr;
+      if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &request, &req_hdr)) {
+        BgFetchData *data = new BgFetchData();
+        // Initialize the data structure (can fail) and acquire a privileged lock on the URL
+        if (data->initialize(request, req_hdr, txnp) && data->acquireUrl()) {
+          TSDebug(PLUGIN_NAME, "scheduling background fetch");
+          data->schedule();
+          ret = true;
+        } else {
+          delete data;
+        }
+      }
+      TSHandleMLocRelease(request, TS_NULL_MLOC, req_hdr);
+    }
+  }
+  return ret;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Main "plugin", which is a global TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE hook. Before
+// initiating a background fetch, this checks
+// if a background fetch is allowed for this request
+//
+static int
+cont_handle_cache(TSCont contp, TSEvent event, void *edata)
+{
+  TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
+  if (TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE == event) {
+    bool const requested = cont_check_cacheable(txnp);
+    if (requested) // Made a background fetch request, do not cache the response
+    {
+      TSDebug(PLUGIN_NAME, "setting no store");
+      TSHttpTxnServerRespNoStoreSet(txnp, 1);
+      TSHttpTxnCacheLookupStatusSet(txnp, TS_CACHE_LOOKUP_MISS);
+    }
+
+  } else {
+    TSError("[%s] Unknown event for this plugin %d", PLUGIN_NAME, event);
+    TSDebug(PLUGIN_NAME, "unknown event for this plugin %d", event);
+  }
+
+  // Reenable and continue with the state machine.
+  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+  return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Setup Remap mode
+///////////////////////////////////////////////////////////////////////////////
+// Initialize the plugin.
+//
+TSReturnCode
+TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
+{
+  TSDebug(PLUGIN_NAME, "cache fill remap init");
+  if (!api_info) {
+    strncpy(errbuf, "[tsremap_init] - Invalid TSRemapInterface argument", errbuf_size - 1);
+    return TS_ERROR;
+  }
+
+  if (api_info->tsremap_version < TSREMAP_VERSION) {
+    snprintf(errbuf, errbuf_size, "[TSRemapInit] - Incorrect API version %ld.%ld", api_info->tsremap_version >> 16,
+             (api_info->tsremap_version & 0xffff));
+    return TS_ERROR;
+  }
+
+  TSDebug(PLUGIN_NAME, "cache fill remap is successfully initialized");
+  return TS_SUCCESS;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// We don't have any specific "instances" here, at least not yet.
+//
+TSReturnCode
+TSRemapNewInstance(int argc, char *argv[], void **ih, char * /* errbuf */, int /* errbuf_size */)
+{
+  TSCont cont = TSContCreate(cont_handle_cache, nullptr);
+  *ih         = cont;
+  return TS_SUCCESS;
+}
+
+void
+TSRemapDeleteInstance(void *ih)
+{
+  TSCont cont = static_cast<TSCont>(ih);
+  TSContDestroy(cont);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//// This is the main "entry" point for the plugin, called for every request.
+////
+TSRemapStatus
+TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo * /* rri */)
+{
+  if (nullptr == ih) {
+    return TSREMAP_NO_REMAP;
+  }
+  TSCont const cont = static_cast<TSCont>(ih);
+  TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, cont);
+  TSDebug(PLUGIN_NAME, "TSRemapDoRemap() added hook");
+
+  return TSREMAP_NO_REMAP;
+}