You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2015/04/19 19:47:59 UTC

[1/3] trafficserver git commit: TS-974: Base implmentation.

Repository: trafficserver
Updated Branches:
  refs/heads/ts-974-5-3-x [created] 799a83bb4


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/hdrs/HTTP.h
----------------------------------------------------------------------
diff --git a/proxy/hdrs/HTTP.h b/proxy/hdrs/HTTP.h
index 78de824..c892dc8 100644
--- a/proxy/hdrs/HTTP.h
+++ b/proxy/hdrs/HTTP.h
@@ -1,32 +1,33 @@
 /** @file
 
-  A brief file description
+    A brief file description
 
-  @section license License
+    @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
+    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
+    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.
- */
+    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.
+*/
 
 #ifndef __HTTP_H__
 #define __HTTP_H__
 
 #include <assert.h>
+#include <vector>
 #include "Arena.h"
-#include "INK_MD5.h"
+#include "CryptoHash.h"
 #include "MIME.h"
 #include "URL.h"
 
@@ -37,6 +38,11 @@
 #define HTTP_MAJOR(v) (((v) >> 16) & 0xFFFF)
 
 class Http2HeaderTable;
+class MIOBuffer;
+namespace ts
+{
+struct ConstBuffer;
+}
 
 enum HTTPStatus {
   HTTP_STATUS_NONE = 0,
@@ -415,6 +421,8 @@ extern int HTTP_LEN_S_MAXAGE;
 extern int HTTP_LEN_NEED_REVALIDATE_ONCE;
 extern int HTTP_LEN_100_CONTINUE;
 
+static size_t const HTTP_RANGE_BOUNDARY_LEN = 32 + 2 + 16;
+
 /* Private */
 void http_hdr_adjust(HTTPHdrImpl *hdrp, int32_t offset, int32_t length, int32_t delta);
 
@@ -462,13 +470,13 @@ int32_t http_parse_version(const char *start, const char *end);
 
 
 /*
-HTTPValAccept*         http_parse_accept (const char *buf, Arena *arena);
-HTTPValAcceptCharset*  http_parse_accept_charset (const char *buf, Arena *arena);
-HTTPValAcceptEncoding* http_parse_accept_encoding (const char *buf, Arena *arena);
-HTTPValAcceptLanguage* http_parse_accept_language (const char *buf, Arena *arena);
-HTTPValCacheControl*   http_parse_cache_control (const char *buf, Arena *arena);
-const char*            http_parse_cache_directive (const char **buf);
-HTTPValRange*          http_parse_range (const char *buf, Arena *arena);
+  HTTPValAccept*         http_parse_accept (const char *buf, Arena *arena);
+  HTTPValAcceptCharset*  http_parse_accept_charset (const char *buf, Arena *arena);
+  HTTPValAcceptEncoding* http_parse_accept_encoding (const char *buf, Arena *arena);
+  HTTPValAcceptLanguage* http_parse_accept_language (const char *buf, Arena *arena);
+  HTTPValCacheControl*   http_parse_cache_control (const char *buf, Arena *arena);
+  const char*            http_parse_cache_directive (const char **buf);
+  HTTPValRange*          http_parse_range (const char *buf, Arena *arena);
 */
 HTTPValTE *http_parse_te(const char *buf, int len, Arena *arena);
 
@@ -495,6 +503,237 @@ public:
   int32_t m_version;
 };
 
+/** A set of content ranges.
+
+    This represents the data for an HTTP range specification.
+    On a request this contains the request ranges. On a response it is the actual ranges in the
+    response, which are the requested ranges modified by the actual content length.
+*/
+struct HTTPRangeSpec {
+  typedef HTTPRangeSpec self;
+
+  /** A range of bytes in an object.
+
+      If @a _min > 0 and @a _max == 0 the range is backwards and counts from the
+      end of the object. That is (100,0) means the last 100 bytes of content.
+  */
+  struct Range {
+    uint64_t _min;
+    uint64_t _max;
+
+    /// Default constructor - invalid range.
+    Range() : _min(UINT64_MAX), _max(1) {}
+    /// Construct as the range ( @a low .. @a high )
+    Range(uint64_t low, uint64_t high) : _min(low), _max(high) {}
+
+    /// Test if this range is a suffix range.
+    bool isSuffix() const;
+    /// Test if this range is a valid range.
+    bool isValid() const;
+    /// Get the size (in bytes) of the range.
+    uint64_t size() const;
+    /** Convert range to absolute values for a content length of @a len.
+
+        @return @c true if the range was valid for @a len, @c false otherwise.
+    */
+    bool apply(uint64_t len);
+
+    /// Force the range to an empty state.
+    Range &invalidate();
+  };
+
+  /// Range iteration type.
+  typedef Range *iterator;
+  typedef Range const *const_iterator;
+
+  /// Current state of the overall specification.
+  /// @internal We can distinguish between @c SINGLE and @c MULTI by looking at the
+  /// size of @a _ranges but we need this to mark @c EMPTY vs. not.
+  enum State {
+    EMPTY,         ///< No range.
+    INVALID,       ///< Range parsing failed.
+    UNSATISFIABLE, ///< Content length application failed.
+    SINGLE,        ///< Single range.
+    MULTI,         ///< Multiple ranges.
+  } _state;
+
+  /// The first range value.
+  /// By separating this out we can avoid allocation in the case of a single
+  /// range value, which is by far the most common ( > 99% in my experience).
+  Range _single;
+  /// Storage for range values.
+  typedef std::vector<Range> RangeBox;
+  /// The first range is copied here if there is more than one (to simplify).
+  RangeBox _ranges;
+
+  /// Default constructor - empty range
+  HTTPRangeSpec();
+
+  /// Reset to re-usable state.
+  void clear();
+
+  /** Parse a Range field @a value and update @a this with the results.
+      @return @c true if @a value was a valid range specifier, @c false otherwise.
+  */
+  bool parseRangeFieldValue(char const *value, int len);
+
+  /** Parse a Content-Range field @a value.
+
+      @a r is set to the content range. If the content range is unsatisfied or a parse error the @a range is
+      set to be invalid.
+
+      @note The content length return is ambiguous on its own, the state of @a r must be checked.
+
+      - Multipart: @a boundary is not empty
+      - Parse error: @a CL == -1 and @a r is invalid
+      - Unsatisfiable: @a CL >= 0 and @a r is invalid
+      - Indeterminate: @c CL == -1 and @a r is valid
+
+      @return The content length, or -1 if there is an error or the content length is indeterminate.
+  */
+  static int64_t parseContentRangeFieldValue(char const *value, int len, Range &r, ts::ConstBuffer &boundary);
+
+  /// Print the range specification.
+  /// @return The number of characters printed.
+  int print(char *buff ///< Output buffer.
+            ,
+            size_t len ///< Size of output buffer.
+            ) const;
+
+  /// Print the range specification quantized.
+  /// @return The number of characters printed.
+  int print_quantized(char *buff ///< Output buffer.
+                      ,
+                      size_t len ///< Size of output buffer.
+                      ,
+                      int64_t quantum ///< Align ranges to multiples of this value.
+                      ,
+                      int64_t interstitial ///< Require gaps to be at least this large.
+                      ,
+                      int64_t initial ///< Require the initial gap to be at least this large.
+                      ) const;
+
+  /// Print the @a ranges.
+  /// @return The number of characters printed.
+  static int print_array(char *buff ///< Output buffer.
+                         ,
+                         size_t len ///< Size of output buffer.
+                         ,
+                         Range const *ranges ///< Array of ranges
+                         ,
+                         int count ///< # of ranges
+                         );
+
+#if 0
+  /** Copy ranges from @a while applying them to the content @a length.
+
+      Ranges are copied if valid for @a length and converted to absolute offsets. The number of ranges
+      after application may be less than the @a src number of ranges. In addition ranges will be clipped
+      to @a length. 
+
+      @return @c true if the range spec is satisfiable, @c false otherwise.
+      Note a range spec with no ranges is always satisfiable and that suffix ranges are also
+      always satisfiable.
+  */
+  bool apply(self const& that, uint64_t length);
+#endif
+
+  /** Update ranges to be absolute based on content @a length.
+
+      Invalid ranges are removed, ranges will be clipped as needed, and suffix ranges will be
+      converted to absolute ranges.
+
+      @return @c true if the range spec is satisfiable (there remains at least one valid range), @c false otherwise.
+      Note a range spec with no ranges is always satisfiable and that suffix ranges are also
+      always satisfiable.
+  */
+  bool apply(uint64_t length);
+
+  /** Number of distinct ranges.
+      @return Number of ranges.
+  */
+  size_t count() const;
+
+  /// Get the size (in bytes) of the ranges.
+  uint64_t size() const;
+
+  /// If this is a valid  single range specification.
+  bool isSingle() const;
+
+  /// If this is a valid multi range specification.
+  bool isMulti() const;
+
+  /// Test if this contains at least one valid range.
+  bool hasRanges() const;
+
+  /// Test if this is a well formed range (may be empty).
+  bool isValid() const;
+
+  /// Test if this is a valid but empty range spec.
+  bool isEmpty() const;
+
+  /// Test if this is an unsatisfied range.
+  bool isUnsatisfied() const;
+
+  /// Access the range at index @a idx.
+  Range &operator[](int n);
+
+  /// Access the range at index @a idx.
+  Range const &operator[](int n) const;
+
+  /// Calculate the convex hull of the range spec.
+  /// The convex hull is the smallest single range that contains all of the ranges in the range spec.
+  /// @note This will return an invalid range if there are no ranges in the range spec.
+  /// @see HttpRangeSpec::Range::isValid
+  Range getConvexHull() const;
+
+  /** Calculate the content length for this range specification.
+
+      @note If a specific content length has not been @c apply 'd this will not produce
+      a usable result.
+
+      @return The content length for the ranges including the range separators.
+  */
+  uint64_t calcContentLength(uint64_t base_content_size, ///< Content size w/o ranges.
+                             uint64_t ct_val_len         ///< Length of Content-Type field value.
+                             ) const;
+
+  /// Calculate the length of the range part boundary header.
+  static uint64_t calcPartBoundarySize(uint64_t object_size ///< Base content size
+                                       ,
+                                       uint64_t ct_val_len ///< Length of the Content-Type value (0 if none).
+                                       );
+
+  /** Write the range part boundary to @a out.
+   */
+  static uint64_t writePartBoundary(MIOBuffer *out ///< Output IO Buffer
+                                    ,
+                                    char const *boundary_str ///< Boundary marker string.
+                                    ,
+                                    size_t boundary_len ///< Length of boundary marker string.
+                                    ,
+                                    uint64_t total_size ///< Base content size.
+                                    ,
+                                    uint64_t low ///< Low value for the range.
+                                    ,
+                                    uint64_t high ///< High value for the raNGE.
+                                    ,
+                                    MIMEField *ctf ///< Content-Type field (@c NULL if none)
+                                    ,
+                                    bool final ///< Is this the final part boundary?
+                                    );
+
+  /// Iterator for first range.
+  iterator begin();
+  const_iterator begin() const;
+  /// Iterator past last range.
+  iterator end();
+  const_iterator end() const;
+
+  self &add(uint64_t low, uint64_t high);
+  self &add(Range const &r);
+};
+
 class IOBufferReader;
 
 class HTTPHdr : public MIMEHdr
@@ -1315,34 +1554,104 @@ HTTPHdr::scheme_get(int *length)
 /*-------------------------------------------------------------------------
   -------------------------------------------------------------------------*/
 
-enum {
-  CACHE_ALT_MAGIC_ALIVE = 0xabcddeed,
-  CACHE_ALT_MAGIC_MARSHALED = 0xdcbadeed,
-  CACHE_ALT_MAGIC_DEAD = 0xdeadeed,
-};
+enum { CACHE_ALT_MAGIC_ALIVE = 0xabcddeed, CACHE_ALT_MAGIC_MARSHALED = 0xdcbadeed, CACHE_ALT_MAGIC_DEAD = 0xdeadeed };
 
-// struct HTTPCacheAlt
+/// Header for an alternate of an object.
+/// This is close to a POD, all the real API is in the @c HTTPInfo class.
+/// @note THIS IS DIRECTLY SERIALIZED TO DISK
+/// (after some tweaks, but any member in this struct will be written to disk)
 struct HTTPCacheAlt {
+  /// Information about a fragment in this alternate.
+  /// @internal Currently @c Dir has only 40 bits for the disk offset of a fragment,
+  /// and since no object (or alternate) is split across stripes (and thence disks)
+  /// no fragment can have an internal offset more than 40 bits long, so 48 bits
+  /// should suffice here.
+  struct FragmentDescriptor {
+    CryptoHash m_key;       ///< Key for fragment.
+    uint64_t m_offset : 48; ///< Starting offset of fragment in object.
+    union {
+      uint16_t m_flags;
+      struct {
+        unsigned int cached_p : 1; ///< Presence bit (is fragment in cache?)
+        unsigned int zero : 15;    ///< Zero fill for future use.
+      } m_flag;
+    };
+  };
+
+  /** Holds the table of fragment descriptors.
+
+      @internal To avoid allocating 2 chunks of memory we hang the descriptors off the end of this structure and provide
+      a method to do the calculations. The @a m_size contains the number of descriptors, the actual byte size must be
+      computed from that. The count of valid entries is held in this structure, not in the table, because it makes
+      serialization easier.  We don't serialize the explicit contents of the table struct (e.g., the capacity / @a
+      m_size value) only the descriptors.
+  */
+  struct FragmentDescriptorTable {
+    uint32_t m_n; ///< The allocated number of entries in the table.
+
+    /** Fragment index of last initial segment cached.
+
+        All fragments from the earliest to this are in cache.
+
+        @note A simple effort to minimize the cost of detecting a complete object.
+        In the normal case we'll get all the fragments in order so this will roll along nicely.
+        Otherwise we may have to do a lot of work on a single fragment, but that' still better
+        than doing it every time for every fragment.
+    */
+    uint32_t m_cached_idx;
+
+    /// 1 based array operator.
+    FragmentDescriptor &operator[](int idx);
+    /// Calculate the allocation size needed for a maximum array index of @a n.
+    static size_t calc_size(uint32_t n);
+  };
+
   HTTPCacheAlt();
+
   void copy(HTTPCacheAlt *to_copy);
-  void copy_frag_offsets_from(HTTPCacheAlt *src);
   void destroy();
 
   uint32_t m_magic;
 
-  // Writeable is set to true is we reside
-  //  in a buffer owned by this structure.
-  // INVARIANT: if own the buffer this HttpCacheAlt
-  //   we also own the buffers for the request &
-  //   response headers
-  int32_t m_writeable;
+  union {
+    uint32_t m_flags;
+    struct {
+      /** Do we own our own buffer?
+          @c true if the buffer containing this data is owned by this object.
+          INVARIANT: if we own this buffer then we also own the buffers for
+          @a m_request_hdr and @a m_response_hdr.
+      */
+      uint32_t writeable_p : 1;
+      /// Was this alternate originally stored as a partial object?
+      uint32_t composite_p : 1;
+      /// Did the origin tell us the actual length of the object?
+      uint32_t content_length_p : 1;
+      /// Are all fragments in cache?
+      uint32_t complete_p : 1;
+      /// Is the fragment table independently allocated?
+      uint32_t table_allocated_p : 1;
+      // Note - !composite_p => complete_p
+      //      - complete_p => content_length_p
+    } m_flag;
+  };
+
   int32_t m_unmarshal_len;
 
   int32_t m_id;
   int32_t m_rid;
 
-  int32_t m_object_key[4];
-  int32_t m_object_size[2];
+  /// # of fragments in the alternate, including the earliest fragment.
+  /// This can be zero for a resident alternate.
+  /// @internal In practice this is the high water mark for cached fragments.
+  /// Contrast with the @a m_cached_idx in the fragment table - that marks the high
+  /// water of continguously cached fragments.
+  uint32_t m_frag_count;
+
+  /** The target size for fragments in this alternate.
+      This is @b mandatory if the object is being partially cached.
+      During read it should be used as a guideline but not considered definitive.
+  */
+  uint32_t m_fixed_fragment_size;
 
   HTTPHdr m_request_hdr;
   HTTPHdr m_response_hdr;
@@ -1350,21 +1659,23 @@ struct HTTPCacheAlt {
   time_t m_request_sent_time;
   time_t m_response_received_time;
 
-  /// # of fragment offsets in this alternate.
-  /// @note This is one less than the number of fragments.
-  int m_frag_offset_count;
-  /// Type of offset for a fragment.
-  typedef uint64_t FragOffset;
-  /// Table of fragment offsets.
-  /// @note The offsets are forward looking so that frag[0] is the
-  /// first byte past the end of fragment 0 which is also the first
-  /// byte of fragment 1. For this reason there is no fragment offset
-  /// for the last fragment.
-  FragOffset *m_frag_offsets;
-  /// # of fragment offsets built in to object.
-  static int const N_INTEGRAL_FRAG_OFFSETS = 4;
-  /// Integral fragment offset table.
-  FragOffset m_integral_frag_offsets[N_INTEGRAL_FRAG_OFFSETS];
+  /** Special case the first (earliest, non-resident) fragment.
+      This holds the key for the earliest fragment and the object size
+      by overloading the offset in this specific instance.
+  */
+  FragmentDescriptor m_earliest;
+
+  /** Descriptors for the rest of the fragments.
+      Because of this, index 0 in this array is really the next fragment after the
+      earliest fragment. We should have the invariant
+      ( @a m_fragments != 0) == ( @a m_frag_count > 1 )
+
+      @internal I thought of using @c std::vector here, but then we end up with either
+      doing 2 allocations (one for the @c std::vector and another for its contents) or
+      writing the @c std::vector container to disk (because this struct is directly
+      serialized). Instead we do our own memory management, which doesn't make me happy either.
+  */
+  FragmentDescriptorTable *m_fragments;
 
   // With clustering, our alt may be in cluster
   //  incoming channel buffer, when we are
@@ -1379,7 +1690,8 @@ struct HTTPCacheAlt {
 class HTTPInfo
 {
 public:
-  typedef HTTPCacheAlt::FragOffset FragOffset; ///< Import type.
+  typedef HTTPCacheAlt::FragmentDescriptor FragmentDescriptor;           ///< Import type.
+  typedef HTTPCacheAlt::FragmentDescriptorTable FragmentDescriptorTable; ///< Import type.
 
   HTTPCacheAlt *m_alt;
 
@@ -1407,7 +1719,6 @@ public:
   {
     m_alt = info->m_alt;
   }
-  void copy_frag_offsets_from(HTTPInfo *src);
   HTTPInfo &operator=(const HTTPInfo &m);
 
   inkcoreapi int marshal_length();
@@ -1438,9 +1749,9 @@ public:
     m_alt->m_rid = id;
   }
 
-  INK_MD5 object_key_get();
-  void object_key_get(INK_MD5 *);
-  bool compare_object_key(const INK_MD5 *);
+  CryptoHash const &object_key_get();
+  void object_key_get(CryptoHash *);
+  bool compare_object_key(const CryptoHash *);
   int64_t object_size_get();
 
   void
@@ -1482,7 +1793,7 @@ public:
     return m_alt->m_response_received_time;
   }
 
-  void object_key_set(INK_MD5 &md5);
+  void object_key_set(CryptoHash const &md5);
   void object_size_set(int64_t size);
 
   void
@@ -1507,14 +1818,55 @@ public:
     m_alt->m_response_received_time = t;
   }
 
+  bool
+  is_composite() const
+  {
+    return m_alt->m_flag.composite_p;
+  }
+  bool
+  is_complete() const
+  {
+    return m_alt->m_flag.complete_p;
+  }
+
+  /** Compute the convex hull of uncached ranges.
+
+      @return An invalid range if all of the request is available in cache.
+  */
+  HTTPRangeSpec::Range get_uncached_hull(HTTPRangeSpec const &req ///< [in] UA request with content length applied
+                                         );
+
   /// Get the fragment table.
-  FragOffset *get_frag_table();
-  /// Get the # of fragment offsets
-  /// @note This is the size of the fragment offset table, and one less
-  /// than the actual # of fragments.
-  int get_frag_offset_count();
-  /// Add an @a offset to the end of the fragment offset table.
-  void push_frag_offset(FragOffset offset);
+  /// @note There is a fragment table only for multi-fragment alternates @b and
+  /// the indexing starts with the second (non-earliest) fragment.
+  /// @deprecated - use specialized methods.
+  FragmentDescriptorTable *get_frag_table();
+
+  /// Force a descriptor at index @a idx.
+  FragmentDescriptor *force_frag_at(unsigned int idx);
+
+  /// Get the fragment index for @a offset.
+  int get_frag_index_of(int64_t offset);
+  /// Get the fragment key for an @a offset.
+  /// @note Forces fragment.
+  CryptoHash const &get_frag_key_of(int64_t offset);
+  /// Get the fragment key of the @a idx fragment.
+  /// @note Forces fragment.
+  CryptoHash const &get_frag_key(unsigned int idx);
+  /// Get the starting offset of a fragment.
+  int64_t get_frag_offset(unsigned int idx);
+
+  /// Get the number of fragments.
+  /// 0 means resident alternate, 1 means single fragment, > 1 means multi-fragment.
+  int get_frag_count() const;
+  /// Get the target fragment size.
+  uint32_t get_frag_fixed_size() const;
+  /// Mark a fragment at index @a idx as written to cache.
+  void mark_frag_write(unsigned int idx);
+  /// Check if a fragment is cached.
+  bool is_frag_cached(unsigned int idx) const;
+  /// Get the range of bytes for the fragments from @a low to @a high.
+  HTTPRangeSpec::Range get_range_for_frags(int low, int high);
 
   // Sanity check functions
   static bool check_marshalled(char *buf, int len);
@@ -1527,7 +1879,7 @@ inline void
 HTTPInfo::destroy()
 {
   if (m_alt) {
-    if (m_alt->m_writeable) {
+    if (m_alt->m_flag.writeable_p) {
       m_alt->destroy();
     } else if (m_alt->m_ext_buffer) {
       if (m_alt->m_ext_buffer->refcount_dec() == 0) {
@@ -1544,77 +1896,297 @@ inline HTTPInfo &HTTPInfo::operator=(const HTTPInfo &m)
   return *this;
 }
 
-inline INK_MD5
+inline CryptoHash const &
 HTTPInfo::object_key_get()
 {
-  INK_MD5 val;
-  int32_t *pi = reinterpret_cast<int32_t *>(&val);
-
-  pi[0] = m_alt->m_object_key[0];
-  pi[1] = m_alt->m_object_key[1];
-  pi[2] = m_alt->m_object_key[2];
-  pi[3] = m_alt->m_object_key[3];
-
-  return val;
+  return m_alt->m_earliest.m_key;
 }
 
 inline void
-HTTPInfo::object_key_get(INK_MD5 *md5)
+HTTPInfo::object_key_get(CryptoHash *key)
 {
-  int32_t *pi = reinterpret_cast<int32_t *>(md5);
-  pi[0] = m_alt->m_object_key[0];
-  pi[1] = m_alt->m_object_key[1];
-  pi[2] = m_alt->m_object_key[2];
-  pi[3] = m_alt->m_object_key[3];
+  memcpy(key, &(m_alt->m_earliest.m_key), sizeof(*key));
 }
 
 inline bool
-HTTPInfo::compare_object_key(const INK_MD5 *md5)
+HTTPInfo::compare_object_key(const CryptoHash *key)
 {
-  int32_t const *pi = reinterpret_cast<int32_t const *>(md5);
-  return ((m_alt->m_object_key[0] == pi[0]) && (m_alt->m_object_key[1] == pi[1]) && (m_alt->m_object_key[2] == pi[2]) &&
-          (m_alt->m_object_key[3] == pi[3]));
+  return *key == m_alt->m_earliest.m_key;
 }
 
 inline int64_t
 HTTPInfo::object_size_get()
 {
-  int64_t val;
-  int32_t *pi = reinterpret_cast<int32_t *>(&val);
-
-  pi[0] = m_alt->m_object_size[0];
-  pi[1] = m_alt->m_object_size[1];
-  return val;
+  return m_alt->m_earliest.m_offset;
 }
 
 inline void
-HTTPInfo::object_key_set(INK_MD5 &md5)
+HTTPInfo::object_key_set(CryptoHash const &md5)
 {
-  int32_t *pi = reinterpret_cast<int32_t *>(&md5);
-  m_alt->m_object_key[0] = pi[0];
-  m_alt->m_object_key[1] = pi[1];
-  m_alt->m_object_key[2] = pi[2];
-  m_alt->m_object_key[3] = pi[3];
+  m_alt->m_earliest.m_key = md5;
 }
 
 inline void
 HTTPInfo::object_size_set(int64_t size)
 {
-  int32_t *pi = reinterpret_cast<int32_t *>(&size);
-  m_alt->m_object_size[0] = pi[0];
-  m_alt->m_object_size[1] = pi[1];
+  m_alt->m_earliest.m_offset = size;
+  m_alt->m_flag.content_length_p = true;
 }
 
-inline HTTPInfo::FragOffset *
+inline HTTPInfo::FragmentDescriptorTable *
 HTTPInfo::get_frag_table()
 {
-  return m_alt ? m_alt->m_frag_offsets : 0;
+  return m_alt ? m_alt->m_fragments : 0;
 }
 
 inline int
-HTTPInfo::get_frag_offset_count()
+HTTPInfo::get_frag_count() const
+{
+  return m_alt ? m_alt->m_frag_count : 0;
+}
+
+inline uint32_t
+HTTPInfo::get_frag_fixed_size() const
+{
+  return m_alt ? m_alt->m_fixed_fragment_size : 0;
+}
+
+inline CryptoHash const &
+HTTPInfo::get_frag_key_of(int64_t offset)
+{
+  return this->get_frag_key(this->get_frag_index_of(offset));
+}
+
+inline CryptoHash const &
+HTTPInfo::get_frag_key(unsigned int idx)
+{
+  return 0 == idx ? m_alt->m_earliest.m_key : this->force_frag_at(idx)->m_key;
+}
+
+inline int64_t
+HTTPInfo::get_frag_offset(unsigned int idx)
+{
+  return 0 == idx ? 0 : (*m_alt->m_fragments)[idx].m_offset;
+}
+
+inline bool
+HTTPInfo::is_frag_cached(unsigned int idx) const
+{
+  return m_alt && ((0 == idx && m_alt->m_earliest.m_flag.cached_p) ||
+                   (m_alt->m_fragments && idx < m_alt->m_fragments->m_n && (*m_alt->m_fragments)[idx].m_flag.cached_p));
+}
+
+inline HTTPRangeSpec::HTTPRangeSpec() : _state(EMPTY)
+{
+}
+
+inline void
+HTTPRangeSpec::clear()
+{
+  _state = EMPTY;
+  RangeBox().swap(_ranges); // force memory drop.
+}
+
+inline bool
+HTTPRangeSpec::isSingle() const
+{
+  return SINGLE == _state;
+}
+
+inline bool
+HTTPRangeSpec::isMulti() const
+{
+  return MULTI == _state;
+}
+
+inline bool
+HTTPRangeSpec::isEmpty() const
+{
+  return EMPTY == _state;
+}
+
+inline bool
+HTTPRangeSpec::isUnsatisfied() const
+{
+  return UNSATISFIABLE == _state;
+}
+
+inline size_t
+HTTPRangeSpec::count() const
+{
+  return SINGLE == _state ? 1 : _ranges.size();
+}
+
+inline bool
+HTTPRangeSpec::hasRanges() const
+{
+  return SINGLE == _state || MULTI == _state;
+}
+
+inline bool
+HTTPRangeSpec::isValid() const
+{
+  return SINGLE == _state || MULTI == _state || EMPTY == _state;
+}
+
+inline HTTPRangeSpec::Range &
+HTTPRangeSpec::Range::invalidate()
+{
+  _min = UINT64_MAX;
+  _max = 1;
+  return *this;
+}
+
+inline bool
+HTTPRangeSpec::Range::isSuffix() const
+{
+  return 0 == _max && _min > 0;
+}
+
+inline bool
+HTTPRangeSpec::Range::isValid() const
+{
+  return _min <= _max || this->isSuffix();
+}
+
+inline uint64_t
+HTTPRangeSpec::Range::size() const
+{
+  return 1 + (_max - _min);
+}
+
+inline uint64_t
+HTTPRangeSpec::size() const
+{
+  uint64_t size = 0;
+  if (this->isSingle())
+    size = _single.size();
+  else if (this->isMulti()) {
+    for (RangeBox::const_iterator spot = _ranges.begin(), limit = _ranges.end(); spot != limit; ++spot)
+      size += spot->size();
+  }
+  return size;
+}
+
+inline bool
+HTTPRangeSpec::Range::apply(uint64_t len)
+{
+  ink_assert(len > 0);
+  bool zret = true; // is this range satisfiable for @a len?
+  if (this->isSuffix()) {
+    _max = len - 1;
+    _min = _min > len ? 0 : len - _min;
+  } else if (_min < len) {
+    _max = MIN(_max, len - 1);
+  } else {
+    this->invalidate();
+    zret = false;
+  }
+  return zret;
+}
+
+inline HTTPRangeSpec &
+HTTPRangeSpec::add(uint64_t low, uint64_t high)
+{
+  return this->add(Range(low, high));
+}
+
+inline HTTPRangeSpec::Range &HTTPRangeSpec::operator[](int n)
+{
+  return SINGLE == _state ? _single : _ranges[n];
+}
+
+inline HTTPRangeSpec::Range const &HTTPRangeSpec::operator[](int n) const
+{
+  return SINGLE == _state ? _single : _ranges[n];
+}
+
+inline HTTPRangeSpec::iterator
+HTTPRangeSpec::begin()
+{
+  switch (_state) {
+  case SINGLE:
+    return &_single;
+  case MULTI:
+    return &(*(_ranges.begin()));
+  default:
+    return NULL;
+  }
+}
+
+inline HTTPRangeSpec::iterator
+HTTPRangeSpec::end()
+{
+  switch (_state) {
+  case SINGLE:
+    return (&_single) + 1;
+  case MULTI:
+    return &(*(_ranges.end()));
+  default:
+    return NULL;
+  }
+}
+
+inline HTTPRangeSpec::const_iterator
+HTTPRangeSpec::begin() const
+{
+  return const_cast<self *>(this)->begin();
+}
+
+inline HTTPRangeSpec::const_iterator
+HTTPRangeSpec::end() const
+{
+  return const_cast<self *>(this)->end();
+}
+
+inline HTTPRangeSpec::Range
+HTTPRangeSpec::getConvexHull() const
+{
+  Range zret;
+  // Compute the convex hull of the original in fragment indices.
+  for (const_iterator spot = this->begin(), limit = this->end(); spot != limit; ++spot) {
+    if (spot->_min < zret._min)
+      zret._min = spot->_min;
+    if (spot->_max > zret._max)
+      zret._max = spot->_max;
+  }
+  return zret;
+}
+
+inline HTTPCacheAlt::FragmentDescriptor &HTTPCacheAlt::FragmentDescriptorTable::operator[](int idx)
+{
+  ink_assert(idx > 0);
+  return *(reinterpret_cast<FragmentDescriptor *>(reinterpret_cast<char *>(this + 1) + sizeof(FragmentDescriptor) * (idx - 1)));
+}
+
+inline size_t
+HTTPCacheAlt::FragmentDescriptorTable::calc_size(uint32_t n)
+{
+  /* Get storage for @a n fragments. Because we store the earliest in the base structure, we only need
+     @a n - 1 entries in the table.
+  */
+  return n < 1 ? 0 : sizeof(FragmentDescriptorTable) + (n - 1) * sizeof(FragmentDescriptor);
+}
+
+#if 0
+inline
+HTTPCacheAlt::FragmentAccessor::FragmentAccessor(HTTPCacheAlt* alt)
+             : _alt(alt), _table(alt->m_fragments)
+{
+}
+
+inline HTTPCacheAlt::FragmentDescriptor&
+HTTPCacheAlt::FragmentAccessor::operator [] (int idx)
+{
+  ink_assert(idx >= 0);
+  return idx == 0 ? _alt->m_earliest : (*_table)[idx];
+}
+
+inline uint32_t
+HTTPCacheAlt::FragmentAccessor::get_initial_cached_index() const
 {
-  return m_alt ? m_alt->m_frag_offset_count : 0;
+  return _table ? _table->m_cached_idx : 0;
 }
+#endif
 
 #endif /* __HTTP_H__ */

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/http/HttpCacheSM.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpCacheSM.cc b/proxy/http/HttpCacheSM.cc
index 1025796..c1dc8ba 100644
--- a/proxy/http/HttpCacheSM.cc
+++ b/proxy/http/HttpCacheSM.cc
@@ -262,6 +262,31 @@ HttpCacheSM::open_read(URL *url, HTTPHdr *hdr, CacheLookupHttpConfig *params, ti
   }
 }
 
+int
+HttpCacheSM::state_cache_open_partial_read(int evid, void* data)
+{
+  if (!open_read_cb)
+    return this->state_cache_open_read(evid, data);
+  Debug("amc", "[HttpCacheSM::state_cache_open_partial_read] second round");
+  return VC_EVENT_DONE;
+}
+
+Action*
+HttpCacheSM::open_partial_read(HTTPHdr* client_request_hdr)
+{
+  // Simple because this requires an active write VC so we know the object is there (no retries).
+  ink_assert(NULL != cache_write_vc);
+
+  SET_HANDLER(&HttpCacheSM::state_cache_open_partial_read);
+  open_read_cb = false;
+
+  Action *action_handle = cacheProcessor.open_read(this, cache_write_vc, client_request_hdr);
+
+  if (action_handle != ACTION_RESULT_DONE) pending_action = action_handle;
+
+  return open_read_cb ? ACTION_RESULT_DONE : &captive_action;
+}
+
 Action *
 HttpCacheSM::open_write(URL *url, HTTPHdr *request, CacheHTTPInfo *old_info, time_t pin_in_cache, bool retry, bool allow_multiple)
 {

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/http/HttpCacheSM.h
----------------------------------------------------------------------
diff --git a/proxy/http/HttpCacheSM.h b/proxy/http/HttpCacheSM.h
index e6bcb63..61246d9 100644
--- a/proxy/http/HttpCacheSM.h
+++ b/proxy/http/HttpCacheSM.h
@@ -70,7 +70,13 @@ public:
 
   Action *open_read(URL *url, HTTPHdr *hdr, CacheLookupHttpConfig *params, time_t pin_in_cache);
 
-  Action *open_write(URL *url, HTTPHdr *request, CacheHTTPInfo *old_info, time_t pin_in_cache, bool retry, bool allow_multiple);
+  /** Open a cache read VC for the same object as the writer.
+      @return @c true if there was no reader and one was successfully created from the writer.
+  */
+  Action* open_partial_read(HTTPHdr* client_request_hdr);
+
+  Action *open_write(URL * url,
+                     HTTPHdr * request, CacheHTTPInfo * old_info, time_t pin_in_cache, bool retry, bool allow_multiple);
 
   CacheVConnection *cache_read_vc;
   CacheVConnection *cache_write_vc;
@@ -158,6 +164,7 @@ private:
 
   int state_cache_open_read(int event, void *data);
   int state_cache_open_write(int event, void *data);
+  int state_cache_open_partial_read(int evid, void* data);
 
   HttpCacheAction captive_action;
   bool open_read_cb;

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/http/HttpDebugNames.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpDebugNames.cc b/proxy/http/HttpDebugNames.cc
index 7db357d..ea293df 100644
--- a/proxy/http/HttpDebugNames.cc
+++ b/proxy/http/HttpDebugNames.cc
@@ -386,6 +386,8 @@ HttpDebugNames::get_action_name(HttpTransact::StateMachineAction_t e)
     return ("SM_ACTION_API_POST_REMAP");
   case HttpTransact::SM_ACTION_POST_REMAP_SKIP:
     return ("SM_ACTION_POST_REMAP_SKIP");
+  case HttpTransact::SM_ACTION_CACHE_OPEN_PARTIAL_READ:
+    return "SM_ACTION_CACHE_OPEN_PARTIAL_READ";
   }
 
   return ("unknown state name");

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/http/HttpSM.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc
index 57b4efa..c4a677a 100644
--- a/proxy/http/HttpSM.cc
+++ b/proxy/http/HttpSM.cc
@@ -300,10 +300,11 @@ HttpVCTable::cleanup_all()
     DebugSM("http", "[%" PRId64 "] [%s, %s]", sm_id, #state_name, HttpDebugNames::get_event_name(event)); \
   }
 
-#define HTTP_SM_SET_DEFAULT_HANDLER(_h) \
-  {                                     \
-    REMEMBER(-1, reentrancy_count);     \
-    default_handler = _h;               \
+#define HTTP_SM_SET_DEFAULT_HANDLER(_h)                                          \
+  {                                                                              \
+    REMEMBER(-1, reentrancy_count);                                              \
+    Debug("amc", "SM %" PRId64 " default handler = %s", sm_id, handlerName(_h)); \
+    default_handler = _h;                                                        \
   }
 
 
@@ -1552,9 +1553,20 @@ HttpSM::handle_api_return()
 
       setup_blind_tunnel(true);
     } else {
-      HttpTunnelProducer *p = setup_server_transfer();
-      perform_cache_write_action();
-      tunnel.tunnel_run(p);
+      if (t_state.hdr_info.request_range.hasRanges() && HttpTransact::CACHE_DO_WRITE == t_state.cache_info.action) {
+        Debug("amc", "Set up for partial read");
+        CacheVConnection *save_write_vc = cache_sm.cache_write_vc;
+        tunnel.tunnel_run(setup_server_transfer_to_cache_only());
+        t_state.next_action = HttpTransact::SM_ACTION_CACHE_OPEN_PARTIAL_READ;
+        t_state.source = HttpTransact::SOURCE_CACHE;
+        HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_cache_open_partial_read);
+        cache_sm.cache_write_vc = save_write_vc;
+        pending_action = cache_sm.open_partial_read(&t_state.hdr_info.client_request);
+        cache_sm.cache_write_vc = NULL;
+      } else {
+        setup_server_transfer();
+        perform_cache_write_action();
+      }
     }
     break;
   }
@@ -2520,6 +2532,61 @@ HttpSM::state_cache_open_read(int event, void *data)
   return 0;
 }
 
+//////////////////////////////////////////////////////////////////////////
+//
+//  HttpSM::state_cache_open_read_from_writer()
+//
+//  Handle the case where a partial request had a cache miss and we sent
+//  a request to the origin which has now come back successfully. We
+//  need to create a reader cache VC to handle the read side of the
+//  operation.
+//////////////////////////////////////////////////////////////////////////
+int
+HttpSM::state_cache_open_partial_read(int event, void *data)
+{
+  STATE_ENTER(&HttpSM::state_cache_open_partial_read, event);
+
+  //  ink_assert(NULL != cache_sm.cache_write_vc);
+  Debug("amc", "Handling partial read event");
+
+  switch (event) {
+  case CACHE_EVENT_OPEN_READ:
+    pending_action = NULL;
+
+    DebugSM("http", "[%" PRId64 "] cache_open_partial_read - CACHE_EVENT_OPEN_READ", sm_id);
+
+    ink_assert(cache_sm.cache_read_vc != NULL);
+
+    cache_sm.cache_read_vc->get_http_info(&t_state.cache_info.object_read);
+    ink_assert(t_state.cache_info.object_read != 0);
+
+    t_state.next_action = HttpTransact::SM_ACTION_SERVE_FROM_CACHE;
+    t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR;
+
+    do_api_callout();
+    break;
+  case CACHE_EVENT_OPEN_READ_FAILED:
+    pending_action = NULL;
+
+    DebugSM("http", "[%" PRId64 "] cache_open_partial_read - "
+                    "CACHE_EVENT_OPEN_READ_FAILED",
+            sm_id);
+
+    // Need to do more here - mainly fall back to bypass from origin.
+    // Although we've got a serious problem if we don't open in this situation.
+    ink_assert("[amc] do something!");
+    break;
+
+
+  default:
+    // When the SM is in this state we've already started a tunnel running so we have to handle
+    // that case in here so unless it's an event of interest to this state, pass it on.
+    return this->tunnel_handler(event, data);
+  }
+
+  return 0;
+}
+
 int
 HttpSM::main_handler(int event, void *data)
 {
@@ -2786,6 +2853,9 @@ HttpSM::tunnel_handler(int event, void *data)
 {
   STATE_ENTER(&HttpSM::tunnel_handler, event);
 
+  if (CACHE_EVENT_OPEN_READ == event)
+    return 0;
+
   ink_assert(event == HTTP_TUNNEL_EVENT_DONE);
   ink_assert(data == &tunnel);
   // The tunnel calls this when it is done
@@ -4243,7 +4313,8 @@ HttpSM::parse_range_and_compare(MIMEField *field, int64_t content_length)
     ranges[nr]._end = end;
     ++nr;
 
-    if (!cache_sm.cache_read_vc->is_pread_capable() && cache_config_read_while_writer == 2) {
+#if 0
+    if (!cache_sm.cache_read_vc->is_pread_capable() && cache_config_read_while_writer==2) {
       // write in progress, check if request range not in cache yet
       HTTPInfo::FragOffset *frag_offset_tbl = t_state.cache_info.object_read->get_frag_table();
       int frag_offset_cnt = t_state.cache_info.object_read->get_frag_offset_count();
@@ -4253,6 +4324,7 @@ HttpSM::parse_range_and_compare(MIMEField *field, int64_t content_length)
         t_state.range_in_cache = false;
       }
     }
+#endif
   }
 
   if (nr > 0) {
@@ -4321,6 +4393,9 @@ HttpSM::do_range_parse(MIMEField *range_field)
 void
 HttpSM::do_range_setup_if_necessary()
 {
+#if 1
+  t_state.range_setup = HttpTransact::RANGE_NONE;
+#else
   MIMEField *field;
   INKVConnInternal *range_trans;
   int field_content_type_len = -1;
@@ -4358,6 +4433,7 @@ HttpSM::do_range_setup_if_necessary()
       }
     }
   }
+#endif
 }
 
 
@@ -5484,15 +5560,16 @@ HttpSM::perform_cache_write_action()
     break;
   }
 
-  case HttpTransact::CACHE_DO_WRITE:
-  case HttpTransact::CACHE_DO_REPLACE:
+  case HttpTransact::CACHE_DO_WRITE: {
     // Fix need to set up delete for after cache write has
     //   completed
+
     if (transform_info.entry == NULL || t_state.api_info.cache_untransformed == true) {
-      cache_sm.close_read();
       t_state.cache_info.write_status = HttpTransact::CACHE_WRITE_IN_PROGRESS;
       setup_cache_write_transfer(&cache_sm, server_entry->vc, &t_state.cache_info.object_store, client_response_hdr_bytes,
                                  "cache write");
+
+      cache_sm.close_read();
     } else {
       // We are not caching the untransformed.  We might want to
       //  use the cache writevc to cache the transformed copy
@@ -5501,7 +5578,7 @@ HttpSM::perform_cache_write_action()
       cache_sm.cache_write_vc = NULL;
     }
     break;
-
+  }
   default:
     ink_release_assert(0);
     break;
@@ -5738,7 +5815,8 @@ HttpSM::setup_cache_read_transfer()
 
   ink_assert(cache_sm.cache_read_vc != NULL);
 
-  doc_size = t_state.cache_info.object_read->object_size_get();
+  //  doc_size = t_state.cache_info.object_read->object_size_get();
+  doc_size = cache_sm.cache_read_vc->get_effective_content_size();
   alloc_index = buffer_size_to_index(doc_size + index_to_buffer_size(HTTP_HEADER_BUFFER_SIZE_INDEX));
 
 #ifndef USE_NEW_EMPTY_MIOBUFFER
@@ -5760,8 +5838,14 @@ HttpSM::setup_cache_read_transfer()
 
   HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler);
 
-  if (doc_size != INT64_MAX)
+  if (doc_size != INT64_MAX) {
+    /* Brokenness - if the object was already in cache, @a doc_size is correct based on the range because the
+       CacheVC had a chance to do that on the way here, but if not then the read CacheVC isn't fully set up
+       and doesn't account for the range data, so we do it. That needs to be rationalized.
+    */
+    doc_size = t_state.hdr_info.request_range.calcContentLength(doc_size, 0);
     doc_size += hdr_size;
+  }
 
   HttpTunnelProducer *p = tunnel.add_producer(cache_sm.cache_read_vc, doc_size, buf_start, &HttpSM::tunnel_handler_cache_read,
                                               HT_CACHE_READ, "cache read");
@@ -5821,6 +5905,12 @@ HttpSM::setup_cache_write_transfer(HttpCacheSM *c_sm, VConnection *source_vc, HT
   store_info->request_sent_time_set(t_state.request_sent_time);
   store_info->response_received_time_set(t_state.response_received_time);
 
+  if (t_state.hdr_info.response_range.isValid()) {
+    if (t_state.hdr_info.response_content_size != HTTP_UNDEFINED_CL)
+      store_info->object_size_set(t_state.hdr_info.response_content_size);
+    c_sm->cache_write_vc->set_inbound_range(t_state.hdr_info.response_range._min, t_state.hdr_info.response_range._max);
+  }
+
   c_sm->cache_write_vc->set_http_info(store_info);
   store_info->clear();
 
@@ -6174,7 +6264,7 @@ HttpSM::setup_transfer_from_transform_to_cache_only()
   return p;
 }
 
-void
+HttpTunnelProducer *
 HttpSM::setup_server_transfer_to_cache_only()
 {
   TunnelChunkingAction_t action;
@@ -6202,6 +6292,7 @@ HttpSM::setup_server_transfer_to_cache_only()
   setup_cache_write_transfer(&cache_sm, server_entry->vc, &t_state.cache_info.object_store, 0, "cache write");
 
   server_entry->in_tunnel = true;
+  return p;
 }
 
 HttpTunnelProducer *
@@ -7658,3 +7749,14 @@ HttpSM::is_redirect_required()
   }
   return redirect_required;
 }
+
+char const *
+HttpSM::handlerName(int (HttpSM::*ptm)(int, void *))
+{
+  char const *zret = "*method*";
+  if (ptm == &HttpSM::tunnel_handler)
+    zret = "tunnel_handler";
+  else if (ptm == &HttpSM::state_cache_open_partial_read)
+    zret = "state_cache_open_partial_read";
+  return zret;
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/http/HttpSM.h
----------------------------------------------------------------------
diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h
index 4edd7d5..ad84509 100644
--- a/proxy/http/HttpSM.h
+++ b/proxy/http/HttpSM.h
@@ -256,6 +256,7 @@ public:
   // Debugging routines to dump the SM history, hdrs
   void dump_state_on_assert();
   void dump_state_hdr(HTTPHdr *h, const char *s);
+  char const* handlerName(int (HttpSM::*ptm)(int, void*));
 
   // Functions for manipulating api hooks
   void txn_hook_append(TSHttpHookID id, INKContInternal *cont);
@@ -374,6 +375,7 @@ protected:
   // Cache Handlers
   int state_cache_open_read(int event, void *data);
   int state_cache_open_write(int event, void *data);
+  int state_cache_open_partial_read(int event, void* data);
 
   // Http Server Handlers
   int state_http_server_open(int event, void *data);
@@ -442,9 +444,9 @@ protected:
   void setup_cache_lookup_complete_api();
   void setup_server_send_request();
   void setup_server_send_request_api();
-  HttpTunnelProducer *setup_server_transfer();
-  void setup_server_transfer_to_cache_only();
-  HttpTunnelProducer *setup_cache_read_transfer();
+  HttpTunnelProducer* setup_server_transfer();
+  HttpTunnelProducer* setup_server_transfer_to_cache_only();
+  HttpTunnelProducer * setup_cache_read_transfer();
   void setup_internal_transfer(HttpSMHandler handler);
   void setup_error_transfer();
   void setup_100_continue_transfer();

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/http/HttpTransact.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc
index 0ca0a48..ffc4293 100644
--- a/proxy/http/HttpTransact.cc
+++ b/proxy/http/HttpTransact.cc
@@ -47,8 +47,7 @@
 #include "HttpClientSession.h"
 #include "I_Machine.h"
 
-static char range_type[] = "multipart/byteranges; boundary=RANGE_SEPARATOR";
-#define RANGE_NUMBERS_LENGTH 60
+static char const HTTP_RANGE_MULTIPART_CONTENT_TYPE[] = "multipart/byteranges; boundary=";
 
 #define HTTP_INCREMENT_TRANS_STAT(X) update_stat(s, X, 1);
 #define HTTP_SUM_TRANS_STAT(X, S) update_stat(s, X, (ink_statval_t)S);
@@ -1769,7 +1768,7 @@ HttpTransact::OSDNSLookup(State *s)
         // from the DNS we need to call LookupSkipOpenServer
       } else if (s->cache_lookup_result == CACHE_LOOKUP_HIT_FRESH || s->cache_lookup_result == CACHE_LOOKUP_HIT_WARNING ||
                  s->cache_lookup_result == CACHE_LOOKUP_HIT_STALE) {
-        // DNS lookup is done if the content is state need to call handle cache open read hit
+        //DNS lookup is done if the content is stale need to call handle cache open read hit
         TRANSACT_RETURN(SM_ACTION_API_OS_DNS, HandleCacheOpenReadHit);
       } else if (s->cache_lookup_result == CACHE_LOOKUP_MISS || s->cache_info.action == CACHE_DO_NO_ACTION) {
         TRANSACT_RETURN(SM_ACTION_API_OS_DNS, HandleCacheOpenReadMiss);
@@ -2516,6 +2515,7 @@ HttpTransact::HandleCacheOpenReadHit(State *s)
   bool needs_cache_auth = false;
   bool server_up = true;
   CacheHTTPInfo *obj;
+  HTTPRangeSpec range;
 
   if (s->api_update_cached_object == HttpTransact::UPDATE_CACHED_OBJECT_CONTINUE) {
     obj = &s->cache_info.object_store;
@@ -2690,6 +2690,17 @@ HttpTransact::HandleCacheOpenReadHit(State *s)
       SET_VIA_STRING(VIA_DETAIL_CACHE_TYPE, VIA_DETAIL_CACHE);
     }
   }
+
+  // Check if it's a range request and we need to get some data from the origin.
+  if (s->state_machine->get_cache_sm().cache_read_vc->get_uncached(range)) {
+    build_request(s, &s->hdr_info.client_request, &s->hdr_info.server_request, s->current.server->http_version, &range);
+    s->next_action = how_to_open_connection(s);
+    if (s->stale_icp_lookup && s->next_action == SM_ACTION_ORIGIN_SERVER_OPEN) {
+      s->next_action = SM_ACTION_ICP_QUERY;
+    }
+    return;
+  }
+
   // cache hit, document is fresh, does not authorization,
   // is valid, etc. etc. send it back to the client.
   //
@@ -2810,7 +2821,7 @@ HttpTransact::build_response_from_cache(State *s, HTTPWarningCode warning_code)
       // send back the full document to the client.
       DebugTxn("http_trans", "[build_response_from_cache] Match! Serving full document.");
       s->cache_info.action = CACHE_DO_SERVE;
-
+# if 0
       // Check if cached response supports Range. If it does, append
       // Range transformation plugin
       // only if the cached response is a 200 OK
@@ -2845,6 +2856,10 @@ HttpTransact::build_response_from_cache(State *s, HTTPWarningCode warning_code)
         build_response(s, cached_response, &s->hdr_info.client_response, s->client_info.http_version);
       }
       s->next_action = SM_ACTION_SERVE_FROM_CACHE;
+# else
+      build_response(s, cached_response, &s->hdr_info.client_response, s->client_info.http_version);
+      s->next_action = SM_ACTION_SERVE_FROM_CACHE;
+# endif
     }
     // If the client request is a HEAD, then serve the header from cache.
     else if (s->method == HTTP_WKSIDX_HEAD) {
@@ -3010,10 +3025,12 @@ HttpTransact::HandleCacheOpenReadMiss(State *s)
   // We must, however, not cache the responses to these requests.
   if (does_method_require_cache_copy_deletion(s->http_config_param, s->method) && s->api_req_cacheable == false) {
     s->cache_info.action = CACHE_DO_NO_ACTION;
+# if 0
   } else if ((s->hdr_info.client_request.presence(MIME_PRESENCE_RANGE) && !s->txn_conf->cache_range_write) ||
              does_method_effect_cache(s->method) == false || s->range_setup == RANGE_NOT_SATISFIABLE ||
              s->range_setup == RANGE_NOT_HANDLED) {
     s->cache_info.action = CACHE_DO_NO_ACTION;
+# endif
   } else {
     s->cache_info.action = CACHE_PREPARE_TO_WRITE;
   }
@@ -3057,8 +3074,7 @@ HttpTransact::HandleCacheOpenReadMiss(State *s)
         return;
       }
     }
-    build_request(s, &s->hdr_info.client_request, &s->hdr_info.server_request, s->current.server->http_version);
-
+    build_request(s, &s->hdr_info.client_request, &s->hdr_info.server_request, s->current.server->http_version, &s->hdr_info.request_range);
     s->next_action = how_to_open_connection(s);
   } else { // miss, but only-if-cached is set
     build_error_response(s, HTTP_STATUS_GATEWAY_TIMEOUT, "Not Cached", "cache#not_in_cache", NULL);
@@ -4039,6 +4055,7 @@ HttpTransact::handle_cache_operation_on_forward_server_response(State *s)
   HTTPStatus client_response_code = HTTP_STATUS_NONE;
   const char *warn_text = NULL;
   bool cacheable = false;
+  HTTPRangeSpec ranges;
 
   cacheable = is_response_cacheable(s, &s->hdr_info.client_request, &s->hdr_info.server_response);
   DebugTxn("http_trans", "[hcoofsr] response %s cacheable", cacheable ? "is" : "is not");
@@ -4360,6 +4377,19 @@ HttpTransact::handle_cache_operation_on_forward_server_response(State *s)
     break;
   }
 
+
+# if 0
+  /* If we plan to do a write and the request was partial, then we need to open a
+     cache read to service the request and not just pass through.
+  */
+  if (SM_ACTION_SERVER_READ == s->next_action &&
+      CACHE_DO_WRITE == s->cache_info.action &&
+      s->hdr_info.request_range.hasRanges()
+  ) {
+    s->next_action = SM_ACTION_CACHE_OPEN_PARTIAL_READ;
+  }
+# endif
+
   // update stat, set via string, etc
 
   switch (s->cache_info.action) {
@@ -4768,6 +4798,11 @@ HttpTransact::set_headers_for_cache_write(State *s, HTTPInfo *cache_info, HTTPHd
   cache_info->request_get()->field_delete(MIME_FIELD_VIA, MIME_LEN_VIA);
   // server 200 Ok for Range request
   cache_info->request_get()->field_delete(MIME_FIELD_RANGE, MIME_LEN_RANGE);
+  if (NULL != cache_info->response_get()->field_find(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE)) {
+    cache_info->response_get()->field_delete(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE);
+    cache_info->response_get()->field_delete(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH);
+    cache_info->response_get()->status_set(HTTP_STATUS_OK);
+  }
 
   // If we're ignoring auth, then we don't want to cache WWW-Auth
   //  headers
@@ -5142,6 +5177,8 @@ HttpTransact::add_client_ip_to_outgoing_request(State *s, HTTPHdr *request)
 HttpTransact::RequestError_t
 HttpTransact::check_request_validity(State *s, HTTPHdr *incoming_hdr)
 {
+  MIMEField* f; // temp for field checks.
+
   if (incoming_hdr == 0) {
     return NON_EXISTANT_REQUEST_HEADER;
   }
@@ -5261,6 +5298,14 @@ HttpTransact::check_request_validity(State *s, HTTPHdr *incoming_hdr)
     }
   }
 
+  if (0 != (f = incoming_hdr->field_find(MIME_FIELD_RANGE, MIME_LEN_RANGE))) {
+    int len;
+    char const* val = f->value_get(&len);
+    if (!s->hdr_info.request_range.parseRangeFieldValue(val, len))
+      return INVALID_RANGE_FIELD;
+  }
+
+
   return NO_REQUEST_HEADER_ERROR;
 }
 
@@ -5605,6 +5650,8 @@ HttpTransact::initialize_state_variables_from_request(State *s, HTTPHdr *obsolet
 void
 HttpTransact::initialize_state_variables_from_response(State *s, HTTPHdr *incoming_response)
 {
+  MIMEField* field;
+
   /* check if the server permits caching */
   s->cache_info.directives.does_server_permit_storing =
     HttpTransactHeaders::does_server_allow_response_to_be_stored(&s->hdr_info.server_response);
@@ -5644,8 +5691,7 @@ HttpTransact::initialize_state_variables_from_response(State *s, HTTPHdr *incomi
     // This code used to discriminate CL: headers when the origin disabled keep-alive.
     if (incoming_response->presence(MIME_PRESENCE_CONTENT_LENGTH)) {
       int64_t cl = incoming_response->get_content_length();
-
-      s->hdr_info.response_content_length = (cl >= 0) ? cl : HTTP_UNDEFINED_CL;
+      s->hdr_info.response_content_length = cl < 0 ? HTTP_UNDEFINED_CL : cl;
       s->hdr_info.trust_response_cl = true;
     } else {
       s->hdr_info.response_content_length = HTTP_UNDEFINED_CL;
@@ -5654,8 +5700,7 @@ HttpTransact::initialize_state_variables_from_response(State *s, HTTPHdr *incomi
   }
 
   if (incoming_response->presence(MIME_PRESENCE_TRANSFER_ENCODING)) {
-    MIMEField *field = incoming_response->field_find(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING);
-    ink_assert(field != NULL);
+    field = incoming_response->field_find(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING);
 
     HdrCsvIter enc_val_iter;
     int enc_val_len;
@@ -5716,6 +5761,14 @@ HttpTransact::initialize_state_variables_from_response(State *s, HTTPHdr *incomi
     }
   }
 
+  // Get the incoming range to store from the origin.
+  if (NULL != (field = incoming_response->field_find(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE))) {
+    int len;
+    char const* cr = field->value_get(&len);
+    s->hdr_info.response_content_size = HTTPRangeSpec::parseContentRangeFieldValue(cr, len, s->hdr_info.response_range, s->hdr_info.response_range_boundary);
+  }      
+
+
   s->current.server->transfer_encoding = NO_TRANSFER_ENCODING;
 }
 
@@ -6109,10 +6162,15 @@ HttpTransact::is_response_cacheable(State *s, HTTPHdr *request, HTTPHdr *respons
     }
   }
   // do not cache partial content - Range response
-  if (response_code == HTTP_STATUS_PARTIAL_CONTENT || response_code == HTTP_STATUS_RANGE_NOT_SATISFIABLE) {
-    DebugTxn("http_trans", "[is_response_cacheable] "
-                           "response code %d - don't cache",
-             response_code);
+  if (response_code == HTTP_STATUS_RANGE_NOT_SATISFIABLE) {
+    DebugTxn("http_trans", "[is_response_cacheable] " "response code %d - don't cache", response_code);
+    return false;
+  } else if (response->presence(MIME_PRESENCE_CONTENT_RANGE) && !s->hdr_info.response_range.isValid()) {
+    if (0 <= s->hdr_info.response_content_size) {
+      DebugTxn("http_trans", "[is_response_cacheable] " "Content-Range header present with unsatisfiable range");
+    } else {
+      DebugTxn("http_trans", "[is_response_cacheable] " "Content-Range header present but unparsable");
+    }
     return false;
   }
 
@@ -6176,8 +6234,11 @@ HttpTransact::is_response_cacheable(State *s, HTTPHdr *request, HTTPHdr *respons
   }
   // default cacheability
   if (!s->txn_conf->negative_caching_enabled) {
-    if ((response_code == HTTP_STATUS_OK) || (response_code == HTTP_STATUS_NOT_MODIFIED) ||
-        (response_code == HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION) || (response_code == HTTP_STATUS_MOVED_PERMANENTLY) ||
+    if ((response_code == HTTP_STATUS_OK) ||
+        (response_code == HTTP_STATUS_NOT_MODIFIED) ||
+        (response_code == HTTP_STATUS_PARTIAL_CONTENT) ||
+        (response_code == HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION) ||
+        (response_code == HTTP_STATUS_MOVED_PERMANENTLY) ||
         (response_code == HTTP_STATUS_MULTIPLE_CHOICES) || (response_code == HTTP_STATUS_GONE)) {
       DebugTxn("http_trans", "[is_response_cacheable] YES by default ");
       return true;
@@ -6297,24 +6358,33 @@ HttpTransact::is_request_valid(State *s, HTTPHdr *incoming_request)
     SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
     build_error_response(s, HTTP_STATUS_FORBIDDEN, "Tunnel Forbidden", "access#connect_forbidden", NULL);
     return false;
-  case NO_POST_CONTENT_LENGTH: {
-    DebugTxn("http_trans", "[is_request_valid] post request without content length");
-    SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
-    build_error_response(s, HTTP_STATUS_LENGTH_REQUIRED, "Content Length Required", "request#no_content_length", NULL);
-    return false;
-  }
-  case UNACCEPTABLE_TE_REQUIRED: {
-    DebugTxn("http_trans", "[is_request_valid] TE required is unacceptable.");
-    SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
-    build_error_response(s, HTTP_STATUS_NOT_ACCEPTABLE, "Transcoding Not Available", "transcoding#unsupported", NULL);
-    return false;
-  }
-  case INVALID_POST_CONTENT_LENGTH: {
-    DebugTxn("http_trans", "[is_request_valid] post request with negative content length value");
-    SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
-    build_error_response(s, HTTP_STATUS_BAD_REQUEST, "Invalid Content Length", "request#invalid_content_length", NULL);
-    return false;
-  }
+  case NO_POST_CONTENT_LENGTH:
+    {
+      DebugTxn("http_trans", "[is_request_valid] post request without content length");
+      SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
+      build_error_response(s, HTTP_STATUS_LENGTH_REQUIRED, "Content Length Required", "request#no_content_length", NULL);
+      return false;
+    }
+  case UNACCEPTABLE_TE_REQUIRED:
+    {
+      DebugTxn("http_trans", "[is_request_valid] TE required is unacceptable.");
+      SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
+      build_error_response(s, HTTP_STATUS_NOT_ACCEPTABLE, "Transcoding Not Available", "transcoding#unsupported", NULL);
+      return false;
+    }
+  case INVALID_POST_CONTENT_LENGTH :
+    {
+      DebugTxn("http_trans", "[is_request_valid] post request with negative content length value");
+      SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
+      build_error_response(s, HTTP_STATUS_BAD_REQUEST, "Invalid Content Length", "request#invalid_content_length", NULL);
+      return false;
+    }
+  case INVALID_RANGE_FIELD :
+    {
+      DebugTxn("http_trans", "[is_request_valid] a Range field was present with an invalid range specification");
+      SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD);
+      build_error_response(s, HTTP_STATUS_BAD_REQUEST, "Invalid Range", "request#syntax_error", NULL);
+    }
   default:
     return true;
   }
@@ -6587,6 +6657,7 @@ void
 HttpTransact::handle_content_length_header(State *s, HTTPHdr *header, HTTPHdr *base)
 {
   int64_t cl = HTTP_UNDEFINED_CL;
+
   ink_assert(header->type_get() == HTTP_TYPE_RESPONSE);
   if (base->presence(MIME_PRESENCE_CONTENT_LENGTH)) {
     cl = base->get_content_length();
@@ -6598,14 +6669,17 @@ HttpTransact::handle_content_length_header(State *s, HTTPHdr *header, HTTPHdr *b
       case SOURCE_HTTP_ORIGIN_SERVER:
         // We made our decision about whether to trust the
         //   response content length in init_state_vars_from_response()
-        if (s->range_setup != HttpTransact::RANGE_NOT_TRANSFORM_REQUESTED)
-          break;
+        if (s->hdr_info.request_range.hasRanges()) {
+          change_response_header_because_of_range_request(s,header);
+          s->hdr_info.trust_response_cl = true;
+        }
+        break;
 
       case SOURCE_CACHE:
         // if we are doing a single Range: request, calculate the new
         // C-L: header
-        if (s->range_setup == HttpTransact::RANGE_NOT_TRANSFORM_REQUESTED) {
-          change_response_header_because_of_range_request(s, header);
+        if (s->hdr_info.request_range.hasRanges()) {
+          change_response_header_because_of_range_request(s,header);
           s->hdr_info.trust_response_cl = true;
         }
         ////////////////////////////////////////////////
@@ -7619,7 +7693,7 @@ HttpTransact::is_request_likely_cacheable(State *s, HTTPHdr *request)
 }
 
 void
-HttpTransact::build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_request, HTTPVersion outgoing_version)
+HttpTransact::build_request(State* s, HTTPHdr* base_request, HTTPHdr* outgoing_request, HTTPVersion outgoing_version, HTTPRangeSpec const* ranges)
 {
   // this part is to restore the original URL in case, multiple cache
   // lookups have happened - client request has been changed as the result
@@ -7646,6 +7720,7 @@ HttpTransact::build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_r
   HttpTransactHeaders::remove_privacy_headers_from_request(s->http_config_param, s->txn_conf, outgoing_request);
   HttpTransactHeaders::add_global_user_agent_header_to_request(s->txn_conf, outgoing_request);
   handle_request_keep_alive_headers(s, outgoing_version, outgoing_request);
+  if (ranges) HttpTransactHeaders::insert_request_range_header(outgoing_request, ranges);
 
   // handle_conditional_headers appears to be obsolete.  Nothing happens
   // unelss s->cache_info.action == HttpTransact::CACHE_DO_UPDATE.  In that
@@ -8863,8 +8938,12 @@ HttpTransact::delete_warning_value(HTTPHdr *to_warn, HTTPWarningCode warning_cod
 void
 HttpTransact::change_response_header_because_of_range_request(State *s, HTTPHdr *header)
 {
-  MIMEField *field;
+  MIMEField *field = header->field_find(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
   char *reason_phrase;
+//  CacheVConnection* cache_read_vc = s->state_machine->get_cache_sm().cache_read_vc;
+//  HTTPHdr* cached_response = find_appropriate_cached_resp(s);
+//  HTTPRangeSpec& rs = cache_read_vc->get_http_range_spec();
+  HTTPRangeSpec& rs = s->state_machine->t_state.hdr_info.request_range;
 
   Debug("http_trans", "Partial content requested, re-calculating content-length");
 
@@ -8873,36 +8952,37 @@ HttpTransact::change_response_header_because_of_range_request(State *s, HTTPHdr
   header->reason_set(reason_phrase, strlen(reason_phrase));
 
   // set the right Content-Type for multiple entry Range
-  if (s->num_range_fields > 1) {
-    field = header->field_find(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
+  if (rs.isMulti()) { // means we need a boundary string.
+    ink_release_assert(!"[amc] Computation of boundary string not correct working");
+#   if 0
+    int rbs_len;
+    char const* rbs = cache_read_vc->get_http_range_boundary_string(&rbs_len);
+    char buff[(sizeof(HTTP_RANGE_MULTIPART_CONTENT_TYPE)-1) + HTTP_RANGE_BOUNDARY_LEN];
 
     if (field != NULL)
       header->field_delete(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
 
     field = header->field_create(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
-    field->value_append(header->m_heap, header->m_mime, range_type, sizeof(range_type) - 1);
+    snprintf(buff, sizeof(buff), "%s%.*s", HTTP_RANGE_MULTIPART_CONTENT_TYPE, rbs_len, rbs);
+    field->value_append(header->m_heap, header->m_mime, buff, sizeof(buff));
 
     header->field_attach(field);
-    // TODO: There's a known bug here where the Content-Length is not correct for multi-part
-    // Range: requests.
-    header->set_content_length(s->range_output_cl);
-  } else {
-    if (s->cache_info.object_read && s->cache_info.object_read->valid()) {
-      // TODO: It's unclear under which conditions we need to update the Content-Range: header,
-      // many times it's already set correctly before calling this. For now, always try do it
-      // when we have the information for it available.
-      // TODO: Also, it's unclear as to why object_read->valid() is not always true here.
-      char numbers[RANGE_NUMBERS_LENGTH];
-      header->field_delete(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE);
-      field = header->field_create(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE);
-      snprintf(numbers, sizeof(numbers), "bytes %" PRId64 "-%" PRId64 "/%" PRId64, s->ranges[0]._start, s->ranges[0]._end,
-               s->cache_info.object_read->object_size_get());
-      field->value_set(header->m_heap, header->m_mime, numbers, strlen(numbers));
-      header->field_attach(field);
-    }
-    // Always update the Content-Length: header.
-    header->set_content_length(s->range_output_cl);
+#   endif
+  } else if (rs.isSingle()) {
+    int n;
+    char buff[HTTP_LEN_BYTES + (18+1)*3];
+    header->field_delete(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE);
+    field = header->field_create(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE);
+    n = snprintf(buff, sizeof(buff), "%s %" PRIu64"-%" PRIu64"/%" PRId64
+                , HTTP_VALUE_BYTES
+                , rs[0]._min, rs[0]._max
+                , s->state_machine->t_state.hdr_info.response_content_size
+      );
+    field->value_set(header->m_heap, header->m_mime, buff, n);
+    header->field_attach(field);
+    header->set_content_length(rs.size());
   }
+//  header->set_content_length(cache_read_vc->get_effective_content_size());
 }
 
 #if TS_HAS_TESTS

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/http/HttpTransact.h
----------------------------------------------------------------------
diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h
index e598951..64ee656 100644
--- a/proxy/http/HttpTransact.h
+++ b/proxy/http/HttpTransact.h
@@ -21,7 +21,6 @@
   limitations under the License.
  */
 
-
 #if !defined(_HttpTransact_h_)
 #define _HttpTransact_h_
 
@@ -54,44 +53,37 @@
 #define ACQUIRE_PRINT_LOCK() // ink_mutex_acquire(&print_lock);
 #define RELEASE_PRINT_LOCK() // ink_mutex_release(&print_lock);
 
-#define DUMP_HEADER(T, H, I, S)                                 \
-  {                                                             \
-    if (diags->on(T)) {                                         \
-      ACQUIRE_PRINT_LOCK()                                      \
-      fprintf(stderr, "+++++++++ %s +++++++++\n", S);           \
-      fprintf(stderr, "-- State Machine Id: %" PRId64 "\n", I); \
-      char b[4096];                                             \
-      int used, tmp, offset;                                    \
-      int done;                                                 \
-      offset = 0;                                               \
-      if ((H)->valid()) {                                       \
-        do {                                                    \
-          used = 0;                                             \
-          tmp = offset;                                         \
-          done = (H)->print(b, 4095, &used, &tmp);              \
-          offset += used;                                       \
-          b[used] = '\0';                                       \
-          fprintf(stderr, "%s", b);                             \
-        } while (!done);                                        \
-      }                                                         \
-      RELEASE_PRINT_LOCK()                                      \
-    }                                                           \
+#define DUMP_HEADER(T, H, I, S)                                            \
+  {                                                                        \
+    if (diags->on(T)) {                                                    \
+      ACQUIRE_PRINT_LOCK() fprintf(stderr, "+++++++++ %s +++++++++\n", S); \
+      fprintf(stderr, "-- State Machine Id: %" PRId64 "\n", I);            \
+      char b[4096];                                                        \
+      int used, tmp, offset;                                               \
+      int done;                                                            \
+      offset = 0;                                                          \
+      if ((H)->valid()) {                                                  \
+        do {                                                               \
+          used = 0;                                                        \
+          tmp = offset;                                                    \
+          done = (H)->print(b, 4095, &used, &tmp);                         \
+          offset += used;                                                  \
+          b[used] = '\0';                                                  \
+          fprintf(stderr, "%s", b);                                        \
+        } while (!done);                                                   \
+      }                                                                    \
+      RELEASE_PRINT_LOCK()                                                 \
+    }                                                                      \
   }
 
-
 #define TRANSACT_SETUP_RETURN(n, r) \
   s->next_action = n;               \
   s->transact_return_point = r;     \
   DebugSpecific((s->state_machine && s->state_machine->debug_on), "http_trans", "Next action %s; %s", #n, #r);
 
-#define TRANSACT_RETURN(n, r) \
-  TRANSACT_SETUP_RETURN(n, r) \
-  return;
-
-#define TRANSACT_RETURN_VAL(n, r, v) \
-  TRANSACT_SETUP_RETURN(n, r)        \
-  return v;
+#define TRANSACT_RETURN(n, r) TRANSACT_SETUP_RETURN(n, r) return;
 
+#define TRANSACT_RETURN_VAL(n, r, v) TRANSACT_SETUP_RETURN(n, r) return v;
 
 #define SET_UNPREPARE_CACHE_ACTION(C)                               \
   {                                                                 \
@@ -371,6 +363,7 @@ public:
     SCHEME_NOT_SUPPORTED,
     UNACCEPTABLE_TE_REQUIRED,
     INVALID_POST_CONTENT_LENGTH,
+    INVALID_RANGE_FIELD,
     TOTAL_REQUEST_ERROR_TYPES
   };
 
@@ -440,22 +433,18 @@ public:
     // SM_ACTION_AUTH_LOOKUP,
     SM_ACTION_DNS_LOOKUP,
     SM_ACTION_DNS_REVERSE_LOOKUP,
-
     SM_ACTION_CACHE_LOOKUP,
     SM_ACTION_CACHE_ISSUE_WRITE,
     SM_ACTION_CACHE_ISSUE_WRITE_TRANSFORM,
     SM_ACTION_CACHE_PREPARE_UPDATE,
     SM_ACTION_CACHE_ISSUE_UPDATE,
-
+    SM_ACTION_CACHE_OPEN_PARTIAL_READ,
     SM_ACTION_ICP_QUERY,
-
     SM_ACTION_ORIGIN_SERVER_OPEN,
     SM_ACTION_ORIGIN_SERVER_RAW_OPEN,
     SM_ACTION_ORIGIN_SERVER_RR_MARK_DOWN,
-
     SM_ACTION_READ_PUSH_HDR,
     SM_ACTION_STORE_PUSH_BODY,
-
     SM_ACTION_INTERNAL_CACHE_DELETE,
     SM_ACTION_INTERNAL_CACHE_NOOP,
     SM_ACTION_INTERNAL_CACHE_UPDATE_HEADERS,
@@ -467,14 +456,12 @@ public:
 #ifdef PROXY_DRAIN
     SM_ACTION_DRAIN_REQUEST_BODY,
 #endif /* PROXY_DRAIN */
-
     SM_ACTION_SERVE_FROM_CACHE,
     SM_ACTION_SERVER_READ,
     SM_ACTION_SERVER_PARSE_NEXT_HDR,
     SM_ACTION_TRANSFORM_READ,
     SM_ACTION_SSL_TUNNEL,
     SM_ACTION_CONTINUE,
-
     SM_ACTION_API_SM_START,
     SM_ACTION_API_READ_REQUEST_HDR,
     SM_ACTION_API_PRE_REMAP,
@@ -486,7 +473,6 @@ public:
     SM_ACTION_API_READ_RESPONSE_HDR,
     SM_ACTION_API_SEND_RESPONSE_HDR,
     SM_ACTION_API_SM_SHUTDOWN,
-
     SM_ACTION_REMAP_REQUEST,
     SM_ACTION_POST_REMAP_SKIP,
     SM_ACTION_REDIRECT_READ
@@ -754,9 +740,15 @@ public:
     HTTPHdr transform_response;
     HTTPHdr cache_response;
     int64_t request_content_length;
-    int64_t response_content_length;
+    int64_t response_content_length; // Length of the payload (Content-Length
+                                     // field)
+    int64_t response_content_size;   // Total size of the object on the origin
+                                     // server.
     int64_t transform_request_cl;
     int64_t transform_response_cl;
+    HTTPRangeSpec request_range;
+    HTTPRangeSpec::Range response_range;
+    ts::ConstBuffer response_range_boundary; // not used yet
     bool client_req_is_server_style;
     bool trust_response_cl;
     ResponseError_t response_error;
@@ -780,7 +772,6 @@ public:
     _SquidLogInfo() : log_code(SQUID_LOG_ERR_UNKNOWN), hier_code(SQUID_HIER_EMPTY), hit_miss_code(SQUID_MISS_NONE) {}
   } SquidLogInfo;
 
-
 #define HTTP_TRANSACT_STATE_MAX_XBUF_SIZE (1024 * 2) /* max size of plugin exchange buffer */
 
   struct State {
@@ -796,8 +787,10 @@ public:
     DNSLookupInfo dns_info;
     RedirectInfo redirect_info;
     unsigned int updated_server_version;
-    bool is_revalidation_necessary; // Added to check if revalidation is necessary - YTS Team, yamsat
-    bool request_will_not_selfloop; // To determine if process done - YTS Team, yamsat
+    bool is_revalidation_necessary; // Added to check if revalidation is
+                                    // necessary - YTS Team, yamsat
+    bool request_will_not_selfloop; // To determine if process done - YTS Team,
+                                    // yamsat
     ConnectionAttributes client_info;
     ConnectionAttributes icp_info;
     ConnectionAttributes parent_info;
@@ -835,7 +828,8 @@ public:
     StateMachineAction_t api_next_action;                  // out
     void (*transact_return_point)(HttpTransact::State *s); // out
 
-    // We keep this so we can jump back to the upgrade handler after remap is complete
+    // We keep this so we can jump back to the upgrade handler after remap is
+    // complete
     bool is_upgrade_request;
     void (*post_remap_upgrade_return_point)(HttpTransact::State *s); // out
     const char *upgrade_token_wks;
@@ -896,7 +890,8 @@ public:
     int api_txn_no_activity_timeout_value;
 
     // Used by INKHttpTxnCachedReqGet and INKHttpTxnCachedRespGet SDK functions
-    // to copy part of HdrHeap (only the writable portion) for cached response headers
+    // to copy part of HdrHeap (only the writable portion) for cached response
+    // headers
     // and request headers
     // These ptrs are deallocate when transaction is over.
     HdrHeapSDKHandle *cache_req_hdr_heap_handle;
@@ -944,7 +939,8 @@ public:
     RangeRecord *ranges;
 
     OverridableHttpConfigParams *txn_conf;
-    OverridableHttpConfigParams my_txn_conf; // Storage for plugins, to avoid malloc
+    OverridableHttpConfigParams my_txn_conf; // Storage for plugins, to avoid
+                                             // malloc
 
     bool transparent_passthrough;
     bool range_in_cache;
@@ -1211,7 +1207,8 @@ public:
   static bool will_this_request_self_loop(State *s);
   static bool is_request_likely_cacheable(State *s, HTTPHdr *request);
 
-  static void build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_request, HTTPVersion outgoing_version);
+  static void build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_request, HTTPVersion outgoing_version,
+                            HTTPRangeSpec const *ranges = 0);
   static void build_response(State *s, HTTPHdr *base_response, HTTPHdr *outgoing_response, HTTPVersion outgoing_version,
                              HTTPStatus status_code, const char *reason_phrase = NULL);
   static void build_response(State *s, HTTPHdr *base_response, HTTPHdr *outgoing_response, HTTPVersion outgoing_version);
@@ -1253,7 +1250,8 @@ public:
   static void client_result_stat(State *s, ink_hrtime total_time, ink_hrtime request_process_time);
   static void add_new_stat_block(State *s);
   static void delete_warning_value(HTTPHdr *to_warn, HTTPWarningCode warning_code);
-  static bool is_connection_collapse_checks_success(State *s); // YTS Team, yamsat
+  static bool is_connection_collapse_checks_success(State *s); // YTS Team,
+                                                               // yamsat
 };
 
 typedef void (*TransactEntryFunc_t)(HttpTransact::State *s);

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/http/HttpTransactHeaders.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpTransactHeaders.cc b/proxy/http/HttpTransactHeaders.cc
index 28cdffc..a5277bb 100644
--- a/proxy/http/HttpTransactHeaders.cc
+++ b/proxy/http/HttpTransactHeaders.cc
@@ -1036,3 +1036,17 @@ HttpTransactHeaders::remove_privacy_headers_from_request(HttpConfigParams *http_
     }
   }
 }
+
+void
+HttpTransactHeaders::insert_request_range_header(HTTPHdr* header, HTTPRangeSpec const* ranges)
+{
+  int n;
+  char buff[1024];
+ 
+  if (ranges->hasRanges()) {
+    int64_t ffs = cacheProcessor.get_fixed_fragment_size();
+    n = ranges->print_quantized(buff, sizeof(buff), ffs, ffs, 4 * ffs);
+    header->value_set(MIME_FIELD_RANGE, MIME_LEN_RANGE, buff, n);
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/http/HttpTransactHeaders.h
----------------------------------------------------------------------
diff --git a/proxy/http/HttpTransactHeaders.h b/proxy/http/HttpTransactHeaders.h
index 505a6fa..e10dd08 100644
--- a/proxy/http/HttpTransactHeaders.h
+++ b/proxy/http/HttpTransactHeaders.h
@@ -85,6 +85,7 @@ public:
   static void remove_privacy_headers_from_request(HttpConfigParams *http_config_param, OverridableHttpConfigParams *http_txn_conf,
                                                   HTTPHdr *header);
 
+  static void insert_request_range_header(HTTPHdr* header, HTTPRangeSpec const* ranges);
   static int nstrcpy(char *d, const char *as);
 };
 


[2/3] trafficserver git commit: TS-974: Base implmentation.

Posted by am...@apache.org.
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/P_CacheBC.h
----------------------------------------------------------------------
diff --git a/iocore/cache/P_CacheBC.h b/iocore/cache/P_CacheBC.h
index 2164692..2ffd4e8 100644
--- a/iocore/cache/P_CacheBC.h
+++ b/iocore/cache/P_CacheBC.h
@@ -33,9 +33,9 @@ namespace cache_bc
 */
 
 typedef HTTPHdr HTTPHdr_v21;
+typedef HTTPHdr HTTPHdr_v23;
 typedef HdrHeap HdrHeap_v23;
 typedef CryptoHash CryptoHash_v23;
-typedef HTTPCacheAlt HTTPCacheAlt_v23;
 
 /** Cache backwards compatibility structure - the fragment table.
     This is copied from @c HTTPCacheAlt in @c HTTP.h.
@@ -120,6 +120,34 @@ struct Doc_v23 {
   size_t data_len();
 };
 
+struct HTTPCacheAlt_v23 {
+  uint32_t m_magic;
+  int32_t m_writeable;
+  int32_t m_unmarshal_len;
+
+  int32_t m_id;
+  int32_t m_rid;
+
+  int32_t m_object_key[4];
+  int32_t m_object_size[2];
+
+  HTTPHdr_v23 m_request_hdr;
+  HTTPHdr_v23 m_response_hdr;
+
+  time_t m_request_sent_time;
+  time_t m_response_received_time;
+
+  int m_frag_offset_count;
+  typedef uint64_t FragOffset;
+  FragOffset *m_frag_offsets;
+  static int const N_INTEGRAL_FRAG_OFFSETS = 4;
+  FragOffset m_integral_frag_offsets[N_INTEGRAL_FRAG_OFFSETS];
+
+  RefCountObj *m_ext_buffer;
+};
+
+typedef HTTPCacheAlt_v23 HTTPCacheAlt_v24; // no changes between these versions.
+
 static size_t const sizeofDoc_v23 = sizeof(Doc_v23);
 char *
 Doc_v23::data()

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/P_CacheDir.h
----------------------------------------------------------------------
diff --git a/iocore/cache/P_CacheDir.h b/iocore/cache/P_CacheDir.h
index 0a31c32..0571150 100644
--- a/iocore/cache/P_CacheDir.h
+++ b/iocore/cache/P_CacheDir.h
@@ -242,43 +242,74 @@ void dir_clean_range_interimvol(off_t start, off_t end, InterimCacheVol *svol);
 #define dir_prev(_e) (_e)->w[2]
 #define dir_set_prev(_e, _o) (_e)->w[2] = (uint16_t)(_o)
 
-// INKqa11166 - Cache can not store 2 HTTP alternates simultaneously.
-// To allow this, move the vector from the CacheVC to the OpenDirEntry.
-// Each CacheVC now maintains a pointer to this vector. Adding/Deleting
-// alternates from this vector is done under the Vol::lock. The alternate
-// is deleted/inserted into the vector just before writing the vector disk
-// (CacheVC::updateVector).
-LINK_FORWARD_DECLARATION(CacheVC, opendir_link) // forward declaration
 struct OpenDirEntry {
-  DLL<CacheVC, Link_CacheVC_opendir_link> writers; // list of all the current writers
-  DLL<CacheVC, Link_CacheVC_opendir_link> readers; // list of all the current readers - not used
-  CacheHTTPInfoVector vector;                      // Vector for the http document. Each writer
-                                                   // maintains a pointer to this vector and
-                                                   // writes it down to disk.
-  CacheKey single_doc_key;                         // Key for the resident alternate.
-  Dir single_doc_dir;                              // Directory for the resident alternate
-  Dir first_dir;                                   // Dir for the vector. If empty, a new dir is
-                                                   // inserted, otherwise this dir is overwritten
-  uint16_t num_writers;                            // num of current writers
-  uint16_t max_writers;                            // max number of simultaneous writers allowed
-  bool dont_update_directory;                      // if set, the first_dir is not updated.
-  bool move_resident_alt;                          // if set, single_doc_dir is inserted.
-  volatile bool reading_vec;                       // somebody is currently reading the vector
-  volatile bool writing_vec;                       // somebody is currently writing the vector
+  typedef OpenDirEntry self; ///< Self reference type.
+
+  Ptr<ProxyMutex> mutex;
+
+  /// Vector for the http document. Each writer maintains a pointer to this vector and writes it down to disk.
+  CacheHTTPInfoVector vector;
+  CacheKey first_key;         ///< Key for first doc for this object.
+  CacheKey single_doc_key;    // Key for the resident alternate.
+  Dir single_doc_dir;         // Directory for the resident alternate
+  Dir first_dir;              // Dir for the vector. If empty, a new dir is
+                              // inserted, otherwise this dir is overwritten
+  uint16_t num_writers;       // num of current writers
+  uint16_t max_writers;       // max number of simultaneous writers allowed
+  bool dont_update_directory; // if set, the first_dir is not updated.
+  bool move_resident_alt;     // if set, single_doc_dir is inserted.
+  volatile bool reading_vec;  // somebody is currently reading the vector
+  volatile bool writing_vec;  // somebody is currently writing the vector
+
+  /** Set to a write @c CacheVC that has started but not yet updated the vector.
+
+      If this is set then there is a write @c CacheVC that is active but has not yet been able to
+      update the vector for its alternate. Any new reader should block on open if this is set and
+      enter itself on the @a _waiting list, making this effectively a write lock on the object.
+      This is necessary because we can't reliably do alternate selection in this state. The waiting
+      read @c CacheVC instances are released as soon as the vector is updated, they do not have to
+      wait until the write @c CacheVC has finished its transaction. In practice this means until the
+      server response has been received and processed.
+  */
+  volatile CacheVC *open_writer;
+  /** A list of @c CacheVC instances that are waiting for the @a open_writer.
+   */
+  DLL<CacheVC, Link_CacheVC_Active_Link> open_waiting;
 
   LINK(OpenDirEntry, link);
 
-  int wait(CacheVC *c, int msec);
+  //  int wait(CacheVC *c, int msec);
 
   bool
   has_multiple_writers()
   {
     return num_writers > 1;
   }
+
+  /// Get the alternate index for the @a key.
+  int index_of(CacheKey const &key);
+  /// Check if there are any writers for the alternate of @a alt_key.
+  bool has_writer(CacheKey const &alt_key);
+  /// Mark a @c CacheVC as actively writing at @a offset on the alternate with @a alt_key.
+  self &write_active(CacheKey const &alt_key, CacheVC *vc, int64_t offset);
+  /// Mark an active write by @a vc as complete and indicate whether it had @a success.
+  /// If the write is not @a success then the fragment is not marked as cached.
+  self &write_complete(CacheKey const &alt_key, CacheVC *vc, bool success = true);
+  /// Indicate if a VC is currently writing to the fragment with this @a offset.
+  bool is_write_active(CacheKey const &alt_key, int64_t offset);
+  /// Get the fragment key for a specific @a offset.
+  CacheKey const &key_for(CacheKey const &alt_key, int64_t offset);
+  /** Wait for a fragment to be written.
+   */
+  self &waiting_for(CacheKey const &alt_key, CacheVC *vc, int64_t offset);
+  /// Close out anything related to this writer
+  self &close_writer(CacheKey const &alt_key, CacheVC *vc);
 };
 
 struct OpenDir : public Continuation {
-  Queue<CacheVC, Link_CacheVC_opendir_link> delayed_readers;
+  typedef Queue<CacheVC, Link_CacheVC_OpenDir_Link> CacheVCQ;
+  CacheVCQ delayed_readers;
+
   DLL<OpenDirEntry> bucket[OPEN_DIR_BUCKETS];
 
   int open_write(CacheVC *c, int allow_if_writers, int max_writers);

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/P_CacheHttp.h
----------------------------------------------------------------------
diff --git a/iocore/cache/P_CacheHttp.h b/iocore/cache/P_CacheHttp.h
index c5bf87e..54fb523 100644
--- a/iocore/cache/P_CacheHttp.h
+++ b/iocore/cache/P_CacheHttp.h
@@ -34,7 +34,6 @@ typedef URL CacheURL;
 typedef HTTPHdr CacheHTTPHdr;
 typedef HTTPInfo CacheHTTPInfo;
 
-
 #define OFFSET_BITS 24
 enum {
   OWNER_NONE = 0,
@@ -48,11 +47,54 @@ struct CacheHTTPInfo {
 
 #endif // HTTP_CACHE
 
-struct vec_info {
-  CacheHTTPInfo alternate;
-};
+LINK_FORWARD_DECLARATION(CacheVC, OpenDir_Link) // forward declaration
+LINK_FORWARD_DECLARATION(CacheVC, Active_Link)  // forward declaration
 
 struct CacheHTTPInfoVector {
+  typedef CacheHTTPInfoVector self; ///< Self reference type.
+
+  struct Item {
+    /// Descriptor for an alternate for this object.
+    CacheHTTPInfo _alternate;
+    /// CacheVCs which are writing data to this alternate.
+    DLL<CacheVC, Link_CacheVC_OpenDir_Link> _writers;
+    ///@{ Active I/O
+    /** These two lists tracks active / outstanding I/O operations on The @a _active list is for writers
+        and the CacheVC should be on this list iff it has initiated an I/O that has not yet
+        completed. The @a _waiting list is for CacheVCs that are waiting for a fragment that is being written
+        by a CacheVC on the @a _active list. That is, it is waiting on the same I/O operation as an @a _active
+        CacheVC.
+
+        @internal An alternative implementation would be to have an array with an element for each fragment. With
+        this scheme we will have to linear search these lists to find the corresponding fragment I/O if any.
+        However, these lists should be short (only very rarely more than 1 or 2) and an array, given the ever
+        larger objects to be stored, would be large and require allocation. For these reasons I think this is the
+        better choice.
+    */
+    /// CacheVCs with pending write I/O.
+    DLL<CacheVC, Link_CacheVC_Active_Link> _active;
+    /// CacheVCs waiting on fragments.
+    DLL<CacheVC, Link_CacheVC_Active_Link> _waiting;
+    // To minimize list walking, we track the convex hull of fragments for which readers are waiting.
+    // We update the values whenever we must actually walk the list.
+    // Otherwise we maintain the convex hull invariant so if a written fragment is outside the range,
+    // we can assume no reader was waiting for it.
+    /// lowest fragment index for which a reader is waiting.
+    int _wait_idx_min;
+    /// highest fragment inddex for which a reader is waiting.
+    int _wait_idx_max;
+    /// Flag
+    union {
+      uint16_t _flags;
+      struct {
+        unsigned int dirty : 1;
+      } f;
+    };
+    ///@}
+  };
+
+  typedef CacheArray<Item> InfoVector;
+
   void *magic;
 
   CacheHTTPInfoVector();
@@ -63,6 +105,7 @@ struct CacheHTTPInfoVector {
   {
     return xcount;
   }
+
   int insert(CacheHTTPInfo *info, int id = -1);
   CacheHTTPInfo *get(int idx);
   void detach(int idx, CacheHTTPInfo *r);
@@ -79,19 +122,239 @@ struct CacheHTTPInfoVector {
   int marshal_length();
   int marshal(char *buf, int length);
   uint32_t get_handles(const char *buf, int length, RefCountObj *block_ptr = NULL);
-  int unmarshal(const char *buf, int length, RefCountObj *block_ptr);
 
-  CacheArray<vec_info> data;
+  /// Get the alternate index for the @a key.
+  int index_of(CacheKey const &key);
+  /// Check if there are any writers for the alternate of @a alt_key.
+  bool has_writer(CacheKey const &alt_key);
+  /// Mark a @c CacheVC as actively writing at @a offset on the alternate with @a alt_key.
+  self &write_active(CacheKey const &alt_key, CacheVC *vc, int64_t offset);
+  /// Mark an active write by @a vc as complete and indicate whether it had @a success.
+  /// If the write is not @a success then the fragment is not marked as cached.
+  self &write_complete(CacheKey const &alt_key, CacheVC *vc, bool success = true);
+  /// Indicate if a VC is currently writing to the fragment with this @a offset.
+  bool is_write_active(CacheKey const &alt_key, int64_t offset);
+  /// Mark a CacheVC as waiting for the fragment containing the byte at @a offset.
+  self &waiting_for(CacheKey const &alt_key, CacheVC *vc, int64_t offset);
+  /// Get the fragment key for a specific @a offset.
+  CacheKey const &key_for(CacheKey const &alt_key, int64_t offset);
+  /// Close out anything related to this writer
+  self &close_writer(CacheKey const &alt_key, CacheVC *vc);
+  /** Compute the convex hull of the uncached parts of the @a request taking current writers in to account.
+
+      @return @c true if there is uncached data that must be retrieved.
+   */
+  HTTPRangeSpec::Range get_uncached_hull(CacheKey const &alt_key, HTTPRangeSpec const &request);
+
+  /** Sigh, yet another custom array class.
+      @c Vec doesn't work because it really only works well with pointers, not objects.
+  */
+  InfoVector data;
+
   int xcount;
   Ptr<RefCountObj> vector_buf;
 };
 
+/** Range operation tracking.
+
+    This holds a range specification. It also tracks the current object offset and the individual range.
+
+    For simplification of the logic that uses this class it will pretend to be a single range of
+    the object size if it is empty. To return the correct response we still need to distinuish
+    those two cases.
+*/
+class CacheRange
+{
+public:
+  typedef CacheRange self; ///< Self reference type.
+
+  /// Default constructor
+  CacheRange() : _offset(0), _idx(-1), _ct_field(NULL), _pending_range_shift_p(false) {}
+
+  /// Test if the range spec has actual ranges in it
+  bool hasRanges() const;
+
+  /// Test for multiple ranges.
+  bool isMulti() const;
+
+  /// Get the current object offset
+  uint64_t getOffset() const;
+
+  /// Get the current range index.
+  int getIdx() const;
+
+  /// Get the number of ranges.
+  size_t count() const;
+
+  /// Get the remaining contiguous bytes for the current range.
+  uint64_t getRemnantSize() const;
+
+  /** Advance @a size bytes in the range spec.
+
+      @return The resulting offset in the object.
+  */
+  uint64_t consume(uint64_t size);
+
+  /** Initialize from a request header.
+   */
+  bool init(HTTPHdr *req);
+
+  /** Set the range to the start of the range set.
+      @return @c true if there is a valid range, @c false otherwise.
+  */
+  bool start();
+
+  /** Apply a content @a len to the ranges.
+
+      @return @c true if successfully applied, @c false otherwise.
+  */
+  bool apply(uint64_t len);
+
+  /** Get the range boundary string.
+      @a len if not @c NULL receives the length of the string.
+  */
+  char const *getBoundaryStr(int *len) const;
+
+  /** Generate the range boundary string */
+  self &generateBoundaryStr(CacheKey const &key);
+
+  /// Get the cached Content-Type field.
+  MIMEField *getContentTypeField() const;
+
+  /// Set the Content-Type field from a response header.
+  self &setContentTypeFromResponse(HTTPHdr *resp);
+
+  /** Calculate the effective HTTP content length value.
+   */
+  uint64_t calcContentLength() const;
+
+  /// Raw access to internal range spec.
+  HTTPRangeSpec &getRangeSpec();
+
+  /// Test if a consume moved across a range boundary.
+  bool hasPendingRangeShift() const;
+
+  /// Clear the pending range shift flag.
+  self &consumeRangeShift();
+
+  /// Range access.
+  HTTPRangeSpec::Range &operator[](int n);
+
+  /// Range access.
+  HTTPRangeSpec::Range const &operator[](int n) const;
+
+  /// Reset to re-usable state.
+  void clear();
+
+protected:
+  uint64_t _len;        ///< Total object length.
+  uint64_t _offset;     ///< Offset in content.
+  int _idx;             ///< Current range index. (< 0 means not in a range)
+  HTTPRangeSpec _r;     ///< The actual ranges.
+  MIMEField *_ct_field; ///< Content-Type field.
+  char _boundary[HTTP_RANGE_BOUNDARY_LEN];
+  bool _pending_range_shift_p;
+};
+
 TS_INLINE CacheHTTPInfo *
 CacheHTTPInfoVector::get(int idx)
 {
   ink_assert(idx >= 0);
   ink_assert(idx < xcount);
-  return &data[idx].alternate;
+  return &data[idx]._alternate;
+}
+
+inline bool
+CacheRange::hasRanges() const
+{
+  return _r.isSingle() || _r.isMulti();
+}
+
+inline uint64_t
+CacheRange::getOffset() const
+{
+  return _offset;
+}
+
+inline int
+CacheRange::getIdx() const
+{
+  return _idx;
+}
+
+inline uint64_t
+CacheRange::getRemnantSize() const
+{
+  uint64_t zret = 0;
+
+  if (_r.isEmpty())
+    zret = _len - _offset;
+  else if (_r.isValid() && 0 <= _idx && _idx < static_cast<int>(_r.count()))
+    zret = (_r[_idx]._max - _offset) + 1;
+
+  return zret;
+}
+
+inline char const *
+CacheRange::getBoundaryStr(int *len) const
+{
+  if (len)
+    *len = sizeof(_boundary);
+  return _boundary;
+}
+
+inline HTTPRangeSpec &
+CacheRange::getRangeSpec()
+{
+  return _r;
+}
+
+inline bool
+CacheRange::isMulti() const
+{
+  return _r.isMulti();
+}
+
+inline bool
+CacheRange::hasPendingRangeShift() const
+{
+  return _pending_range_shift_p;
+}
+
+inline CacheRange &
+CacheRange::consumeRangeShift()
+{
+  _pending_range_shift_p = false;
+  return *this;
+}
+
+inline MIMEField *
+CacheRange::getContentTypeField() const
+{
+  return _ct_field;
+}
+
+inline size_t
+CacheRange::count() const
+{
+  return _r.count();
+}
+
+inline HTTPRangeSpec::Range &CacheRange::operator[](int n)
+{
+  return _r[n];
+}
+
+inline HTTPRangeSpec::Range const &CacheRange::operator[](int n) const
+{
+  return _r[n];
+}
+
+inline CacheRange &
+CacheRange::setContentTypeFromResponse(HTTPHdr *resp)
+{
+  _ct_field = resp->field_find(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
+  return *this;
 }
 
 #endif /* __CACHE_HTTP_H__ */

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/P_CacheInternal.h
----------------------------------------------------------------------
diff --git a/iocore/cache/P_CacheInternal.h b/iocore/cache/P_CacheInternal.h
index 3d95778..1f7ac29 100644
--- a/iocore/cache/P_CacheInternal.h
+++ b/iocore/cache/P_CacheInternal.h
@@ -229,8 +229,15 @@ extern int cache_config_mutex_retry_delay;
 #if TS_USE_INTERIM_CACHE == 1
 extern int good_interim_disks;
 #endif
+
+
 // CacheVC
-struct CacheVC : public CacheVConnection {
+struct CacheVC: public CacheVConnection
+{
+  typedef CacheVC self; ///< Self reference type.
+  typedef HTTPCacheAlt::FragmentDescriptor FragmentDescriptor; ///< Import type.
+  typedef HTTPCacheAlt::FragmentDescriptorTable FragmentDescriptorTable; ///< Import type.
+
   CacheVC();
 
   VIO *do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf);
@@ -280,7 +287,9 @@ struct CacheVC : public CacheVConnection {
     return -1;
   }
 
-  bool writer_done();
+  Action* do_init_write();
+
+  //  bool writer_done();
   int calluser(int event);
   int callcont(int event);
   int die();
@@ -300,6 +309,7 @@ struct CacheVC : public CacheVConnection {
   int openReadReadDone(int event, Event *e);
   int openReadMain(int event, Event *e);
   int openReadStartEarliest(int event, Event *e);
+  int openReadWaitEarliest(int evid, Event* e);
 #ifdef HTTP_CACHE
   int openReadVecWrite(int event, Event *e);
 #endif
@@ -317,13 +327,16 @@ struct CacheVC : public CacheVConnection {
   int openWriteRemoveVector(int event, Event *e);
   int openWriteWriteDone(int event, Event *e);
   int openWriteOverwrite(int event, Event *e);
+  int openWriteInit(int event, Event* e);
   int openWriteMain(int event, Event *e);
   int openWriteStartDone(int event, Event *e);
   int openWriteStartBegin(int event, Event *e);
+  int openWriteEmptyEarliestDone(int event, Event* e);
 
   int updateVector(int event, Event *e);
   int updateReadDone(int event, Event *e);
   int updateVecWrite(int event, Event *e);
+  int updateWriteStateFromRange();
 
   int removeEvent(int event, Event *e);
 
@@ -363,12 +376,27 @@ struct CacheVC : public CacheVConnection {
       @return The address of the start of the fragment table,
       or @c NULL if there is no fragment table.
   */
-  virtual HTTPInfo::FragOffset *get_frag_table();
+  virtual HTTPInfo::FragmentDescriptorTable* get_frag_table();
   /** Load alt pointers and do fixups if needed.
       @return Length of header data used for alternates.
    */
-  virtual uint32_t load_http_info(CacheHTTPInfoVector *info, struct Doc *doc, RefCountObj *block_ptr = NULL);
+  virtual uint32_t load_http_info(CacheHTTPInfoVector* info, struct Doc* doc, RefCountObj * block_ptr = NULL);
+
+  /// Change member @a key to be the key for the @a idx 'th fragment.
+  void update_key_to_frag_idx(int idx);
+  /// Compute the index of the fragment that contains the byte at content location @a offset.
+  int frag_idx_for_offset(uint64_t offset);
+
+  virtual char const* get_http_range_boundary_string(int* len) const;
+  virtual int64_t get_effective_content_size();
+  virtual void set_full_content_length(int64_t size);
+  virtual HTTPRangeSpec& get_http_range_spec();
+  virtual bool is_http_partial_content();
+  virtual bool get_uncached(HTTPRangeSpec& result);
+  virtual int64_t set_inbound_range(int64_t min, int64_t max);
+
 #endif
+
   virtual bool is_pread_capable();
   virtual bool set_pin_in_cache(time_t time_pin);
   virtual time_t get_pin_in_cache();
@@ -392,6 +420,9 @@ struct CacheVC : public CacheVConnection {
   // before being used by the CacheVC
   CacheKey key, first_key, earliest_key, update_key;
   Dir dir, earliest_dir, overwrite_dir, first_dir;
+  /// Thread to use to wake up this VC. Set when the VC puts itself on a wait list.
+  /// The waker should schedule @c EVENT_IMMEDIATE on this thread to wake up this VC.
+  EThread* wake_up_thread;
   // end Region A
 
   // Start Region B
@@ -411,11 +442,14 @@ struct CacheVC : public CacheVConnection {
 
   OpenDirEntry *od;
   AIOCallbackInternal io;
-  int alternate_index; // preferred position in vector
-  LINK(CacheVC, opendir_link);
+  int alternate_index;          // preferred position in vector
+  LINK(CacheVC, OpenDir_Link); ///< Reader/writer link per alternate in @c OpenDir.
+  LINK(CacheVC, Active_Link); ///< Active I/O pending list in @c OpenDir.
 #ifdef CACHE_STAT_PAGES
   LINK(CacheVC, stat_link);
 #endif
+  CacheRange resp_range;          ///< Tracking information for range data for response.
+  //  CacheRange uncached_range;      ///< The ranges in the request that are not in cache.
   // end Region B
 
   // Start Region C
@@ -457,6 +491,12 @@ struct CacheVC : public CacheVConnection {
   uint64_t total_len;    // total length written and available to write
   uint64_t doc_len;      // total_length (of the selected alternate for HTTP)
   uint64_t update_len;
+  HTTPRangeSpec::Range write_range; ///< Object based range for incoming partial content.
+  /// The offset in the content of the first byte beyond the end of the current fragment.
+  /// @internal This seems very weird but I couldn't figure out how to keep the more sensible
+  /// lower bound correctly updated.
+  /// The lower bound can can computed by subtracting doc->len from this value.
+  uint64_t frag_upper_bound;
   int fragment;
   int scan_msec_delay;
   CacheVC *write_vc;
@@ -465,6 +505,7 @@ struct CacheVC : public CacheVConnection {
   int header_to_write_len;
   void *header_to_write;
   short writer_lock_retry;
+
 #if TS_USE_INTERIM_CACHE == 1
   InterimCacheVol *interim_vol;
   MigrateToInterimCache *mts;
@@ -542,7 +583,10 @@ extern CacheSync *cacheDirSync;
 // Function Prototypes
 #ifdef HTTP_CACHE
 int cache_write(CacheVC *, CacheHTTPInfoVector *);
-int get_alternate_index(CacheHTTPInfoVector *cache_vector, CacheKey key);
+/// Get the index for the alternate indentified by @a key in @a cache_vector.
+/// @a idx is a hint - that index is checked first and if not there the vector is scanned.
+/// This makes repeated access faster if the vector is not being updated.
+int get_alternate_index(CacheHTTPInfoVector *cache_vector, CacheKey key, int idx = -1);
 #endif
 CacheVC *new_DocEvacuator(int nbytes, Vol *d);
 
@@ -629,7 +673,8 @@ free_CacheVC(CacheVC *cont)
   cont->alternate_index = CACHE_ALT_INDEX_DEFAULT;
   if (cont->scan_vol_map)
     ats_free(cont->scan_vol_map);
-  memset((char *)&cont->vio, 0, cont->size_to_init);
+  cont->resp_range.clear();
+  memset((char *) &cont->vio, 0, cont->size_to_init);
 #ifdef CACHE_STAT_PAGES
   ink_assert(!cont->stat_link.next && !cont->stat_link.prev);
 #endif
@@ -783,6 +828,7 @@ CacheVC::do_write_lock_call()
   return handleWriteLock(EVENT_CALL, 0);
 }
 
+# if 0
 TS_INLINE bool
 CacheVC::writer_done()
 {
@@ -800,6 +846,7 @@ CacheVC::writer_done()
     return true;
   return false;
 }
+# endif
 
 TS_INLINE int
 Vol::close_write(CacheVC *cont)
@@ -937,6 +984,18 @@ rand_CacheKey(CacheKey *next_key, ProxyMutex *mutex)
   next_key->b[1] = mutex->thread_holding->generator.random();
 }
 
+# if 1
+void TS_INLINE
+next_CacheKey(CacheKey *next_key, CacheKey *key)
+{
+  next_key->next(*key);
+}
+void TS_INLINE
+prev_CacheKey(CacheKey *prev_key, CacheKey *key)
+{
+  prev_key->prev(*key);
+}
+# else
 extern uint8_t CacheKey_next_table[];
 void TS_INLINE
 next_CacheKey(CacheKey *next_key, CacheKey *key)
@@ -957,6 +1016,7 @@ prev_CacheKey(CacheKey *prev_key, CacheKey *key)
     b[i] = 256 + CacheKey_prev_table[k[i]] - k[i - 1];
   b[0] = CacheKey_prev_table[k[0]];
 }
+# endif
 
 TS_INLINE unsigned int
 next_rand(unsigned int *p)
@@ -1025,10 +1085,14 @@ struct Cache {
 
   Action *lookup(Continuation *cont, CacheKey *key, CacheFragType type, char const *hostname, int host_len);
   inkcoreapi Action *open_read(Continuation *cont, CacheKey *key, CacheFragType type, char *hostname, int len);
-  inkcoreapi Action *open_write(Continuation *cont, CacheKey *key, CacheFragType frag_type, int options = 0,
-                                time_t pin_in_cache = (time_t)0, char *hostname = 0, int host_len = 0);
-  inkcoreapi Action *remove(Continuation *cont, CacheKey *key, CacheFragType type = CACHE_FRAG_TYPE_HTTP, bool user_agents = true,
-                            bool link = false, char *hostname = 0, int host_len = 0);
+  inkcoreapi Action* open_read(Continuation* cont, CacheVConnection* writer, HTTPHdr* client_request_hdr);
+  inkcoreapi Action *open_write(Continuation *cont, CacheKey *key,
+                                CacheFragType frag_type, int options = 0,
+                                time_t pin_in_cache = (time_t) 0, char *hostname = 0, int host_len = 0);
+  inkcoreapi Action *remove(Continuation *cont, CacheKey *key,
+                            CacheFragType type = CACHE_FRAG_TYPE_HTTP,
+                            bool user_agents = true, bool link = false,
+                            char *hostname = 0, int host_len = 0);
   Action *scan(Continuation *cont, char *hostname = 0, int host_len = 0, int KB_per_second = 2500);
 
 #ifdef HTTP_CACHE
@@ -1040,6 +1104,7 @@ struct Cache {
                      CacheKey *key1 = NULL, CacheFragType type = CACHE_FRAG_TYPE_HTTP, char *hostname = 0, int host_len = 0);
   Action *open_write(Continuation *cont, URL *url, CacheHTTPHdr *request, CacheHTTPInfo *old_info, time_t pin_in_cache = (time_t)0,
                      CacheFragType type = CACHE_FRAG_TYPE_HTTP);
+
   static void generate_key(INK_MD5 *md5, URL *url);
 #endif
 
@@ -1135,8 +1200,29 @@ CacheProcessor::open_read(Continuation *cont, CacheKey *key, bool cluster_cache_
 {
 #ifdef CLUSTER_CACHE
   if (cache_clustering_enabled > 0 && !cluster_cache_local) {
-    return open_read_internal(CACHE_OPEN_READ, cont, (MIOBuffer *)0, (CacheURL *)0, (CacheHTTPHdr *)0, (CacheLookupHttpConfig *)0,
-                              key, 0, frag_type, hostname, host_len);
+    return open_read_internal(CACHE_OPEN_READ, cont, (MIOBuffer *) 0,
+                              (CacheURL *) 0, (CacheHTTPHdr *) 0,
+                              (CacheLookupHttpConfig *) 0, key, 0, frag_type, hostname, host_len);
+  }
+#endif
+  return caches[frag_type]->open_read(cont, key, frag_type, hostname, host_len);
+}
+
+TS_INLINE inkcoreapi Action *
+CacheProcessor::open_read(Continuation *cont, CacheVConnection* writer, HTTPHdr* client_request_hdr)
+{
+  return caches[CACHE_FRAG_TYPE_HTTP]->open_read(cont, writer, client_request_hdr);
+}
+
+TS_INLINE Action *
+CacheProcessor::open_read_buffer(Continuation *cont, MIOBuffer *buf ATS_UNUSED, CacheKey *key, CacheFragType frag_type,
+                                 char *hostname, int host_len)
+{
+#ifdef CLUSTER_CACHE
+  if (cache_clustering_enabled > 0) {
+    return open_read_internal(CACHE_OPEN_READ_BUFFER, cont, buf,
+                              (CacheURL *) 0, (CacheHTTPHdr *) 0,
+                              (CacheLookupHttpConfig *) 0, key, 0, frag_type, hostname, host_len);
   }
 #endif
   return caches[frag_type]->open_read(cont, key, frag_type, hostname, host_len);
@@ -1278,6 +1364,7 @@ local_cache()
   return theCache;
 }
 
-LINK_DEFINITION(CacheVC, opendir_link)
+LINK_DEFINITION(CacheVC, OpenDir_Link)
+LINK_DEFINITION(CacheVC, Active_Link)
 
 #endif /* _P_CACHE_INTERNAL_H__ */

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cluster/ClusterCache.cc
----------------------------------------------------------------------
diff --git a/iocore/cluster/ClusterCache.cc b/iocore/cluster/ClusterCache.cc
index 198d8e1..b1f8b9e 100644
--- a/iocore/cluster/ClusterCache.cc
+++ b/iocore/cluster/ClusterCache.cc
@@ -2625,7 +2625,7 @@ CacheContinuation::getObjectSize(VConnection *vc, int opcode, CacheHTTPInfo *ret
     } else {
       new_ci.object_size_set(object_size);
     }
-    new_ci.m_alt->m_writeable = 1;
+    new_ci.m_alt->m_flag.writeable_p = true;
     ret_ci->copy_shallow(&new_ci);
   }
   ink_release_assert(object_size);

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cluster/ClusterVConnection.cc
----------------------------------------------------------------------
diff --git a/iocore/cluster/ClusterVConnection.cc b/iocore/cluster/ClusterVConnection.cc
index 7a3185e..d831b39 100644
--- a/iocore/cluster/ClusterVConnection.cc
+++ b/iocore/cluster/ClusterVConnection.cc
@@ -631,4 +631,5 @@ ClusterVConnection::get_disk_io_priority()
   return disk_io_priority;
 }
 
+
 // End of ClusterVConnection.cc

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cluster/P_Cluster.h
----------------------------------------------------------------------
diff --git a/iocore/cluster/P_Cluster.h b/iocore/cluster/P_Cluster.h
index 0376f63..22fdaf9 100644
--- a/iocore/cluster/P_Cluster.h
+++ b/iocore/cluster/P_Cluster.h
@@ -46,7 +46,7 @@
 #include "P_ClusterLoadMonitor.h"
 #include "P_TimeTrace.h"
 
-
+# if 0 // defined in InkErrno.h
 #define ECLUSTER_NO_VC (CLUSTER_ERRNO + 0)
 #define ECLUSTER_NO_MACHINE (CLUSTER_ERRNO + 1)
 #define ECLUSTER_OP_TIMEOUT (CLUSTER_ERRNO + 2)
@@ -54,6 +54,7 @@
 #define ECLUSTER_ORB_EIO (CLUSTER_ERRNO + 4)
 #define ECLUSTER_CHANNEL_INUSE (CLUSTER_ERRNO + 5)
 #define ECLUSTER_NOMORE_CHANNELS (CLUSTER_ERRNO + 6)
+#endif
 
 int init_clusterprocessor(void);
 enum {

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cluster/P_ClusterCache.h
----------------------------------------------------------------------
diff --git a/iocore/cluster/P_ClusterCache.h b/iocore/cluster/P_ClusterCache.h
index 37f49e1..b01d950 100644
--- a/iocore/cluster/P_ClusterCache.h
+++ b/iocore/cluster/P_ClusterCache.h
@@ -357,6 +357,17 @@ struct ClusterVConnectionBase : public CacheVConnection {
   virtual void do_io_close(int lerrno = -1);
   virtual VIO *do_io_pread(Continuation *, int64_t, MIOBuffer *, int64_t);
 
+  // TODO - fix these to work for cluster.
+  // I think the best approach is to foist the work off to the source peer and have it do
+  // the range formatting which we then just pass through. For now, this just prevents
+  // link problems so I can get the base case to work.
+  virtual char const* get_http_range_boundary_string(int*) const { return NULL; }
+  virtual int64_t get_effective_content_size() { return this->get_object_size(); }
+  virtual void set_full_content_length(int64_t) { } // only used when writing to cache
+  virtual HTTPRangeSpec& get_http_range_spec() { return resp_range.getRangeSpec(); }
+  virtual bool is_http_partial_content() { return false; }
+  virtual bool get_uncached(HTTPRangeSpec& r) { r.clear(); return false; }
+
   // Set the timeouts associated with this connection.
   // active_timeout is for the total elasped time of the connection.
   // inactivity_timeout is the elapsed time *while an operation was
@@ -388,6 +399,7 @@ struct ClusterVConnectionBase : public CacheVConnection {
   ink_hrtime active_timeout_in;
   Event *inactivity_timeout;
   Event *active_timeout;
+  CacheRange resp_range;
 
   virtual void reenable(VIO *);
   virtual void reenable_re(VIO *);

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/lib/ts/CryptoHash.h
----------------------------------------------------------------------
diff --git a/lib/ts/CryptoHash.h b/lib/ts/CryptoHash.h
index 28b6296..316d9f1 100644
--- a/lib/ts/CryptoHash.h
+++ b/lib/ts/CryptoHash.h
@@ -26,8 +26,15 @@
 /// Apache Traffic Server commons.
 namespace ats
 {
+/// Permutation table for computing next hash.
+extern uint8_t const CRYPTO_HASH_NEXT_TABLE[256];
+/// Permutation table for computing previous hash.
+extern uint8_t const CRYPTO_HASH_PREV_TABLE[256];
+
 /// Crypto hash output.
 union CryptoHash {
+  typedef CryptoHash self; ///< Self reference type.
+
   uint64_t b[2]; // Legacy placeholder
   uint64_t u64[2];
   uint32_t u32[4];
@@ -51,7 +58,7 @@ union CryptoHash {
   /// Equality - bitwise identical.
   bool operator==(CryptoHash const &that) const { return u64[0] == that.u64[0] && u64[1] == that.u64[1]; }
 
-  /// Equality - bitwise identical.
+  /// Inequality - bitwise identical.
   bool operator!=(CryptoHash const &that) const { return !(*this == that); }
 
   /// Reduce to 64 bit value.
@@ -85,6 +92,44 @@ union CryptoHash {
   {
     return ink_code_to_hex_str(buffer, u8);
   }
+
+  /// Check for the zero key.
+  bool
+  is_zero() const
+  {
+    return 0 == (u64[0] | u64[1]);
+  }
+
+  /// Update the key to the computationally chained next key from @a that.
+  void
+  next(self const &that)
+  {
+    u8[0] = CRYPTO_HASH_NEXT_TABLE[that.u8[0]];
+    for (unsigned int i = 1; i < sizeof(u8); ++i)
+      u8[i] = CRYPTO_HASH_NEXT_TABLE[(u8[i - 1] + that.u8[i]) & 0xFF];
+  }
+  /// Update the key to the computationally chained next key.
+  void
+  next()
+  {
+    this->next(*this);
+  }
+
+  /// Update the key to the computationally chained previous key from @a that.
+  void
+  prev(self const &that)
+  {
+    for (unsigned int i = sizeof(u8) - 1; i > 0; --i)
+      u8[i] = 256 + CRYPTO_HASH_PREV_TABLE[that.u8[i]] - that.u8[i - 1];
+    u8[0] = CRYPTO_HASH_PREV_TABLE[that.u8[0]];
+  }
+
+  /// Update the key to the computationally chained previous key.
+  void
+  prev()
+  {
+    this->prev(*this);
+  }
 };
 
 extern CryptoHash const CRYPTO_HASH_ZERO;

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/lib/ts/InkErrno.h
----------------------------------------------------------------------
diff --git a/lib/ts/InkErrno.h b/lib/ts/InkErrno.h
index f3f796a..6109c32 100644
--- a/lib/ts/InkErrno.h
+++ b/lib/ts/InkErrno.h
@@ -48,24 +48,26 @@
 #define ESOCK_NO_SOCK_SERVER_CONN (SOCK_ERRNO + 2)
 
 // Error codes for CLUSTER_EVENT_OPEN_FAILED
-#define ECLUSTER_NO_VC (CLUSTER_ERRNO + 0)
-#define ECLUSTER_NO_MACHINE (CLUSTER_ERRNO + 1)
-#define ECLUSTER_OP_TIMEOUT (CLUSTER_ERRNO + 2)
-#define ECLUSTER_ORB_DATA_READ (CLUSTER_ERRNO + 3)
-#define ECLUSTER_ORB_EIO (CLUSTER_ERRNO + 4)
-#define ECLUSTER_CHANNEL_INUSE (CLUSTER_ERRNO + 5)
-#define ECLUSTER_NOMORE_CHANNELS (CLUSTER_ERRNO + 6)
-
-#define ECACHE_NO_DOC (CACHE_ERRNO + 0)
-#define ECACHE_DOC_BUSY (CACHE_ERRNO + 1)
-#define ECACHE_DIR_BAD (CACHE_ERRNO + 2)
-#define ECACHE_BAD_META_DATA (CACHE_ERRNO + 3)
-#define ECACHE_READ_FAIL (CACHE_ERRNO + 4)
-#define ECACHE_WRITE_FAIL (CACHE_ERRNO + 5)
-#define ECACHE_MAX_ALT_EXCEEDED (CACHE_ERRNO + 6)
-#define ECACHE_NOT_READY (CACHE_ERRNO + 7)
-#define ECACHE_ALT_MISS (CACHE_ERRNO + 8)
-#define ECACHE_BAD_READ_REQUEST (CACHE_ERRNO + 9)
+#define ECLUSTER_NO_VC                    (CLUSTER_ERRNO+0)
+#define ECLUSTER_NO_MACHINE               (CLUSTER_ERRNO+1)
+#define ECLUSTER_OP_TIMEOUT               (CLUSTER_ERRNO+2)
+#define ECLUSTER_ORB_DATA_READ            (CLUSTER_ERRNO+3)
+#define ECLUSTER_ORB_EIO            	  (CLUSTER_ERRNO+4)
+#define ECLUSTER_CHANNEL_INUSE       	  (CLUSTER_ERRNO+5)
+#define ECLUSTER_NOMORE_CHANNELS       	  (CLUSTER_ERRNO+6)
+
+#define ECACHE_NO_DOC                     (CACHE_ERRNO+0)
+#define ECACHE_DOC_BUSY                   (CACHE_ERRNO+1)
+#define ECACHE_DIR_BAD                    (CACHE_ERRNO+2)
+#define ECACHE_BAD_META_DATA              (CACHE_ERRNO+3)
+#define ECACHE_READ_FAIL                  (CACHE_ERRNO+4)
+#define ECACHE_WRITE_FAIL                 (CACHE_ERRNO+5)
+#define ECACHE_MAX_ALT_EXCEEDED           (CACHE_ERRNO+6)
+#define ECACHE_NOT_READY                  (CACHE_ERRNO+7)
+#define ECACHE_ALT_MISS                   (CACHE_ERRNO+8)
+#define ECACHE_BAD_READ_REQUEST           (CACHE_ERRNO+9)
+#define ECACHE_INVALID_RANGE              (CACHE_ERRNO+10)
+#define ECACHE_UNSATISFIABLE_RANGE        (CACHE_ERRNO+11)
 
 #define EHTTP_ERROR (HTTP_ERRNO + 0)
 

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/lib/ts/ParseRules.cc
----------------------------------------------------------------------
diff --git a/lib/ts/ParseRules.cc b/lib/ts/ParseRules.cc
index 5864e70..fe9f0c8 100644
--- a/lib/ts/ParseRules.cc
+++ b/lib/ts/ParseRules.cc
@@ -306,3 +306,20 @@ ink_atoi64(const char *str, int len)
   }
   return num;
 }
+
+uint64_t ats_strto64(char const* s, size_t len, size_t* used)
+{
+  uint64_t zret = 0;
+  char const* spot = s;
+
+  if (s && len) {
+    for (char const* limit = spot + len ; spot < limit && ParseRules::is_digit(*spot) ; ++spot) {
+      zret *= 10;
+      zret += *spot - '0';
+    }
+  }
+  
+  if (used) *used = spot - s;
+  return zret;
+    
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/lib/ts/ParseRules.h
----------------------------------------------------------------------
diff --git a/lib/ts/ParseRules.h b/lib/ts/ParseRules.h
index ce5c676..b910415 100644
--- a/lib/ts/ParseRules.h
+++ b/lib/ts/ParseRules.h
@@ -842,4 +842,24 @@ ink_atoui(const char *str)
     return static_cast<int>(val);
 }
 
+/** Convert a span of characters to an unsigned 64 bit value.
+
+    Parsing starts at @a s and continues for at most @a len characters.
+    Parsing stops when the first non-digit is encountered. Leading whitespace is not permitted.
+    @a *used is set to the number of characters parsed if @a used is not @c NULL.
+    If @a *used is set to 0 and 0 is returned, then no characters were parsed.
+
+    Key features
+
+    - No termination required.
+    - Number of parsed characters returned.
+    - Unsigned 64 bit return.
+    - Clip to UINT64_MAX;
+
+    @return The binary equivalent of @a s.
+
+    @internal All of these conversions and none work as I need. Sigh.
+*/
+uint64_t ats_strto64(char const* s, size_t len, size_t* used);
+
 #endif /* #if !defined (_ParseRules_h_) */

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/lib/ts/TsBuffer.h
----------------------------------------------------------------------
diff --git a/lib/ts/TsBuffer.h b/lib/ts/TsBuffer.h
index 4da12ff..4f4b2ee 100644
--- a/lib/ts/TsBuffer.h
+++ b/lib/ts/TsBuffer.h
@@ -219,6 +219,7 @@ struct ConstBuffer {
   self &set(char const *start, ///< First valid character.
             char const *end    ///< First invalid character.
             );
+
   /// Reset to empty.
   self &reset();
 
@@ -238,7 +239,7 @@ struct ConstBuffer {
       This is convenient when tokenizing and @a p points at the token
       separator.
 
-      @note If @a *p is not in the buffer then @a this is not changed
+      @note If @a *p is in the buffer then @a this is not changed
       and an empty buffer is returned. This means the caller can
       simply pass the result of @c find and check for an empty
       buffer returned to detect no more separators.
@@ -263,17 +264,20 @@ struct ConstBuffer {
       @return A buffer containing data up to but not including @a p.
   */
   self splitOn(char c);
+
   /** Get a trailing segment of the buffer.
 
       @return A buffer that contains all data after @a p.
   */
   self after(char const *p) const;
+
   /** Get a trailing segment of the buffer.
 
       @return A buffer that contains all data after the first
       occurrence of @a c.
   */
   self after(char c) const;
+
   /** Remove trailing segment.
 
       Data at @a p and beyond is removed from the buffer.
@@ -282,6 +286,48 @@ struct ConstBuffer {
       @return @a this.
   */
   self &clip(char const *p);
+
+  /** Remove initial instances of @a c.
+
+      @return @c true if not all characters were skipped, @c false if all characters matched @a c.
+      @see trim
+  */
+  bool skip(char c);
+
+  /** Remove leading characters that satisfy a @a predicate.
+      @return @c true if not all characters were skipped, @c false if all characters matched the @a predicate.
+
+      @internal We template this because the @c ParseRules predicates (which are the usual suspects)
+      return an integral type that is not @c bool.
+  */
+  template <typename BOOL_EQUIV ///< Type that can be automatically converted to bool
+            >
+  bool skip(BOOL_EQUIV (*predicate)(char));
+
+  /** Remove an initial instance the string @a str.
+
+      If the initial characters of the buffer match @a str (ignoring case) then the buffer is advanced past @a str.
+
+      @return @c true if matched and skipped, @c false otherwise.
+  */
+  bool skipNoCase(self const &str);
+
+  /** Remove trailing instances of @a c.
+
+      @return @c true if not all characters were trimmed, @c false if all characters matched @a c.
+      @see @c skip
+  */
+  bool trim(char c);
+
+  /** Remove trailing characters that satisfy a @a predicate.
+      @return @c true if not all characters were trimmed, @c false if all characters matched the @a predicate.
+
+      @internal We template this because the @c ParseRules predicates (which are the usual suspects)
+      return an integral type that is not @c bool.
+  */
+  template <typename BOOL_EQUIV ///< Type that can be automatically converted to bool
+            >
+  bool trim(BOOL_EQUIV (*predicate)(char));
 };
 
 // ----------------------------------------------------------
@@ -500,6 +546,55 @@ ConstBuffer::clip(char const *p)
   return *this;
 }
 
+template <typename BOOL_EQUIV>
+inline bool
+ConstBuffer::skip(BOOL_EQUIV (*predicate)(char))
+{
+  while (*this && predicate(**this))
+    ++*this;
+  return *this;
+}
+inline bool
+ConstBuffer::skip(char c)
+{
+  while (*this && c == **this)
+    ++*this;
+  return *this;
+}
+
+template <typename BOOL_EQUIV>
+inline bool
+ConstBuffer::trim(BOOL_EQUIV (*predicate)(char))
+{
+  if (NULL != _ptr) {
+    while (_size && predicate(_ptr[_size - 1]))
+      --_size;
+  }
+  return *this;
+}
+
+inline bool
+ConstBuffer::skipNoCase(self const &str)
+{
+  bool zret = true;
+  if (str._size <= _size && 0 == strncasecmp(_ptr, str._ptr, str._size))
+    *this += str._size;
+  else
+    zret = false;
+  return zret;
+}
+
+inline bool
+ConstBuffer::trim(char c)
+{
+  if (NULL != _ptr) {
+    while (_size && c == _ptr[_size - 1])
+      --_size;
+  }
+
+  return *this;
+}
+
 } // end namespace
 
 typedef ts::Buffer TsBuffer;

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/lib/ts/ink_code.cc
----------------------------------------------------------------------
diff --git a/lib/ts/ink_code.cc b/lib/ts/ink_code.cc
index e099b9c..d0a6144 100644
--- a/lib/ts/ink_code.cc
+++ b/lib/ts/ink_code.cc
@@ -154,3 +154,77 @@ ink_code_to_hex_str(char *dest33, uint8_t const *hash)
   *d = '\0';
   return (dest33);
 }
+
+namespace ats {
+
+  uint8_t const CRYPTO_HASH_NEXT_TABLE[256] = {
+    21, 53, 167, 51, 255, 126, 241, 151,
+    115, 66, 155, 174, 226, 215, 80, 188,
+    12, 95, 8, 24, 162, 201, 46, 104,
+    79, 172, 39, 68, 56, 144, 142, 217,
+    101, 62, 14, 108, 120, 90, 61, 47,
+    132, 199, 110, 166, 83, 125, 57, 65,
+    19, 130, 148, 116, 228, 189, 170, 1,
+    71, 0, 252, 184, 168, 177, 88, 229,
+    242, 237, 183, 55, 13, 212, 240, 81,
+    211, 74, 195, 205, 147, 93, 30, 87,
+    86, 63, 135, 102, 233, 106, 118, 163,
+    107, 10, 243, 136, 160, 119, 43, 161,
+    206, 141, 203, 78, 175, 36, 37, 140,
+    224, 197, 185, 196, 248, 84, 122, 73,
+    152, 157, 18, 225, 219, 145, 45, 2,
+    171, 249, 173, 32, 143, 137, 69, 41,
+    35, 89, 33, 98, 179, 214, 114, 231,
+    251, 123, 180, 194, 29, 3, 178, 31,
+    192, 164, 15, 234, 26, 230, 91, 156,
+    5, 16, 23, 244, 58, 50, 4, 67,
+    134, 165, 60, 235, 250, 7, 138, 216,
+    49, 139, 191, 154, 11, 52, 239, 59,
+    111, 245, 9, 64, 25, 129, 247, 232,
+    190, 246, 109, 22, 112, 210, 221, 181,
+    92, 169, 48, 100, 193, 77, 103, 133,
+    70, 220, 207, 223, 176, 204, 76, 186,
+    200, 208, 158, 182, 227, 222, 131, 38,
+    187, 238, 6, 34, 253, 128, 146, 44,
+    94, 127, 105, 153, 113, 20, 27, 124,
+    159, 17, 72, 218, 96, 149, 213, 42,
+    28, 254, 202, 40, 117, 82, 97, 209,
+    54, 236, 121, 75, 85, 150, 99, 198,
+  };
+
+  uint8_t const CRYPTO_HASH_PREV_TABLE[256] = {
+    57, 55, 119, 141, 158, 152, 218, 165,
+    18, 178, 89, 172, 16, 68, 34, 146,
+    153, 233, 114, 48, 229, 0, 187, 154,
+    19, 180, 148, 230, 240, 140, 78, 143,
+    123, 130, 219, 128, 101, 102, 215, 26,
+    243, 127, 239, 94, 223, 118, 22, 39,
+    194, 168, 157, 3, 173, 1, 248, 67,
+    28, 46, 156, 175, 162, 38, 33, 81,
+    179, 47, 9, 159, 27, 126, 200, 56,
+    234, 111, 73, 251, 206, 197, 99, 24,
+    14, 71, 245, 44, 109, 252, 80, 79,
+    62, 129, 37, 150, 192, 77, 224, 17,
+    236, 246, 131, 254, 195, 32, 83, 198,
+    23, 226, 85, 88, 35, 186, 42, 176,
+    188, 228, 134, 8, 51, 244, 86, 93,
+    36, 250, 110, 137, 231, 45, 5, 225,
+    221, 181, 49, 214, 40, 199, 160, 82,
+    91, 125, 166, 169, 103, 97, 30, 124,
+    29, 117, 222, 76, 50, 237, 253, 7,
+    112, 227, 171, 10, 151, 113, 210, 232,
+    92, 95, 20, 87, 145, 161, 43, 2,
+    60, 193, 54, 120, 25, 122, 11, 100,
+    204, 61, 142, 132, 138, 191, 211, 66,
+    59, 106, 207, 216, 15, 53, 184, 170,
+    144, 196, 139, 74, 107, 105, 255, 41,
+    208, 21, 242, 98, 205, 75, 96, 202,
+    209, 247, 189, 72, 69, 238, 133, 13,
+    167, 31, 235, 116, 201, 190, 213, 203,
+    104, 115, 12, 212, 52, 63, 149, 135,
+    183, 84, 147, 163, 249, 65, 217, 174,
+    70, 6, 64, 90, 155, 177, 185, 182,
+    108, 121, 164, 136, 58, 220, 241, 4,
+  };
+
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/proxy/hdrs/HTTP.cc
----------------------------------------------------------------------
diff --git a/proxy/hdrs/HTTP.cc b/proxy/hdrs/HTTP.cc
index c5953b6..43fcdc7 100644
--- a/proxy/hdrs/HTTP.cc
+++ b/proxy/hdrs/HTTP.cc
@@ -29,6 +29,7 @@
 #include "HTTP.h"
 #include "HdrToken.h"
 #include "Diags.h"
+#include "I_IOBuffer.h"
 
 /***********************************************************************
  *                                                                     *
@@ -1770,82 +1771,66 @@ ClassAllocator<HTTPCacheAlt> httpCacheAltAllocator("httpCacheAltAllocator");
 
 /*-------------------------------------------------------------------------
   -------------------------------------------------------------------------*/
-HTTPCacheAlt::HTTPCacheAlt()
-  : m_magic(CACHE_ALT_MAGIC_ALIVE), m_writeable(1), m_unmarshal_len(-1), m_id(-1), m_rid(-1), m_request_hdr(), m_response_hdr(),
-    m_request_sent_time(0), m_response_received_time(0), m_frag_offset_count(0), m_frag_offsets(0), m_ext_buffer(NULL)
+HTTPCacheAlt::HTTPCacheAlt():
+  m_magic(CACHE_ALT_MAGIC_ALIVE)
+  , m_unmarshal_len(-1)
+  , m_id(-1), m_rid(-1)
+  , m_frag_count(0)
+  , m_request_hdr(), m_response_hdr()
+  , m_request_sent_time(0), m_response_received_time(0)
+  , m_fragments(0)
+  , m_ext_buffer(NULL)
 {
-  m_object_key[0] = 0;
-  m_object_key[1] = 0;
-  m_object_key[2] = 0;
-  m_object_key[3] = 0;
-  m_object_size[0] = 0;
-  m_object_size[1] = 0;
+  m_flags = 0; // set all flags to false.
+  m_flag.writeable_p = true;
 }
 
 void
 HTTPCacheAlt::destroy()
 {
   ink_assert(m_magic == CACHE_ALT_MAGIC_ALIVE);
-  ink_assert(m_writeable);
+  ink_assert(m_flag.writeable_p);
   m_magic = CACHE_ALT_MAGIC_DEAD;
-  m_writeable = 0;
+  m_flag.writeable_p = 0;
   m_request_hdr.destroy();
   m_response_hdr.destroy();
-  m_frag_offset_count = 0;
-  if (m_frag_offsets && m_frag_offsets != m_integral_frag_offsets) {
-    ats_free(m_frag_offsets);
-    m_frag_offsets = 0;
-  }
+  m_frag_count = 0;
+  if (m_flag.table_allocated_p) ats_free(m_fragments);
+  m_fragments = 0;
   httpCacheAltAllocator.free(this);
 }
 
 void
-HTTPCacheAlt::copy(HTTPCacheAlt *to_copy)
+HTTPCacheAlt::copy(HTTPCacheAlt *that)
 {
-  m_magic = to_copy->m_magic;
-  // m_writeable =      to_copy->m_writeable;
-  m_unmarshal_len = to_copy->m_unmarshal_len;
-  m_id = to_copy->m_id;
-  m_rid = to_copy->m_rid;
-  m_object_key[0] = to_copy->m_object_key[0];
-  m_object_key[1] = to_copy->m_object_key[1];
-  m_object_key[2] = to_copy->m_object_key[2];
-  m_object_key[3] = to_copy->m_object_key[3];
-  m_object_size[0] = to_copy->m_object_size[0];
-  m_object_size[1] = to_copy->m_object_size[1];
 
-  if (to_copy->m_request_hdr.valid()) {
-    m_request_hdr.copy(&to_copy->m_request_hdr);
+  m_magic = that->m_magic;
+  m_unmarshal_len = that->m_unmarshal_len;
+  m_id = that->m_id;
+  m_rid = that->m_rid;
+  m_earliest = that->m_earliest;
+
+  if (that->m_request_hdr.valid()) {
+    m_request_hdr.copy(&that->m_request_hdr);
   }
 
-  if (to_copy->m_response_hdr.valid()) {
-    m_response_hdr.copy(&to_copy->m_response_hdr);
+  if (that->m_response_hdr.valid()) {
+    m_response_hdr.copy(&that->m_response_hdr);
   }
 
-  m_request_sent_time = to_copy->m_request_sent_time;
-  m_response_received_time = to_copy->m_response_received_time;
-  this->copy_frag_offsets_from(to_copy);
-}
+  m_request_sent_time = that->m_request_sent_time;
+  m_response_received_time = that->m_response_received_time;
 
-void
-HTTPCacheAlt::copy_frag_offsets_from(HTTPCacheAlt *src)
-{
-  m_frag_offset_count = src->m_frag_offset_count;
-  if (m_frag_offset_count > 0) {
-    if (m_frag_offset_count > N_INTEGRAL_FRAG_OFFSETS) {
-      /* Mixed feelings about this - technically we don't need it to be a
-         power of two when copied because currently that means it is frozen.
-         But that could change later and it would be a nasty bug to find.
-         So we'll do it for now. The relative overhead is tiny.
-      */
-      int bcount = HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS * 2;
-      while (bcount < m_frag_offset_count)
-        bcount *= 2;
-      m_frag_offsets = static_cast<FragOffset *>(ats_malloc(sizeof(FragOffset) * bcount));
-    } else {
-      m_frag_offsets = m_integral_frag_offsets;
-    }
-    memcpy(m_frag_offsets, src->m_frag_offsets, sizeof(FragOffset) * m_frag_offset_count);
+  m_frag_count = that->m_frag_count;
+
+  ats_free(m_fragments);
+  if (that->m_fragments) {
+    size_t size = FragmentDescriptorTable::calc_size(that->m_fragments->m_n);
+    m_fragments = static_cast<FragmentDescriptorTable*>(ats_malloc(size));
+    memcpy(m_fragments, that->m_fragments, size);
+    m_flag.table_allocated_p = true;
+  } else {
+    m_fragments = 0;
   }
 }
 
@@ -1860,7 +1845,8 @@ HTTPInfo::create()
 void
 HTTPInfo::copy(HTTPInfo *hi)
 {
-  if (m_alt && m_alt->m_writeable) {
+
+  if (m_alt && m_alt->m_flag.writeable_p) {
     destroy();
   }
 
@@ -1868,14 +1854,6 @@ HTTPInfo::copy(HTTPInfo *hi)
   m_alt->copy(hi->m_alt);
 }
 
-void
-HTTPInfo::copy_frag_offsets_from(HTTPInfo *src)
-{
-  if (m_alt && src->m_alt)
-    m_alt->copy_frag_offsets_from(src->m_alt);
-}
-
-
 int
 HTTPInfo::marshal_length()
 {
@@ -1889,10 +1867,8 @@ HTTPInfo::marshal_length()
     len += m_alt->m_response_hdr.m_heap->marshal_length();
   }
 
-  if (m_alt->m_frag_offset_count > HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS) {
-    len -= sizeof(m_alt->m_integral_frag_offsets);
-    len += sizeof(FragOffset) * m_alt->m_frag_offset_count;
-  }
+  if (m_alt->m_fragments)
+    len += FragmentDescriptorTable::calc_size(m_alt->m_fragments->m_n);
 
   return len;
 }
@@ -1905,42 +1881,30 @@ HTTPInfo::marshal(char *buf, int len)
   HTTPCacheAlt *marshal_alt = (HTTPCacheAlt *)buf;
   // non-zero only if the offsets are external. Otherwise they get
   // marshalled along with the alt struct.
-  int frag_len = (0 == m_alt->m_frag_offset_count || m_alt->m_frag_offsets == m_alt->m_integral_frag_offsets) ?
-                   0 :
-                   sizeof(HTTPCacheAlt::FragOffset) * m_alt->m_frag_offset_count;
+  size_t frag_len = m_alt->m_fragments ? FragmentDescriptorTable::calc_size(m_alt->m_fragments->m_n) : 0;
 
   ink_assert(m_alt->m_magic == CACHE_ALT_MAGIC_ALIVE);
 
   // Make sure the buffer is aligned
   //    ink_assert(((intptr_t)buf) & 0x3 == 0);
 
-  // If we have external fragment offsets, copy the initial ones
-  // into the integral data.
-  if (frag_len) {
-    memcpy(m_alt->m_integral_frag_offsets, m_alt->m_frag_offsets, sizeof(m_alt->m_integral_frag_offsets));
-    frag_len -= sizeof(m_alt->m_integral_frag_offsets);
-    // frag_len should never be non-zero at this point, as the offsets
-    // should be external only if too big for the internal table.
-  }
   // Memcpy the whole object so that we can use it
   //   live later.  This involves copying a few
   //   extra bytes now but will save copying any
   //   bytes on the way out of the cache
   memcpy(buf, m_alt, sizeof(HTTPCacheAlt));
   marshal_alt->m_magic = CACHE_ALT_MAGIC_MARSHALED;
-  marshal_alt->m_writeable = 0;
+  marshal_alt->m_flag.writeable_p = 0;
   marshal_alt->m_unmarshal_len = -1;
   marshal_alt->m_ext_buffer = NULL;
   buf += HTTP_ALT_MARSHAL_SIZE;
   used += HTTP_ALT_MARSHAL_SIZE;
 
   if (frag_len > 0) {
-    marshal_alt->m_frag_offsets = static_cast<FragOffset *>(reinterpret_cast<void *>(used));
-    memcpy(buf, m_alt->m_frag_offsets + HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS, frag_len);
+    marshal_alt->m_fragments = static_cast<FragmentDescriptorTable*>(reinterpret_cast<void*>(used));
+    memcpy(buf, m_alt->m_fragments, frag_len);
     buf += frag_len;
     used += frag_len;
-  } else {
-    marshal_alt->m_frag_offsets = 0;
   }
 
   // The m_{request,response}_hdr->m_heap pointers are converted
@@ -1982,7 +1946,6 @@ HTTPInfo::unmarshal(char *buf, int len, RefCountObj *block_ref)
 
   if (alt->m_magic == CACHE_ALT_MAGIC_ALIVE) {
     // Already unmarshaled, must be a ram cache
-    //  it
     ink_assert(alt->m_unmarshal_len > 0);
     ink_assert(alt->m_unmarshal_len <= len);
     return alt->m_unmarshal_len;
@@ -1993,31 +1956,14 @@ HTTPInfo::unmarshal(char *buf, int len, RefCountObj *block_ref)
 
   ink_assert(alt->m_unmarshal_len < 0);
   alt->m_magic = CACHE_ALT_MAGIC_ALIVE;
-  ink_assert(alt->m_writeable == 0);
+  ink_assert(alt->m_flag.writeable_p == 0);
   len -= HTTP_ALT_MARSHAL_SIZE;
 
-  if (alt->m_frag_offset_count > HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS) {
-    // stuff that didn't fit in the integral slots.
-    int extra = sizeof(FragOffset) * alt->m_frag_offset_count - sizeof(alt->m_integral_frag_offsets);
-    char *extra_src = buf + reinterpret_cast<intptr_t>(alt->m_frag_offsets);
-    // Actual buffer size, which must be a power of two.
-    // Well, technically not, because we never modify an unmarshalled fragment
-    // offset table, but it would be a nasty bug should that be done in the
-    // future.
-    int bcount = HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS * 2;
-
-    while (bcount < alt->m_frag_offset_count)
-      bcount *= 2;
-    alt->m_frag_offsets =
-      static_cast<FragOffset *>(ats_malloc(bcount * sizeof(FragOffset))); // WRONG - must round up to next power of 2.
-    memcpy(alt->m_frag_offsets, alt->m_integral_frag_offsets, sizeof(alt->m_integral_frag_offsets));
-    memcpy(alt->m_frag_offsets + HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS, extra_src, extra);
-    len -= extra;
-  } else if (alt->m_frag_offset_count > 0) {
-    alt->m_frag_offsets = alt->m_integral_frag_offsets;
-  } else {
-    alt->m_frag_offsets = 0; // should really already be zero.
+  if (alt->m_fragments) {
+    alt->m_fragments = reinterpret_cast<FragmentDescriptorTable*>(buf + reinterpret_cast<intptr_t>(alt->m_fragments));
+    len -= FragmentDescriptorTable::calc_size(alt->m_fragments->m_n);
   }
+  alt->m_flag.table_allocated_p = false;
 
   HdrHeap *heap = (HdrHeap *)(alt->m_request_hdr.m_heap ? (buf + (intptr_t)alt->m_request_hdr.m_heap) : 0);
   HTTPHdrImpl *hh = NULL;
@@ -2033,6 +1979,7 @@ HTTPInfo::unmarshal(char *buf, int len, RefCountObj *block_ref)
     alt->m_request_hdr.m_http = hh;
     alt->m_request_hdr.m_mime = hh->m_fields_impl;
     alt->m_request_hdr.m_url_cached.m_heap = heap;
+    alt->m_request_hdr.mark_target_dirty();
   }
 
   heap = (HdrHeap *)(alt->m_response_hdr.m_heap ? (buf + (intptr_t)alt->m_response_hdr.m_heap) : 0);
@@ -2047,6 +1994,7 @@ HTTPInfo::unmarshal(char *buf, int len, RefCountObj *block_ref)
     alt->m_response_hdr.m_heap = heap;
     alt->m_response_hdr.m_http = hh;
     alt->m_response_hdr.m_mime = hh->m_fields_impl;
+    alt->m_response_hdr.mark_target_dirty();
   }
 
   alt->m_unmarshal_len = orig_len - len;
@@ -2067,7 +2015,7 @@ HTTPInfo::check_marshalled(char *buf, int len)
     return false;
   }
 
-  if (alt->m_writeable != false) {
+  if (alt->m_flag.writeable_p != false) {
     return false;
   }
 
@@ -2156,22 +2104,587 @@ HTTPInfo::get_handle(char *buf, int len)
   return -1;
 }
 
+HTTPInfo::FragmentDescriptor*
+HTTPInfo::force_frag_at(unsigned int idx) {
+  FragmentDescriptor* frag;
+  FragmentDescriptorTable* old_table = 0;
+
+  ink_assert(m_alt);
+  ink_assert(idx >= 0);
+
+  if (0 == idx) return &m_alt->m_earliest;
+
+  if (0 == m_alt->m_fragments || idx > m_alt->m_fragments->m_n) { // no room at the inn
+    int64_t obj_size = this->object_size_get();
+    uint32_t ff_size = this->get_frag_fixed_size();
+    unsigned int n = 0; // set if we need to allocate, this is max array index needed.
+
+    ink_assert(ff_size);
+
+    if (0 == m_alt->m_fragments && obj_size > 0) {
+      n = (obj_size + ff_size - 1) / ff_size;
+      if (idx > n) n = idx;
+      if (!m_alt->m_earliest.m_flag.cached_p) ++n; // going to have an empty earliest fragment.
+    } else {
+      n = idx + MAX(4, idx>>1); // grow by 50% and at least 4
+      old_table = m_alt->m_fragments;
+    }
+
+    size_t size = FragmentDescriptorTable::calc_size(n);
+    size_t old_size = 0;
+    unsigned int old_count = 0;
+    int64_t offset = 0;
+    CryptoHash key;
+
+    m_alt->m_fragments = static_cast<FragmentDescriptorTable*>(ats_malloc(size));
+    ink_zero(*(m_alt->m_fragments)); // just need to zero the base struct.
+    if (old_table) {
+      old_count = old_table->m_n;
+      frag = &((*old_table)[old_count]);
+      offset = frag->m_offset;
+      key = frag->m_key;
+      old_size = FragmentDescriptorTable::calc_size(old_count);
+      memcpy(m_alt->m_fragments, old_table, old_size);
+      ats_free(old_table);
+    } else {
+      key = m_alt->m_earliest.m_key;
+      m_alt->m_fragments->m_cached_idx = 0;
+    }
+    m_alt->m_fragments->m_n = n;
+    m_alt->m_flag.table_allocated_p = true;
+    // fill out the new parts with offsets & keys.
+    ++old_count; // left as the index of the last frag in the previous set.
+    for ( frag = &((*m_alt->m_fragments)[old_count]) ; old_count < n ; ++old_count, ++frag ) {
+      key.next();
+      offset += ff_size;
+      frag->m_key = key;
+      frag->m_offset = offset;
+      frag->m_flags = 0;
+    }
+  }
+  ink_assert(idx > m_alt->m_fragments->m_cached_idx);
+  return &(*m_alt->m_fragments)[idx];
+}
+
 void
-HTTPInfo::push_frag_offset(FragOffset offset)
-{
+HTTPInfo::mark_frag_write(unsigned int idx) {
   ink_assert(m_alt);
-  if (0 == m_alt->m_frag_offsets) {
-    m_alt->m_frag_offsets = m_alt->m_integral_frag_offsets;
-  } else if (m_alt->m_frag_offset_count >= HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS &&
-             0 == (m_alt->m_frag_offset_count & (m_alt->m_frag_offset_count - 1))) {
-    // need more space than in integral storage and we're at an upgrade
-    // size (power of 2).
-    FragOffset *nf = static_cast<FragOffset *>(ats_malloc(sizeof(FragOffset) * (m_alt->m_frag_offset_count * 2)));
-    memcpy(nf, m_alt->m_frag_offsets, sizeof(FragOffset) * m_alt->m_frag_offset_count);
-    if (m_alt->m_frag_offsets != m_alt->m_integral_frag_offsets)
-      ats_free(m_alt->m_frag_offsets);
-    m_alt->m_frag_offsets = nf;
+  ink_assert(idx >= 0);
+
+  if (idx >= m_alt->m_frag_count) m_alt->m_frag_count = idx + 1;
+
+  if (0 == idx) {
+    m_alt->m_earliest.m_flag.cached_p = true;
+  } else {
+    this->force_frag_at(idx)->m_flag.cached_p = true;
   }
 
-  m_alt->m_frag_offsets[m_alt->m_frag_offset_count++] = offset;
+  // bump the last cached value if possible and mark complete if appropriate.
+  if (m_alt->m_fragments && idx == m_alt->m_fragments->m_cached_idx + 1) {
+    unsigned int j = idx + 1;
+    while (j < m_alt->m_frag_count && (*m_alt->m_fragments)[j].m_flag.cached_p)
+      ++j;
+    m_alt->m_fragments->m_cached_idx = j - 1;
+    if (m_alt->m_flag.content_length_p &&
+        (this->get_frag_fixed_size() + this->get_frag_offset(j-1)) > static_cast<int64_t>(m_alt->m_earliest.m_offset))
+      m_alt->m_flag.complete_p = true;
+  }
+}
+
+int
+HTTPInfo::get_frag_index_of(int64_t offset)
+{
+  int zret = 0;
+  int n = this->get_frag_count();
+  uint32_t ff_size = this->get_frag_fixed_size();
+
+  if (n < 2) {
+    // Not multi-frag, so just compute the index.
+    zret = offset / ff_size;
+  } else {
+    FragmentDescriptorTable* frags = this->get_frag_table();
+    zret = (offset / ff_size) + 1;
+    if ((*frags)[1].m_offset == 0) ++zret;
+    else if (static_cast<uint64_t>(offset) >= (*frags)[1].m_offset) { // otherwise it's in the earliest frag, index 0
+      // Need to handle old data where the offsets are not guaranteed to be regular.
+      // So we guess (which should be close) and if we're right, boom, else linear
+      // search.
+      while (0 < zret) {
+        if (static_cast<uint64_t>(offset) < (*frags)[zret].m_offset) {
+          --zret;
+        } else if (zret >= n-1) {
+          // if we're at the last known fragment, just compute the index.
+          zret += (static_cast<uint64_t>(offset) - (*frags)[zret].m_offset) / ff_size;
+          break;
+        } else if (static_cast<uint64_t>(offset) >= (*frags)[zret+1].m_offset) {
+          ++zret;
+        } else {
+          break;
+        }
+      }
+    }
+  }
+  return zret;
+}
+/***********************************************************************
+ *                                                                     *
+ *                      R A N G E   S U P P O R T                      *
+ *                                                                     *
+ ***********************************************************************/
+
+namespace {
+  // Need to promote this out of here at some point.
+  // This handles parsing an integer from a string with various limits and in 64 bits.
+  struct integer {
+    static size_t const MAX_DIGITS = 15;
+    static bool parse(ts::ConstBuffer const& b, uint64_t& result) {
+      bool zret = false;
+      if (0 < b.size() && b.size() <= MAX_DIGITS) {
+        size_t n;
+        result = ats_strto64(b.data(), b.size(), &n);
+        zret = n == b.size();
+      }
+      return zret;
+    }
+  };
+}
+
+bool
+HTTPRangeSpec::parseRangeFieldValue(char const* v, int len)
+{
+  // Maximum # of digits permitted for an offset. Avoid issues with overflow.
+  static size_t const MAX_DIGITS = 15;
+  ts::ConstBuffer src(v, len);
+  size_t n;
+
+  _state = INVALID;
+  src.skip(&ParseRules::is_ws);
+
+  if (src.size() > sizeof(HTTP_LEN_BYTES)+1 &&
+      0 == strncasecmp(src.data(), HTTP_VALUE_BYTES, HTTP_LEN_BYTES) && '=' == src[HTTP_LEN_BYTES]
+  ) {
+    src += HTTP_LEN_BYTES+1;
+    while (src) {
+      ts::ConstBuffer max = src.splitOn(',');
+
+      if (!max) { // no comma so everything in @a src should be processed as a single range.
+        max = src;
+        src.reset();
+      }
+
+      ts::ConstBuffer min = max.splitOn('-');
+
+      src.skip(&ParseRules::is_ws);
+      // Spec forbids whitespace anywhere in the range element.
+
+      if (min) {
+        if (ParseRules::is_digit(*min) && min.size() <= MAX_DIGITS) {
+          uint64_t low = ats_strto64(min.data(), min.size(), &n);
+          if (n < min.size()) break; // extra cruft in range, not even ws allowed
+          if (max) {
+            if (ParseRules::is_digit(*max) && max.size() <= MAX_DIGITS) {
+              uint64_t high = ats_strto64(max.data(), max.size(), &n);
+              if (n < max.size() && (max += n).skip(&ParseRules::is_ws))
+                break; // non-ws cruft after maximum
+              else
+                this->add(low, high);
+            } else {
+              break; // invalid characters for maximum
+            }
+          } else {
+            this->add(low, UINT64_MAX); // "X-" : "offset X to end of content"
+          }
+        } else {
+          break; // invalid characters for minimum
+        }
+      } else {
+        if (max) {
+          if (ParseRules::is_digit(*max) && max.size() <= MAX_DIGITS) {
+            uint64_t high = ats_strto64(max.data(), max.size(), &n);
+            if (n < max.size() && (max += n).skip(&ParseRules::is_ws)) {
+              break; // cruft after end of maximum
+            } else {
+              this->add(high, 0);
+            }
+          } else {
+            break; // invalid maximum
+          }
+        }
+      }
+    }
+    if (src) _state = INVALID; // didn't parse everything, must have been an error.
+  }
+  return _state != INVALID;
+}
+
+HTTPRangeSpec&
+HTTPRangeSpec::add(Range const& r)
+{
+  if (MULTI == _state) {
+    _ranges.push_back(r);
+  } else if (SINGLE == _state) {
+    _ranges.push_back(_single);
+    _ranges.push_back(r);
+    _state = MULTI;
+  } else {
+    _single = r;
+    _state = SINGLE;
+  }
+  return *this;
+}
+
+bool
+HTTPRangeSpec::apply(uint64_t len)
+{
+  if (!this->hasRanges()) {
+    // nothing - simplifying later logic.
+  } else if (0 == len) {
+    /* Must special case zero length content
+       - suffix ranges are OK but other ranges are not.
+       - Best option is to return a 200 (not 206 or 416) for all suffix range spec on zero length content.
+         (this is what Apache HTTPD does)
+       - So, mark result as either @c UNSATISFIABLE or @c EMPTY, clear all ranges.
+    */
+    _state = EMPTY;
+    if (!_single.isSuffix()) _state = UNSATISFIABLE;
+    for ( RangeBox::iterator spot = _ranges.begin(), limit = _ranges.end() ; spot != limit && EMPTY == _state ; ++spot ) {
+      if (!spot->isSuffix()) _state = UNSATISFIABLE;
+    }
+    _ranges.clear();
+  } else if (this->isSingle()) {
+    if (!_single.apply(len)) _state = UNSATISFIABLE;
+  } else { // gotta be MULTI
+    int src = 0, dst = 0;
+    int n = _ranges.size();
+    while (src < n) {
+      Range& r = _ranges[src];
+      if (r.apply(len)) {
+        if (src != dst) _ranges[dst] = r;
+        ++dst;
+      }
+      ++src;
+    }
+    // at this point, @a dst is the # of valid ranges.
+    if (dst > 0) {
+      _single = _ranges[0];
+      if (dst == 1) _state = SINGLE;
+      _ranges.resize(dst);
+    } else {
+      _state = UNSATISFIABLE;
+      _ranges.clear();
+    }
+  }
+  return this->isValid();
+}
+
+static ts::ConstBuffer const MULTIPART_BYTERANGE("multipart/byteranges", 20);
+static ts::ConstBuffer const MULTIPART_BOUNDARY("boundary", 9);
+
+int64_t
+HTTPRangeSpec::parseContentRangeFieldValue(char const* v, int len, Range& r, ts::ConstBuffer& boundary)
+{
+  // [amc] TBD - handle the multipart/byteranges syntax.
+  ts::ConstBuffer src(v, len);
+  int64_t zret = -1;
+
+  r.invalidate();
+  src.skip(&ParseRules::is_ws);
+
+  if (src.skipNoCase(MULTIPART_BYTERANGE)) {
+    while (src && (';' == *src || ParseRules::is_ws(*src))) ++src;
+    if (src.skipNoCase(MULTIPART_BOUNDARY)) {
+      src.trim(&ParseRules::is_ws);
+      boundary = src;
+    }
+  } else if (src.size() > sizeof(HTTP_LEN_BYTES)+1 &&
+      0 == strncasecmp(src.data(), HTTP_VALUE_BYTES, HTTP_LEN_BYTES) &&
+      ParseRules::is_ws(src[HTTP_LEN_BYTES]) // must have white space
+    ) {
+    uint64_t cl, low, high;
+    bool unsatisfied_p = false, indeterminate_p = false;
+    ts::ConstBuffer min, max;
+
+    src += HTTP_LEN_BYTES;
+    src.skip(&ParseRules::is_ws); // but can have any number
+
+    max = src.splitOn('/'); // src has total length value
+
+    if (max.size() == 1 && *max == '*') unsatisfied_p = true;
+    else min = max.splitOn('-');
+
+    src.trim(&ParseRules::is_ws);
+    if (src && src.size() == 1 && *src == '*') indeterminate_p = true;
+
+    // note - spec forbids internal spaces so it's "X-Y/Z" w/o whitespace.
+    // spec also says we can have "*/Z" or "X-Y/*" but never "*/*".
+
+    if ( !(indeterminate_p && unsatisfied_p) &&
+         (indeterminate_p || integer::parse(src, cl)) &&
+         (unsatisfied_p || (integer::parse(min, low) && integer::parse(max, high))
+    )) {
+      if (!unsatisfied_p) r._min = low, r._max = high;
+      if (!indeterminate_p) zret = static_cast<int64_t>(cl);
+    }
+  }
+  return zret;
+}
+
+namespace {
+
+  int
+  Calc_Digital_Length(uint64_t x)
+  {
+    char buff[32]; // big enough for 64 bit #
+    return snprintf(buff, sizeof(buff), "%" PRIu64, x);
+  }
+
+}
+
+uint64_t
+HTTPRangeSpec::calcPartBoundarySize(uint64_t object_size, uint64_t ct_val_len)
+{
+  size_t l_size = Calc_Digital_Length(object_size);
+  // CR LF "--" boundary-string CR LF "Content-Range" ": " "bytes " X "-" Y "/" Z CR LF Content-Type CR LF
+  uint64_t zret = 4 + HTTP_RANGE_BOUNDARY_LEN + 2 + MIME_LEN_CONTENT_RANGE + 2 + HTTP_LEN_BYTES + 1 + l_size + 1 +l_size + 1 + l_size + 2;
+  if (ct_val_len) zret += MIME_LEN_CONTENT_TYPE + 2 + ct_val_len + 2;
+  return zret;
+}
+
+uint64_t
+HTTPRangeSpec::calcContentLength(uint64_t object_size, uint64_t ct_val_len) const
+{
+  uint64_t size = object_size;
+  size_t nr = this->count();
+
+  if (nr >= 1) {
+    size = this->size(); // the real content size.
+    if (nr > 1) // part boundaries
+      size += nr * self::calcPartBoundarySize(object_size, ct_val_len) + 2; // need trailing '--'
+  }
+  return size;
+}
+
+uint64_t
+HTTPRangeSpec::writePartBoundary(MIOBuffer* out, char const* boundary_str, size_t boundary_len, uint64_t total_size, uint64_t low, uint64_t high, MIMEField* ctf, bool final)
+{
+  size_t x; // tmp for printf results.
+  size_t loc_size = Calc_Digital_Length(total_size)*3 + 3; // precomputed size of all the location / size text.
+  size_t n = self::calcPartBoundarySize(total_size, ctf ? ctf->m_len_value : 0) + (final ? 2 : 0);
+  Ptr<IOBufferData> d(new_IOBufferData(iobuffer_size_to_index(n, MAX_BUFFER_SIZE_INDEX), MEMALIGNED));
+  char* spot = d->data();
+
+  x = snprintf(spot, n, "\r\n--%.*s", static_cast<int>(boundary_len), boundary_str);
+  spot += x;
+  n -= x;
+  if (final) {
+    memcpy(spot, "--", 2);
+    spot += 2;
+    n -= 2;
+  }
+
+  x = snprintf(spot, n, "\r\n%.*s: %.*s", MIME_LEN_CONTENT_RANGE, MIME_FIELD_CONTENT_RANGE, HTTP_LEN_BYTES, HTTP_VALUE_BYTES);
+  spot += x;
+  n -= x;
+  spot[-HTTP_LEN_BYTES] = tolower(spot[-HTTP_LEN_BYTES]); // ugly cleanup just to be careful of stupid user agents.
+
+  x = snprintf(spot, n, " %" PRIu64 "-%" PRIu64 "/%" PRIu64, low, high, total_size);
+  // Need to space fill to match pre-computed size
+  if (x < loc_size) memset(spot+x, ' ', loc_size - x);
+  spot += loc_size;
+  n -= loc_size;
+
+  if (ctf) {
+    int ctf_len;
+    char const* ctf_val = ctf->value_get(&ctf_len);
+    if (ctf_val) {
+      x = snprintf(spot, n, "\r\n%.*s: %.*s", MIME_LEN_CONTENT_TYPE, MIME_FIELD_CONTENT_TYPE, ctf_len, ctf_val);
+      spot += x;
+      n -= x;
+    }
+  }
+
+  // This also takes care of the snprintf null termination problem.
+  *spot++ = '\r';
+  *spot++ = '\n';
+  n -= 2;
+
+  ink_assert(n == 0);
+  
+  IOBufferBlock* b = new_IOBufferBlock(d, spot - d->data());
+  b->_buf_end = b->_end;
+  out->append_block(b);
+
+  return spot - d->data();
+}
+
+int
+HTTPRangeSpec::print_array(char* buff, size_t len, Range const* rv, int count)
+{
+  size_t zret = 0;
+  bool first = true;
+
+  // Can't possibly write a range in less than this size buffer.
+  if (len < static_cast<size_t>(HTTP_LEN_BYTES) + 4) return 0;
+
+  for ( int i = 0 ; i < count ; ++i ) {
+    int n;
+
+    if (first) {
+      memcpy(buff, HTTP_VALUE_BYTES, HTTP_LEN_BYTES);
+      buff[HTTP_LEN_BYTES] = '=';
+      zret += HTTP_LEN_BYTES + 1;
+      first = false;
+    } else if (len < zret + 4) {
+      break;
+    } else {
+      buff[zret++] = ',';
+    }
+
+    n = snprintf(buff + zret, len - zret, "%" PRIu64 "-%" PRIu64 , rv[i]._min , rv[i]._max);
+    if (n + zret >= len) break; // ran out of room
+    else zret += n;
+  }
+  return zret;
+}
+
+int
+HTTPRangeSpec::print(char* buff, size_t len) const
+{
+  return this->hasRanges() ? this->print_array(buff, len, &(*(this->begin())), this->count()) : 0;
+}
+
+int
+HTTPRangeSpec::print_quantized(char* buff, size_t len, int64_t quantum, int64_t interstitial, int64_t initial) const
+{
+  static const int MAX_R = 20; // this needs to be promoted
+  // We will want a max # of ranges, probably a build time constant, in the not so distant
+  // future anyway, so might as well start here.
+  int qrn = 0; // count of quantized ranges
+  Range qr[MAX_R]; // quantized ranges
+
+  // Can't possibly write a range in less than this size buffer.
+  if (len < static_cast<size_t>(HTTP_LEN_BYTES) + 4) return 0;
+
+  // Avoid annoying "+1" in the adjacency checks.
+  if (interstitial < 1) interstitial = 1;
+  else ++interstitial;
+
+  for ( const_iterator spot = this->begin(), limit = this->end() ; spot != limit ; ++spot ) {
+    Range r(*spot);
+    int i;
+    if (quantum > 1) {
+      r._min = (r._min / quantum) * quantum;
+      r._max = ((r._max + quantum - 1)/quantum) * quantum - 1;
+    }
+    if (1 < initial && r._min < static_cast<uint64_t>(initial)) r._min = 0;
+    // blend in to the current ranges
+    for ( i = 0 ; i < qrn ; ++i ) {
+      Range& cr = qr[i];
+      if ((r._max + interstitial) < cr._min) {
+        memmove(qr, qr+1, sizeof(*qr) * qrn);
+        ++qrn;
+        qr[0] = r;
+        i = -1;
+        break;
+      } else if (cr._max + interstitial >= r._min) {
+        int j = i + 1;
+        cr._min = std::min(cr._min, r._min);
+        cr._max = std::max(cr._max, r._max);
+        while ( j < qrn) {
+          if (qr[j]._min < cr._max + interstitial)
+            cr._max = std::max(cr._max, qr[j]._max);
+          ++j;
+        }
+        if (j < qrn)
+          memmove(qr + i + 1, qr + j, sizeof(*qr) * qrn - j);
+        qrn -= j - i;
+        i = -1;
+        break;   
+      }
+    }
+    if (i >= qrn)
+      qr[qrn++] = r;
+    ink_assert(qrn <= MAX_R);
+  }
+
+  return this->print_array(buff, len, qr, qrn);
+}
+
+HTTPRangeSpec::Range
+HTTPInfo::get_range_for_frags(int low, int high)
+{
+  HTTPRangeSpec::Range zret;
+  zret._min = low < 1 ? 0 : (*m_alt->m_fragments)[low].m_offset;
+  zret._max = (high >= static_cast<int>(m_alt->m_frag_count) - 1 ? this->object_size_get()
+               : (*m_alt->m_fragments)[high+1].m_offset) - 1;
+  return zret;
+}
+
+/* Note - we're not handling unspecified content length and trailing segments at all here.
+   Must deal with that at some point.
+*/
+
+HTTPRangeSpec::Range
+HTTPInfo::get_uncached_hull(HTTPRangeSpec const& req)
+{
+  HTTPRangeSpec::Range r;
+
+  if (m_alt && !m_alt->m_flag.complete_p) {
+    HTTPRangeSpec::Range s = req.getConvexHull();
+    if (m_alt->m_fragments) {
+      int32_t lidx;
+      int32_t ridx;
+      if (s.isValid()) {
+        lidx = this->get_frag_index_of(s._min);
+        ridx = this->get_frag_index_of(s._max);
+      } else { // not a range request, get hull of all uncached fragments
+        lidx = m_alt->m_fragments->m_cached_idx + 1;
+        // This really isn't valid if !content_length_p, need to deal with that at some point.
+        ridx = this->get_frag_index_of(this->object_size_get());
+      }
+
+      if (lidx < 2 && !m_alt->m_earliest.m_flag.cached_p) lidx = 0;
+      else while (lidx <= ridx && (*m_alt->m_fragments)[lidx].m_flag.cached_p) ++lidx;
+
+      while (lidx <= ridx && (*m_alt->m_fragments)[ridx].m_flag.cached_p) --ridx;
+
+      if (lidx <= ridx) r = this->get_range_for_frags(lidx, ridx);
+    } else { // no fragments past earliest cached yet
+      r._min = m_alt->m_earliest.m_flag.cached_p ? this->get_frag_fixed_size() : 0;
+      if (s.isValid()) {
+        r._min = std::max(r._min, s._min);
+        r._max = s._max;
+      }
+    }
+    if (r.isValid() && m_alt->m_flag.content_length_p && static_cast<int64_t>(r._max) > this->object_size_get())
+      r._max = this->object_size_get();
+  }
+  return r;
+}
+
+# if 0
+bool
+HTTPInfo::get_uncached(HTTPRangeSpec const& req, HTTPRangeSpec& result)
+{
+  bool zret = false;
+  if (m_alt && !m_alt->m_flag.complete_p) {
+    FragmentAccessor frags(m_alt);
+
+    for ( HTTPRangeSpec::const_iterator spot = req.begin(), limit = req.end() ; spot != limit ; ++spot ) {
+      int32_t lidx = this->get_frag_index_of(spot->_min);
+      int32_t ridx = this->get_frag_index_of(spot->_max);
+      while (lidx <= ridx && frags[lidx].m_flag.cached_p)
+        ++lidx;
+      if (lidx > ridx) continue; // All of this range is present.
+      while (lidx <= ridx && frags[ridx].m_flag.cached_p) // must hit missing frag at lhs at the latest
+        --ridx;
+
+      if (lidx <= ridx) {
+        result.add(this->get_range_for_frags(lidx, ridx));
+        zret = true;
+      }
+    }
+  }
+  return zret;
 }
+# endif


[3/3] trafficserver git commit: TS-974: Base implmentation.

Posted by am...@apache.org.
TS-974: Base implmentation.


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/799a83bb
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/799a83bb
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/799a83bb

Branch: refs/heads/ts-974-5-3-x
Commit: 799a83bb4bdf8a34b5716289e6b9e2edc60772cf
Parents: 98a2e1b
Author: Alan M. Carroll <so...@yahoo-inc.com>
Authored: Wed Mar 25 20:13:14 2015 -0500
Committer: Alan M. Carroll <am...@apache.org>
Committed: Sun Apr 19 12:46:57 2015 -0500

----------------------------------------------------------------------
 doc/arch/cache/cache-data-structures.en.rst |   3 +-
 iocore/cache/Cache.cc                       |  75 ++-
 iocore/cache/CacheDir.cc                    |  92 ++-
 iocore/cache/CacheHttp.cc                   | 310 +++++++--
 iocore/cache/CacheRead.cc                   | 559 ++++++++++------
 iocore/cache/CacheWrite.cc                  | 331 +++++++---
 iocore/cache/I_Cache.h                      |  90 ++-
 iocore/cache/I_CacheDefs.h                  |   4 +-
 iocore/cache/P_CacheBC.h                    |  30 +-
 iocore/cache/P_CacheDir.h                   |  79 ++-
 iocore/cache/P_CacheHttp.h                  | 277 +++++++-
 iocore/cache/P_CacheInternal.h              | 117 +++-
 iocore/cluster/ClusterCache.cc              |   2 +-
 iocore/cluster/ClusterVConnection.cc        |   1 +
 iocore/cluster/P_Cluster.h                  |   3 +-
 iocore/cluster/P_ClusterCache.h             |  12 +
 lib/ts/CryptoHash.h                         |  47 +-
 lib/ts/InkErrno.h                           |  38 +-
 lib/ts/ParseRules.cc                        |  17 +
 lib/ts/ParseRules.h                         |  20 +
 lib/ts/TsBuffer.h                           |  97 ++-
 lib/ts/ink_code.cc                          |  74 +++
 proxy/hdrs/HTTP.cc                          | 757 ++++++++++++++++++----
 proxy/hdrs/HTTP.h                           | 786 ++++++++++++++++++++---
 proxy/http/HttpCacheSM.cc                   |  25 +
 proxy/http/HttpCacheSM.h                    |   9 +-
 proxy/http/HttpDebugNames.cc                |   2 +
 proxy/http/HttpSM.cc                        | 132 +++-
 proxy/http/HttpSM.h                         |   8 +-
 proxy/http/HttpTransact.cc                  | 204 ++++--
 proxy/http/HttpTransact.h                   |  94 ++-
 proxy/http/HttpTransactHeaders.cc           |  14 +
 proxy/http/HttpTransactHeaders.h            |   1 +
 33 files changed, 3512 insertions(+), 798 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/doc/arch/cache/cache-data-structures.en.rst
----------------------------------------------------------------------
diff --git a/doc/arch/cache/cache-data-structures.en.rst b/doc/arch/cache/cache-data-structures.en.rst
index 1158051..b172999 100644
--- a/doc/arch/cache/cache-data-structures.en.rst
+++ b/doc/arch/cache/cache-data-structures.en.rst
@@ -24,7 +24,8 @@ Cache Data Structures
 
 .. cpp:class:: OpenDir
 
-   An open directory entry. It contains all the information of a
+   This represents an open directory entry. An entry is open when there is an active write on the object. Read operations do not of themselves require an `OpenDir` but if there is already one for the object it will be used by the read operation to coordinate with the write operations.
+
    :cpp:class:`Dir` plus additional information from the first :cpp:class:`Doc`.
 
 .. cpp:class:: CacheVC

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/Cache.cc
----------------------------------------------------------------------
diff --git a/iocore/cache/Cache.cc b/iocore/cache/Cache.cc
index d8e7bed..78f8d14 100644
--- a/iocore/cache/Cache.cc
+++ b/iocore/cache/Cache.cc
@@ -301,7 +301,7 @@ CacheVC::CacheVC() : alternate_index(CACHE_ALT_INDEX_DEFAULT)
 }
 
 #ifdef HTTP_CACHE
-HTTPInfo::FragOffset *
+HTTPInfo::FragmentDescriptorTable*
 CacheVC::get_frag_table()
 {
   ink_assert(alternate.valid());
@@ -471,12 +471,30 @@ CacheVC::set_http_info(CacheHTTPInfo *ainfo)
   } else
     f.allow_empty_doc = 0;
   alternate.copy_shallow(ainfo);
+  // This is not a good place to do this but I can't figure out a better one. We must do it
+  // no earlier than this, because there's no actual alternate to store the value in before this
+  // and I don't know of any later point that's guaranteed to be called before this is needed.
+  alternate.m_alt->m_fixed_fragment_size = cache_config_target_fragment_size - sizeofDoc;
   ainfo->clear();
 }
 #endif
 
-bool
-CacheVC::set_pin_in_cache(time_t time_pin)
+int64_t
+CacheVC::set_inbound_range(int64_t min, int64_t max)
+{
+  resp_range.clear();
+  resp_range.getRangeSpec().add(min,max);
+  return 1 + (max - min);
+}
+
+void
+CacheVC::set_full_content_length(int64_t cl)
+{
+  alternate.object_size_set(cl);
+  resp_range.apply(cl);
+}
+
+bool CacheVC::set_pin_in_cache(time_t time_pin)
 {
   if (total_len) {
     ink_assert(!"should Pin the document before writing");
@@ -491,7 +509,19 @@ CacheVC::set_pin_in_cache(time_t time_pin)
 }
 
 bool
-CacheVC::set_disk_io_priority(int priority)
+CacheVC::get_uncached(HTTPRangeSpec& result)
+{
+  HTTPRangeSpec::Range r = od ? write_vector->get_uncached_hull(earliest_key, resp_range.getRangeSpec())
+    : alternate.get_uncached_hull(resp_range.getRangeSpec())
+    ;
+  if (r.isValid()) {
+    result.add(r);
+    return true;
+  }
+  return false;
+}
+
+bool CacheVC::set_disk_io_priority(int priority)
 {
   ink_assert(priority >= AIO_LOWEST_PRIORITY);
   io.aiocb.aio_reqprio = priority;
@@ -2359,6 +2389,19 @@ CacheVC::is_pread_capable()
   return !f.read_from_writer_called;
 }
 
+# if 0
+void
+CacheVC::get_missing_ranges(HTTPRangeSpec& missing)
+{
+  missing.reset();
+  if (0 == alternate.);
+  // For now we'll just compute the convex hull of the missing data.
+  for ( RangeBox::const_iterator spot = req.begin(), limit = req.end() ; spot != limit ; ++spot ) {
+    
+  }
+}
+#endif
+
 #define STORE_COLLISION 1
 
 #ifdef HTTP_CACHE
@@ -2384,7 +2427,7 @@ unmarshal_helper(Doc *doc, Ptr<IOBufferData> &buf, int &okay)
     @internal I looked at doing this in place (rather than a copy & modify) but
     - The in place logic would be even worse than this mess
     - It wouldn't save you that much, since you end up doing inserts early in the buffer.
-      Without extreme care in the logic it could end up doing more copying thatn
+      Without extreme care in the logic it could end up doing more copying than
       the simpler copy & modify.
 
     @internal This logic presumes the existence of some slack at the end of the buffer, which
@@ -2403,16 +2446,16 @@ upgrade_doc_version(Ptr<IOBufferData> &buf)
     if (0 == doc->hlen) {
       Debug("cache_bc", "Doc %p without header, no upgrade needed.", doc);
     } else if (CACHE_FRAG_TYPE_HTTP_V23 == doc->doc_type) {
-      cache_bc::HTTPCacheAlt_v21 *alt = reinterpret_cast<cache_bc::HTTPCacheAlt_v21 *>(doc->hdr());
+      typedef cache_bc::HTTPCacheFragmentTable::FragOffset FragOffset;
+      cache_bc::HTTPCacheAlt_v21* alt = reinterpret_cast<cache_bc::HTTPCacheAlt_v21*>(doc->hdr());
       if (alt && alt->is_unmarshalled_format()) {
         Ptr<IOBufferData> d_buf(ioDataAllocator.alloc());
-        Doc *d_doc;
-        char *src;
-        char *dst;
-        char *hdr_limit = doc->data();
-        HTTPInfo::FragOffset *frags =
-          reinterpret_cast<HTTPInfo::FragOffset *>(static_cast<char *>(buf->data()) + cache_bc::sizeofDoc_v23);
-        int frag_count = doc->_flen / sizeof(HTTPInfo::FragOffset);
+        Doc* d_doc;
+        char* src;
+        char* dst;
+        char* hdr_limit = doc->data();
+        FragOffset* frags = reinterpret_cast<FragOffset*>(static_cast<char*>(buf->data()) + cache_bc::sizeofDoc_v23);
+        int frag_count = doc->_flen / sizeof(FragOffset);
         size_t n = 0;
         size_t content_size = doc->data_len();
 
@@ -2753,6 +2796,11 @@ LinterimRead:
   io.action = this;
   io.thread = mutex->thread_holding->tt == DEDICATED ? AIO_CALLBACK_THREAD_ANY : mutex->thread_holding;
   SET_HANDLER(&CacheVC::handleReadDone);
+  {
+    char xt[33];
+    Debug("amc", "cache read : key = %s %" PRId64 " bytes at stripe offset =% " PRId64
+          , key.toHexStr(xt), io.aiocb.aio_nbytes, io.aiocb.aio_offset);
+  }
   ink_assert(ink_aio_read(&io) >= 0);
   CACHE_DEBUG_INCREMENT_DYN_STAT(cache_pread_count_stat);
   return EVENT_CONT;
@@ -3598,7 +3646,6 @@ CacheProcessor::open_read(Continuation *cont, URL *url, bool cluster_cache_local
   return caches[type]->open_read(cont, url, request, params, type);
 }
 
-
 //----------------------------------------------------------------------------
 Action *
 CacheProcessor::open_write(Continuation *cont, int expected_size, URL *url, bool cluster_cache_local, CacheHTTPHdr *request,

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/CacheDir.cc
----------------------------------------------------------------------
diff --git a/iocore/cache/CacheDir.cc b/iocore/cache/CacheDir.cc
index 779c16b..9bac5d8 100644
--- a/iocore/cache/CacheDir.cc
+++ b/iocore/cache/CacheDir.cc
@@ -69,28 +69,33 @@ OpenDir::OpenDir()
    Returns 1 on success and 0 on failure.
    */
 int
-OpenDir::open_write(CacheVC *cont, int allow_if_writers, int max_writers)
+OpenDir::open_write(CacheVC *cont, int /* allow_if_writers */, int /* max_writers */)
 {
   ink_assert(cont->vol->mutex->thread_holding == this_ethread());
   unsigned int h = cont->first_key.slice32(0);
   int b = h % OPEN_DIR_BUCKETS;
   for (OpenDirEntry *d = bucket[b].head; d; d = d->link.next) {
-    if (!(d->writers.head->first_key == cont->first_key))
+    if (!(d->first_key == cont->first_key))
       continue;
-    if (allow_if_writers && d->num_writers < d->max_writers) {
-      d->writers.push(cont);
+//    if (allow_if_writers && d->num_writers < d->max_writers) {
+//      d->writers.push(cont);
+    // [amc] Need to think if we want to track writers per object and not just per alt.
+    // useful to know when to close out the OpenDirEntry.
       d->num_writers++;
       cont->od = d;
       cont->write_vector = &d->vector;
       return 1;
-    }
+//    }
     return 0;
   }
-  OpenDirEntry *od = THREAD_ALLOC(openDirEntryAllocator, cont->mutex->thread_holding);
-  od->readers.head = NULL;
-  od->writers.push(cont);
+  OpenDirEntry *od = THREAD_ALLOC(openDirEntryAllocator,
+                                  cont->mutex->thread_holding);
+//  od->readers.head = NULL;
+//  od->writers.push(cont);
+  od->mutex = new_ProxyMutex();
+  od->first_key = cont->first_key;
   od->num_writers = 1;
-  od->max_writers = max_writers;
+//  od->max_writers = max_writers;
   od->vector.data.data = &od->vector.data.fast_data[0];
   od->dont_update_directory = 0;
   od->move_resident_alt = 0;
@@ -106,7 +111,7 @@ OpenDir::open_write(CacheVC *cont, int allow_if_writers, int max_writers)
 int
 OpenDir::signal_readers(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
 {
-  Queue<CacheVC, Link_CacheVC_opendir_link> newly_delayed_readers;
+  CacheVCQ newly_delayed_readers;
   EThread *t = mutex->thread_holding;
   CacheVC *c = NULL;
   while ((c = delayed_readers.dequeue())) {
@@ -132,15 +137,15 @@ int
 OpenDir::close_write(CacheVC *cont)
 {
   ink_assert(cont->vol->mutex->thread_holding == this_ethread());
-  cont->od->writers.remove(cont);
-  cont->od->num_writers--;
-  if (!cont->od->writers.head) {
+//  cont->od->writers.remove(cont);
+  if (--(cont->od->num_writers) < 1) {
     unsigned int h = cont->first_key.slice32(0);
     int b = h % OPEN_DIR_BUCKETS;
     bucket[b].remove(cont->od);
-    delayed_readers.append(cont->od->readers);
+//    delayed_readers.append(cont->od->readers);
     signal_readers(0, 0);
     cont->od->vector.clear();
+    cont->od->mutex = 0;
     THREAD_FREE(cont->od, openDirEntryAllocator, cont->mutex->thread_holding);
   }
   cont->od = NULL;
@@ -153,11 +158,12 @@ OpenDir::open_read(INK_MD5 *key)
   unsigned int h = key->slice32(0);
   int b = h % OPEN_DIR_BUCKETS;
   for (OpenDirEntry *d = bucket[b].head; d; d = d->link.next)
-    if (d->writers.head->first_key == *key)
+    if (d->first_key == *key)
       return d;
   return NULL;
 }
 
+# if 0
 int
 OpenDirEntry::wait(CacheVC *cont, int msec)
 {
@@ -168,6 +174,62 @@ OpenDirEntry::wait(CacheVC *cont, int msec)
   readers.push(cont);
   return EVENT_CONT;
 }
+# endif
+
+int
+OpenDirEntry::index_of(CacheKey const& alt_key)
+{
+  return vector.index_of(alt_key);
+}
+
+bool
+OpenDirEntry::has_writer(CacheKey const& alt_key)
+{
+  return vector.has_writer(alt_key);
+}
+
+OpenDirEntry&
+OpenDirEntry::write_active(CacheKey const& alt_key, CacheVC* vc, int64_t offset)
+{
+  Debug("amc", "VC %p write active @ %" PRId64, vc, offset);
+  vector.write_active(alt_key, vc, offset);
+  return *this;
+}
+
+OpenDirEntry&
+OpenDirEntry::write_complete(CacheKey const& alt_key, CacheVC* vc, bool success)
+{
+  Debug("amc", "[OpenDir::write_complete] VC %p write %s", vc, (success ? "succeeded" : "failed"));
+  vector.write_complete(alt_key, vc, success);
+  return *this;
+}
+
+bool
+OpenDirEntry::is_write_active(CacheKey const& alt_key, int64_t offset)
+{
+  return vector.is_write_active(alt_key, offset);
+}
+
+CacheKey const&
+OpenDirEntry::key_for(CacheKey const& alt_key, int64_t offset)
+{
+  return vector.key_for(alt_key, offset);
+}
+
+OpenDirEntry&
+OpenDirEntry::waiting_for(CacheKey const& alt_key, CacheVC* vc, int64_t offset)
+{
+  Debug("amc", "vc %p waiting for %" PRId64, vc, offset);
+  vector.waiting_for(alt_key, vc, offset);
+  return *this;
+}
+
+OpenDirEntry&
+OpenDirEntry::close_writer(CacheKey const& alt_key, CacheVC* vc)
+{
+  vector.close_writer(alt_key, vc);
+  return *this;
+}
 
 //
 // Cache Directory

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/CacheHttp.cc
----------------------------------------------------------------------
diff --git a/iocore/cache/CacheHttp.cc b/iocore/cache/CacheHttp.cc
index 3d7dfba..97837ee 100644
--- a/iocore/cache/CacheHttp.cc
+++ b/iocore/cache/CacheHttp.cc
@@ -29,10 +29,10 @@
 /*-------------------------------------------------------------------------
   -------------------------------------------------------------------------*/
 
-static vec_info default_vec_info;
+// Guaranteed to be all zero?
+static CacheHTTPInfoVector::Item default_vec_info;
 
 #ifdef HTTP_CACHE
-static CacheHTTPInfo default_http_info;
 
 CacheHTTPInfoVector::CacheHTTPInfoVector() : magic(NULL), data(&default_vec_info, 4), xcount(0)
 {
@@ -46,7 +46,7 @@ CacheHTTPInfoVector::~CacheHTTPInfoVector()
   int i;
 
   for (i = 0; i < xcount; i++) {
-    data[i].alternate.destroy();
+    data[i]._alternate.destroy();
   }
   vector_buf.clear();
   magic = NULL;
@@ -61,7 +61,7 @@ CacheHTTPInfoVector::insert(CacheHTTPInfo *info, int index)
   if (index == CACHE_ALT_INDEX_DEFAULT)
     index = xcount++;
 
-  data(index).alternate.copy_shallow(info);
+  data(index)._alternate.copy_shallow(info);
   return index;
 }
 
@@ -77,8 +77,8 @@ CacheHTTPInfoVector::detach(int idx, CacheHTTPInfo *r)
   ink_assert(idx >= 0);
   ink_assert(idx < xcount);
 
-  r->copy_shallow(&data[idx].alternate);
-  data[idx].alternate.destroy();
+  r->copy_shallow(&data[idx]._alternate);
+  data[idx]._alternate.destroy();
 
   for (i = idx; i < (xcount - 1); i++) {
     data[i] = data[i + i];
@@ -94,7 +94,7 @@ void
 CacheHTTPInfoVector::remove(int idx, bool destroy)
 {
   if (destroy)
-    data[idx].alternate.destroy();
+    data[idx]._alternate.destroy();
 
   for (; idx < (xcount - 1); idx++)
     data[idx] = data[idx + 1];
@@ -112,7 +112,7 @@ CacheHTTPInfoVector::clear(bool destroy)
 
   if (destroy) {
     for (i = 0; i < xcount; i++) {
-      data[i].alternate.destroy();
+      data[i]._alternate.destroy();
     }
   }
   xcount = 0;
@@ -134,14 +134,14 @@ CacheHTTPInfoVector::print(char *buffer, size_t buf_size, bool temps)
   purl = 1;
 
   for (i = 0; i < xcount; i++) {
-    if (data[i].alternate.valid()) {
+    if (data[i]._alternate.valid()) {
       if (purl) {
         Arena arena;
         char *url;
 
         purl = 0;
         URL u;
-        data[i].alternate.request_url_get(&u);
+        data[i]._alternate.request_url_get(&u);
         url = u.string_get(&arena);
         if (url) {
           snprintf(p, buf_size, "[%s] ", url);
@@ -151,8 +151,9 @@ CacheHTTPInfoVector::print(char *buffer, size_t buf_size, bool temps)
         }
       }
 
-      if (temps || !(data[i].alternate.object_key_get() == zero_key)) {
-        snprintf(p, buf_size, "[%d %s]", data[i].alternate.id_get(), CacheKey(data[i].alternate.object_key_get()).toHexStr(buf));
+      if (temps || !(data[i]._alternate.object_key_get() == zero_key)) {
+        snprintf(p, buf_size, "[%d %s]", data[i]._alternate.id_get(),
+                     CacheKey(data[i]._alternate.object_key_get()).toHexStr(buf));
         tmp = strlen(p);
         p += tmp;
         buf_size -= tmp;
@@ -170,7 +171,7 @@ CacheHTTPInfoVector::marshal_length()
   int length = 0;
 
   for (int i = 0; i < xcount; i++) {
-    length += data[i].alternate.marshal_length();
+    length += data[i]._alternate.marshal_length();
   }
 
   return length;
@@ -187,7 +188,7 @@ CacheHTTPInfoVector::marshal(char *buf, int length)
   ink_assert(!(((intptr_t)buf) & 3)); // buf must be aligned
 
   for (int i = 0; i < xcount; i++) {
-    int tmp = data[i].alternate.marshal(buf, length);
+    int tmp = data[i]._alternate.marshal(buf, length);
     length -= tmp;
     buf += tmp;
     count++;
@@ -199,8 +200,10 @@ CacheHTTPInfoVector::marshal(char *buf, int length)
   return buf - start;
 }
 
-int
-CacheHTTPInfoVector::unmarshal(const char *buf, int length, RefCountObj *block_ptr)
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+uint32_t
+CacheHTTPInfoVector::get_handles(const char *buf, int length, RefCountObj * block_ptr)
 {
   ink_assert(!(((intptr_t)buf) & 3)); // buf must be aligned
 
@@ -208,51 +211,280 @@ CacheHTTPInfoVector::unmarshal(const char *buf, int length, RefCountObj *block_p
   CacheHTTPInfo info;
   xcount = 0;
 
-  while (length - (buf - start) > (int)sizeof(HTTPCacheAlt)) {
-    int tmp = HTTPInfo::unmarshal((char *)buf, length - (buf - start), block_ptr);
+  vector_buf = block_ptr;
+
+  while (length - (buf - start) > (int) sizeof(HTTPCacheAlt)) {
+
+    int tmp = info.get_handle((char *) buf, length - (buf - start));
     if (tmp < 0) {
-      return -1;
+      ink_assert(!"CacheHTTPInfoVector::unmarshal get_handle() failed");
+      return (uint32_t) -1;
     }
-    info.m_alt = (HTTPCacheAlt *)buf;
     buf += tmp;
 
-    data(xcount).alternate = info;
+    data(xcount)._alternate = info;
     xcount++;
   }
 
   return ((caddr_t)buf - (caddr_t)start);
 }
 
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+
+int
+CacheHTTPInfoVector::index_of(CacheKey const& alt_key)
+{
+  int zret;
+  for ( zret = 0 ; zret < xcount && alt_key != data[zret]._alternate.object_key_get() ; ++zret )
+    ;
+  return zret < xcount ? zret : -1;
+}
 
 /*-------------------------------------------------------------------------
   -------------------------------------------------------------------------*/
-uint32_t
-CacheHTTPInfoVector::get_handles(const char *buf, int length, RefCountObj *block_ptr)
+
+CacheKey const&
+CacheHTTPInfoVector::key_for(CacheKey const& alt_key, int64_t offset)
 {
-  ink_assert(!(((intptr_t)buf) & 3)); // buf must be aligned
+  int idx = this->index_of(alt_key);
+  Item& item = data[idx];
+  return item._alternate.get_frag_key_of(offset);
+}
 
-  const char *start = buf;
-  CacheHTTPInfo info;
-  xcount = 0;
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
 
-  vector_buf = block_ptr;
+CacheHTTPInfoVector&
+CacheHTTPInfoVector::write_active(CacheKey const& alt_key, CacheVC* vc, int64_t offset)
+{
+  int idx = this->index_of(alt_key);
+  Item& item = data[idx];
 
-  while (length - (buf - start) > (int)sizeof(HTTPCacheAlt)) {
-    int tmp = info.get_handle((char *)buf, length - (buf - start));
-    if (tmp < 0) {
-      ink_assert(!"CacheHTTPInfoVector::unmarshal get_handle() failed");
-      return (uint32_t)-1;
+  Debug("amc", "[CacheHTTPInfoVector::write_active] VC %p write %" PRId64, vc, offset);
+
+  vc->fragment = item._alternate.get_frag_index_of(offset);
+  item._active.push(vc);
+  return *this;
+}
+
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+
+CacheHTTPInfoVector&
+CacheHTTPInfoVector::write_complete(CacheKey const& alt_key, CacheVC* vc, bool success)
+{
+  int idx = this->index_of(alt_key);
+  Item& item = data[idx];
+  CacheVC* reader;
+
+  Debug("amc", "[CacheHTTPInfoVector::write_complete] VC %p write %s", vc, (success ? "succeeded" : "failed"));
+
+  item._active.remove(vc);
+  if (success) item._alternate.mark_frag_write(vc->fragment);
+
+  // Kick all the waiters, success or fail.
+  while (NULL != (reader = item._waiting.pop()))
+    eventProcessor.schedule_imm(reader);
+
+  return *this;
+}
+
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+
+bool
+CacheHTTPInfoVector::has_writer(CacheKey const& alt_key)
+{
+  int alt_idx = this->index_of(alt_key);
+  return alt_idx >= 0 && data[alt_idx]._writers.head != NULL;
+}
+
+bool
+CacheHTTPInfoVector::is_write_active(CacheKey const& alt_key, int64_t offset)
+{
+  int alt_idx = this->index_of(alt_key);
+  Item& item = data[alt_idx];
+  int frag_idx = item._alternate.get_frag_index_of(offset);
+  for ( CacheVC* vc = item._active.head ; vc ; vc = item._active.next(vc) ) {
+    if (vc->fragment == frag_idx) return true;
+  }
+  return false;
+}
+
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+
+CacheHTTPInfoVector&
+CacheHTTPInfoVector::waiting_for(CacheKey const& alt_key, CacheVC* vc, int64_t offset)
+{
+  int alt_idx = this->index_of(alt_key);
+  Item& item = data[alt_idx];
+  int frag_idx = item._alternate.get_frag_index_of(offset);
+  vc->fragment = frag_idx;
+  item._waiting.push(vc);
+  return *this;
+}
+
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+
+CacheHTTPInfoVector&
+CacheHTTPInfoVector::close_writer(CacheKey const& alt_key, CacheVC* vc)
+{
+  CacheVC* reader;
+  int alt_idx = this->index_of(alt_key);
+  Item& item = data[alt_idx];
+  item._writers.remove(vc);
+  while (NULL != (reader = item._waiting.pop())) {
+    Debug("amc", "[close_writer] wake up %p", reader);
+    reader->wake_up_thread->schedule_imm(reader);
+  }
+  return *this;
+}
+
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+
+HTTPRangeSpec::Range
+CacheHTTPInfoVector::get_uncached_hull(CacheKey const& alt_key, HTTPRangeSpec const& req)
+{
+  int alt_idx = this->index_of(alt_key);
+  Item& item = data[alt_idx];
+  Queue<CacheVC, Link_CacheVC_OpenDir_Link> writers;
+  CacheVC* vc;
+  CacheVC* cycle_vc = NULL;
+  // Yeah, this need to be tunable.
+  uint64_t DELTA = item._alternate.get_frag_fixed_size() * 16;
+  HTTPRangeSpec::Range r(item._alternate.get_uncached_hull(req));
+
+  if (r.isValid()) {
+    /* Now clip against the writers.
+       We move all the writers to a local list and move them back as we are done using them to clip.
+       This is so we don't skip a potentially valid writer because they are not in start order.
+    */
+    writers.append(item._writers);
+    item._writers.clear();
+    while (r._min < r._max && NULL != (vc = writers.pop())) {
+      uint64_t base = static_cast<int64_t>(writers.head->resp_range.getOffset());
+      uint64_t delta = static_cast<int64_t>(writers.head->resp_range.getRemnantSize());
+
+      if (base+delta < r._min || base > r._max) {
+        item._writers.push(vc); // of no use to us, just put it back.
+      } else if (base < r._min + DELTA) {
+        r._min = base + delta; // we can wait, so depend on this writer and clip.
+        item._writers.push(vc); // we're done with it, put it back.
+        cycle_vc = NULL; // we did something so clear cycle indicator
+      } else if (vc == cycle_vc) { // we're looping.
+        item._writers.push(vc); // put this one back.
+        while (NULL != (vc = writers.pop())) item._writers.push(vc); // and the rest.
+      } else {
+        writers.enqueue(vc); // put it back to later checking.
+        if (NULL == cycle_vc) cycle_vc = vc; // but keep an eye out for it coming around again.
+      }
     }
-    buf += tmp;
+  }
+  return r;
+}
 
-    data(xcount).alternate = info;
-    xcount++;
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+
+void
+CacheRange::clear()
+{
+  _offset = 0;
+  _idx = -1;
+  _pending_range_shift_p = false;
+  _ct_field = NULL; // need to do real cleanup at some point.
+  _r.clear();
+}
+
+bool
+CacheRange::init(HTTPHdr* req)
+{
+  bool zret = true;
+  MIMEField* rf = req->field_find(MIME_FIELD_RANGE, MIME_LEN_RANGE);
+  if (rf) {
+    int len;
+    char const* val = rf->value_get(&len);
+    zret = _r.parseRangeFieldValue(val, len);
   }
+  return zret;
+}
 
-  return ((caddr_t)buf - (caddr_t)start);
+bool
+CacheRange::start()
+{
+  bool zret = false;
+
+  if (_r.hasRanges()) {
+    _offset = _r[_idx = 0]._min;
+    _pending_range_shift_p = true;
+    zret = true;
+  }
+  return zret;
+}
+
+bool
+CacheRange::apply(uint64_t len)
+{
+  bool zret = _r.apply(len);
+  if (zret) {
+    _len = len;
+    if (_r.hasRanges()) {
+      _offset = _r[_idx = 0]._min;
+      if (_r.isMulti()) _pending_range_shift_p = true;
+    }
+  }
+  return zret;
+}
+
+uint64_t
+CacheRange::consume(uint64_t size)
+{
+  switch (_r._state) {
+  case HTTPRangeSpec::EMPTY: _offset += size; break;
+  case HTTPRangeSpec::SINGLE: _offset += std::min(size, (_r._single._max - _offset) + 1 ); break;
+  case HTTPRangeSpec::MULTI:
+    ink_assert(_idx < static_cast<int>(_r.count()));
+    // Must not consume more than 1 range or the boundary strings won't get sent.
+    ink_assert(!_pending_range_shift_p);
+    ink_assert(size <= (_r[_idx]._max - _offset) + 1);
+    _offset += size;
+    if (_offset > _r[_idx]._max && ++_idx < static_cast<int>(_r.count())) {
+      _offset = _r[_idx]._min;
+      _pending_range_shift_p = true;
+    }
+    break;
+  default: break;
+  }
+
+  return _offset;
 }
 
-#else // HTTP_CACHE
+CacheRange&
+CacheRange::generateBoundaryStr(CacheKey const& key)
+{
+  uint64_t rnd = this_ethread()->generator.random();
+  snprintf(_boundary, sizeof(_boundary), "%016" PRIx64 "%016" PRIx64 "..%016" PRIx64, key.slice64(0), key.slice64(1), rnd);
+  // GAH! snprintf null terminates so we can't actually print the last nybble that way and all of
+  // the internal hex converters do the same thing. This is crazy code I need to fix at some point.
+  // It is critical to print every nybble or the content lengths won't add up.
+  _boundary[HTTP_RANGE_BOUNDARY_LEN-1] = "0123456789abcdef"[rnd & 0xf];
+  return *this;
+}
+
+uint64_t
+CacheRange::calcContentLength() const
+{
+  return _r.calcContentLength(_len, _ct_field ? _ct_field->m_len_value : 0);
+}
+
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+
+#else //HTTP_CACHE
 
 CacheHTTPInfoVector::CacheHTTPInfoVector() : data(&default_vec_info, 4), xcount(0)
 {
@@ -348,5 +580,7 @@ CacheHTTPInfoVector::get_handles(const char * /* buf ATS_UNUSED */, int /* lengt
   ink_assert(0);
   return 0;
 }
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
 
 #endif // HTTP_CACHE

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/CacheRead.cc
----------------------------------------------------------------------
diff --git a/iocore/cache/CacheRead.cc b/iocore/cache/CacheRead.cc
index 4ba02f4..6a41267 100644
--- a/iocore/cache/CacheRead.cc
+++ b/iocore/cache/CacheRead.cc
@@ -48,14 +48,14 @@ Cache::open_read(Continuation *cont, CacheKey *key, CacheFragType type, char *ho
     CACHE_TRY_LOCK(lock, vol->mutex, mutex->thread_holding);
     if (!lock.is_locked() || (od = vol->open_read(key)) || dir_probe(key, vol, &result, &last_collision)) {
       c = new_CacheVC(cont);
-      SET_CONTINUATION_HANDLER(c, &CacheVC::openReadStartHead);
+      c->vol = vol;
+      c->first_key = c->key = c->earliest_key = *key;
       c->vio.op = VIO::READ;
       c->base_stat = cache_read_active_stat;
-      CACHE_INCREMENT_DYN_STAT(c->base_stat + CACHE_STAT_ACTIVE);
-      c->first_key = c->key = c->earliest_key = *key;
-      c->vol = vol;
-      c->frag_type = type;
       c->od = od;
+      c->frag_type = type;
+      CACHE_INCREMENT_DYN_STAT(c->base_stat + CACHE_STAT_ACTIVE);
+      SET_CONTINUATION_HANDLER(c, &CacheVC::openReadStartHead);
     }
     if (!c)
       goto Lmiss;
@@ -92,6 +92,42 @@ Lcallreturn:
 }
 
 #ifdef HTTP_CACHE
+Action*
+Cache::open_read(Continuation* cont, CacheVConnection* vc, HTTPHdr* client_request_hdr)
+{
+  Action* zret = ACTION_RESULT_DONE;
+
+  CacheVC* write_vc = dynamic_cast<CacheVC*>(vc);
+  if (write_vc) {
+    Vol *vol = write_vc->vol;
+    ProxyMutex *mutex = cont->mutex; // needed for stat macros
+    CacheVC *c = new_CacheVC(cont);
+
+    c->vol = write_vc->vol;
+    c->first_key = write_vc->first_key;
+    c->earliest_key = c->key = write_vc->earliest_key;
+    c->vio.op = VIO::READ;
+    c->base_stat = cache_read_active_stat;
+    c->od = write_vc->od;
+    c->frag_type = write_vc->frag_type;
+    CACHE_INCREMENT_DYN_STAT(c->base_stat + CACHE_STAT_ACTIVE);
+//    write_vc->alternate.request_get(&c->request);
+//    client_request_hdr->copy_shallow(&c->request);
+    c->request.copy_shallow(client_request_hdr);
+    c->params = write_vc->params; // seems to be a no-op, always NULL.
+    c->dir = c->first_dir = write_vc->first_dir;
+    c->write_vc = write_vc;
+    c->first_buf = write_vc->first_buf; // I don't think this is effective either.
+    SET_CONTINUATION_HANDLER(c, &CacheVC::openReadFromWriter);
+    zret = &c->_action; // default, override if needed.
+    CACHE_TRY_LOCK(lock, vol->mutex, mutex->thread_holding);
+    if (lock.is_locked() && c->handleEvent(EVENT_IMMEDIATE, 0) == EVENT_DONE) {
+      zret = ACTION_RESULT_DONE;
+    }
+  }
+  return zret;
+}
+
 Action *
 Cache::open_read(Continuation *cont, CacheKey *key, CacheHTTPHdr *request, CacheLookupHttpConfig *params, CacheFragType type,
                  char *hostname, int host_len)
@@ -112,15 +148,15 @@ Cache::open_read(Continuation *cont, CacheKey *key, CacheHTTPHdr *request, Cache
     CACHE_TRY_LOCK(lock, vol->mutex, mutex->thread_holding);
     if (!lock.is_locked() || (od = vol->open_read(key)) || dir_probe(key, vol, &result, &last_collision)) {
       c = new_CacheVC(cont);
-      c->first_key = c->key = c->earliest_key = *key;
       c->vol = vol;
+      c->first_key = c->key = c->earliest_key = *key;
       c->vio.op = VIO::READ;
       c->base_stat = cache_read_active_stat;
+      c->od = od;
+      c->frag_type = CACHE_FRAG_TYPE_HTTP;
       CACHE_INCREMENT_DYN_STAT(c->base_stat + CACHE_STAT_ACTIVE);
       c->request.copy_shallow(request);
-      c->frag_type = CACHE_FRAG_TYPE_HTTP;
       c->params = params;
-      c->od = od;
     }
     if (!lock.is_locked()) {
       SET_CONTINUATION_HANDLER(c, &CacheVC::openReadStartHead);
@@ -166,17 +202,43 @@ uint32_t
 CacheVC::load_http_info(CacheHTTPInfoVector *info, Doc *doc, RefCountObj *block_ptr)
 {
   uint32_t zret = info->get_handles(doc->hdr(), doc->hlen, block_ptr);
-  if (cache_config_compatibility_4_2_0_fixup && // manual override not engaged
-      !this->f.doc_from_ram_cache &&            // it's already been done for ram cache fragments
-      vol->header->version.ink_major == 23 && vol->header->version.ink_minor == 0) {
-    for (int i = info->xcount - 1; i >= 0; --i) {
-      info->data(i).alternate.m_alt->m_response_hdr.m_mime->recompute_accelerators_and_presence_bits();
-      info->data(i).alternate.m_alt->m_request_hdr.m_mime->recompute_accelerators_and_presence_bits();
+  if (zret != static_cast<uint32_t>(-1) && // Make sure we haven't already failed
+      cache_config_compatibility_4_2_0_fixup && // manual override not engaged
+      ! this->f.doc_from_ram_cache && // it's already been done for ram cache fragments
+      vol->header->version.ink_major == 23 && vol->header->version.ink_minor == 0
+    ) {
+    for ( int i = info->xcount - 1 ; i >= 0 ; --i ) {
+      info->data(i)._alternate.m_alt->m_response_hdr.m_mime->recompute_accelerators_and_presence_bits();
+      info->data(i)._alternate.m_alt->m_request_hdr.m_mime->recompute_accelerators_and_presence_bits();
     }
   }
   return zret;
 }
 
+char const*
+CacheVC::get_http_range_boundary_string(int* len) const
+{
+  return resp_range.getBoundaryStr(len);
+}
+
+int64_t
+CacheVC::get_effective_content_size()
+{
+  return this->is_http_partial_content() ? resp_range.calcContentLength() : alternate.object_size_get();
+}
+
+HTTPRangeSpec&
+CacheVC::get_http_range_spec()
+{
+  return resp_range.getRangeSpec();
+}
+
+bool
+CacheVC::is_http_partial_content()
+{
+  return resp_range.hasRanges();
+}
+
 int
 CacheVC::openReadFromWriterFailure(int event, Event *e)
 {
@@ -192,24 +254,13 @@ CacheVC::openReadFromWriterFailure(int event, Event *e)
 int
 CacheVC::openReadChooseWriter(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
 {
-  intptr_t err = ECACHE_DOC_BUSY;
-  CacheVC *w = NULL;
+//  intptr_t err = ECACHE_DOC_BUSY;
+//  CacheVC *w = NULL;
 
   ink_assert(vol->mutex->thread_holding == mutex->thread_holding && write_vc == NULL);
 
-  if (!od)
-    return EVENT_RETURN;
-
   if (frag_type != CACHE_FRAG_TYPE_HTTP) {
-    ink_assert(od->num_writers == 1);
-    w = od->writers.head;
-    if (w->start_time > start_time || w->closed < 0) {
-      od = NULL;
-      return EVENT_RETURN;
-    }
-    if (!w->closed)
-      return -err;
-    write_vc = w;
+    ink_release_assert(! "[amc] Fix reader from writer for non-HTTP");
   }
 #ifdef HTTP_CACHE
   else {
@@ -217,41 +268,8 @@ CacheVC::openReadChooseWriter(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSE
     int write_vec_cnt = write_vector->count();
     for (int c = 0; c < write_vec_cnt; c++)
       vector.insert(write_vector->get(c));
-    // check if all the writers who came before this reader have
-    // set the http_info.
-    for (w = (CacheVC *)od->writers.head; w; w = (CacheVC *)w->opendir_link.next) {
-      if (w->start_time > start_time || w->closed < 0)
-        continue;
-      if (!w->closed && !cache_config_read_while_writer) {
-        return -err;
-      }
-      if (w->alternate_index != CACHE_ALT_INDEX_DEFAULT)
-        continue;
-
-      if (!w->closed && !w->alternate.valid()) {
-        od = NULL;
-        ink_assert(!write_vc);
-        vector.clear(false);
-        return EVENT_CONT;
-      }
-      // construct the vector from the writers.
-      int alt_ndx = CACHE_ALT_INDEX_DEFAULT;
-      if (w->f.update) {
-        // all Update cases. Need to get the alternate index.
-        alt_ndx = get_alternate_index(&vector, w->update_key);
-        // if its an alternate delete
-        if (!w->alternate.valid()) {
-          if (alt_ndx >= 0)
-            vector.remove(alt_ndx, false);
-          continue;
-        }
-      }
-      ink_assert(w->alternate.valid());
-      if (w->alternate.valid())
-        vector.insert(&w->alternate, alt_ndx);
-    }
-
     if (!vector.count()) {
+      // [amc] Not sure of the utility of this now
       if (od->reading_vec) {
         // the writer(s) are reading the vector, so there is probably
         // an old vector. Since this reader came before any of the
@@ -267,22 +285,11 @@ CacheVC::openReadChooseWriter(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSE
         return -ECACHE_ALT_MISS;
     } else
       alternate_index = 0;
-    CacheHTTPInfo *obj = vector.get(alternate_index);
-    for (w = (CacheVC *)od->writers.head; w; w = (CacheVC *)w->opendir_link.next) {
-      if (obj->m_alt == w->alternate.m_alt) {
-        write_vc = w;
-        break;
-      }
-    }
     vector.clear(false);
-    if (!write_vc) {
-      DDebug("cache_read_agg", "%p: key: %X writer alternate different: %d", this, first_key.slice32(1), alternate_index);
-      od = NULL;
-      return EVENT_RETURN;
-    }
-
-    DDebug("cache_read_agg", "%p: key: %X eKey: %d # alts: %d, ndx: %d, # writers: %d writer: %p", this, first_key.slice32(1),
-           write_vc->earliest_key.slice32(1), vector.count(), alternate_index, od->num_writers, write_vc);
+    DDebug("cache_read_agg",
+          "%p: key: %X eKey: %d # alts: %d, ndx: %d, # writers: %d writer: %p",
+          this, first_key.slice32(1), write_vc->earliest_key.slice32(1),
+          vector.count(), alternate_index, od->num_writers, write_vc);
   }
 #endif // HTTP_CACHE
   return EVENT_NONE;
@@ -324,14 +331,36 @@ CacheVC::openReadFromWriter(int event, Event *e)
     return openReadStartHead(event, e);
   } else
     ink_assert(od == vol->open_read(&first_key));
-  if (!write_vc) {
+
+  CACHE_TRY_LOCK(lock_od, od->mutex, mutex->thread_holding);
+  if (!lock_od.is_locked())
+    VC_SCHED_LOCK_RETRY();
+
+  if (od->open_writer) {
+    // Can't pick a writer yet, so wait for it.
+    if (!od->open_waiting.in(this)) {
+      wake_up_thread = mutex->thread_holding;
+      od->open_waiting.push(this);
+    }
+    Debug("amc", "[CacheVC::openReadFromWriter] waiting for %p", od->open_writer);
+    return EVENT_CONT; // wait for the writer to wake us up.
+  }
+
+  MUTEX_RELEASE(lock); // we have the OD lock now, don't need the vol lock.
+
+  if (write_vc && CACHE_ALT_INDEX_DEFAULT != (alternate_index = get_alternate_index(&(od->vector), earliest_key))) {
+    alternate.copy_shallow(od->vector.get(alternate_index));
+    MUTEX_RELEASE(lock_od);
+    SET_HANDLER(&CacheVC::openReadStartEarliest);
+    return openReadStartEarliest(event, e);
+  } else {
     int ret = openReadChooseWriter(event, e);
     if (ret < 0) {
-      MUTEX_RELEASE(lock);
+      MUTEX_RELEASE(lock_od);
       SET_HANDLER(&CacheVC::openReadFromWriterFailure);
       return openReadFromWriterFailure(CACHE_EVENT_OPEN_READ_FAILED, reinterpret_cast<Event *>(ret));
     } else if (ret == EVENT_RETURN) {
-      MUTEX_RELEASE(lock);
+      MUTEX_RELEASE(lock_od);
       SET_HANDLER(&CacheVC::openReadStartHead);
       return openReadStartHead(event, e);
     } else if (ret == EVENT_CONT) {
@@ -339,15 +368,6 @@ CacheVC::openReadFromWriter(int event, Event *e)
       VC_SCHED_WRITER_RETRY();
     } else
       ink_assert(write_vc);
-  } else {
-    if (writer_done()) {
-      MUTEX_RELEASE(lock);
-      DDebug("cache_read_agg", "%p: key: %X writer %p has left, continuing as normal read", this, first_key.slice32(1), write_vc);
-      od = NULL;
-      write_vc = NULL;
-      SET_HANDLER(&CacheVC::openReadStartHead);
-      return openReadStartHead(event, e);
-    }
   }
 #ifdef HTTP_CACHE
   OpenDirEntry *cod = od;
@@ -355,7 +375,7 @@ CacheVC::openReadFromWriter(int event, Event *e)
   od = NULL;
   // someone is currently writing the document
   if (write_vc->closed < 0) {
-    MUTEX_RELEASE(lock);
+    MUTEX_RELEASE(lock_od);
     write_vc = NULL;
     // writer aborted, continue as if there is no writer
     SET_HANDLER(&CacheVC::openReadStartHead);
@@ -365,8 +385,8 @@ CacheVC::openReadFromWriter(int event, Event *e)
   ink_assert(frag_type == CACHE_FRAG_TYPE_HTTP || write_vc->closed);
   if (!write_vc->closed && !write_vc->fragment) {
     if (!cache_config_read_while_writer || frag_type != CACHE_FRAG_TYPE_HTTP) {
-      MUTEX_RELEASE(lock);
-      return openReadFromWriterFailure(CACHE_EVENT_OPEN_READ_FAILED, (Event *)-err);
+      MUTEX_RELEASE(lock_od);
+      return openReadFromWriterFailure(CACHE_EVENT_OPEN_READ_FAILED, (Event *) - err);
     }
     DDebug("cache_read_agg", "%p: key: %X writer: closed:%d, fragment:%d, retry: %d", this, first_key.slice32(1), write_vc->closed,
            write_vc->fragment, writer_lock_retry);
@@ -592,6 +612,8 @@ CacheVC::openReadReadDone(int event, Event *e)
         goto Lcallreturn;
       return EVENT_CONT;
     } else if (write_vc) {
+      ink_release_assert(! "[amc] Handle this");
+# if 0
       if (writer_done()) {
         last_collision = NULL;
         while (dir_probe(&earliest_key, vol, &dir, &last_collision)) {
@@ -610,6 +632,7 @@ CacheVC::openReadReadDone(int event, Event *e)
       }
       DDebug("cache_read_agg", "%p: key: %X ReadRead retrying: %d", this, first_key.slice32(1), (int)vio.ndone);
       VC_SCHED_WRITER_RETRY(); // wait for writer
+# endif
     }
     // fall through for truncated documents
   }
@@ -617,126 +640,164 @@ Lerror:
   char tmpstring[100];
   Warning("Document %s truncated", earliest_key.toHexStr(tmpstring));
   return calluser(VC_EVENT_ERROR);
-Ldone:
+//Ldone:
   return calluser(VC_EVENT_EOS);
 Lcallreturn:
   return handleEvent(AIO_EVENT_DONE, 0);
 LreadMain:
-  fragment++;
+  ++fragment;
   doc_pos = doc->prefix_len();
+  doc_pos += resp_range.getOffset() - frag_upper_bound; // used before update!
+  frag_upper_bound += doc->data_len();
   next_CacheKey(&key, &key);
   SET_HANDLER(&CacheVC::openReadMain);
   return openReadMain(event, e);
 }
 
-int
-CacheVC::openReadMain(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
+void
+CacheVC::update_key_to_frag_idx(int target)
 {
-  cancel_trigger();
-  Doc *doc = (Doc *)buf->data();
-  int64_t ntodo = vio.ntodo();
-  int64_t bytes = doc->len - doc_pos;
-  IOBufferBlock *b = NULL;
-  if (seek_to) { // handle do_io_pread
-    if (seek_to >= doc_len) {
-      vio.ndone = doc_len;
-      return calluser(VC_EVENT_EOS);
-    }
-#ifdef HTTP_CACHE
-    HTTPInfo::FragOffset *frags = alternate.get_frag_table();
-    if (is_debug_tag_set("cache_seek")) {
-      char b[33], c[33];
-      Debug("cache_seek", "Seek @ %" PRId64 " in %s from #%d @ %" PRId64 "/%d:%s", seek_to, first_key.toHexStr(b), fragment,
-            doc_pos, doc->len, doc->key.toHexStr(c));
-    }
-    /* Because single fragment objects can migrate to hang off an alt vector
-       they can appear to the VC as multi-fragment when they are not really.
-       The essential difference is the existence of a fragment table.
-    */
-    if (frags) {
-      int target = 0;
-      HTTPInfo::FragOffset next_off = frags[target];
-      int lfi = static_cast<int>(alternate.get_frag_offset_count()) - 1;
-      ink_assert(lfi >= 0); // because it's not a single frag doc.
-
-      /* Note: frag[i].offset is the offset of the first byte past the
-         i'th fragment. So frag[0].offset is the offset of the first
-         byte of fragment 1. In addition the # of fragments is one
-         more than the fragment table length, the start of the last
-         fragment being the last offset in the table.
-      */
-      if (fragment == 0 || seek_to < frags[fragment - 1] || (fragment <= lfi && frags[fragment] <= seek_to)) {
-        // search from frag 0 on to find the proper frag
-        while (seek_to >= next_off && target < lfi) {
-          next_off = frags[++target];
-        }
-        if (target == lfi && seek_to >= next_off)
-          ++target;
-      } else { // shortcut if we are in the fragment already
-        target = fragment;
-      }
-      if (target != fragment) {
-        // Lread will read the next fragment always, so if that
-        // is the one we want, we don't need to do anything
-        int cfi = fragment;
-        --target;
-        while (target > fragment) {
-          next_CacheKey(&key, &key);
-          ++fragment;
-        }
+  FragmentDescriptorTable& frags = (*alternate.get_frag_table());
+
+  if (0 == target) {
+    fragment = 0;
+    key = earliest_key;
+  } else if (! frags[target].m_key.is_zero()) {
+    key = frags[target].m_key;
+  } else { // step through the hard way
+    if (target < fragment) { // going backwards
+      if (target < (fragment - target)) { // faster to go from start
+        fragment = 0;
+        key = earliest_key;
+      } else { // quicker to go from here to there
         while (target < fragment) {
           prev_CacheKey(&key, &key);
           --fragment;
+          if (frags[fragment].m_key.is_zero())
+            frags[fragment].m_key = key; // might as well store it as we go.
+          else ink_assert(frags[fragment].m_key == key);
         }
-
-        if (is_debug_tag_set("cache_seek")) {
-          char target_key_str[33];
-          key.toHexStr(target_key_str);
-          Debug("cache_seek", "Seek #%d @ %" PRId64 " -> #%d @ %" PRId64 ":%s", cfi, doc_pos, target, seek_to, target_key_str);
-        }
-        goto Lread;
       }
     }
-    doc_pos = doc->prefix_len() + seek_to;
-    if (fragment)
-      doc_pos -= static_cast<int64_t>(frags[fragment - 1]);
-    vio.ndone = 0;
-    seek_to = 0;
-    ntodo = vio.ntodo();
-    bytes = doc->len - doc_pos;
-    if (is_debug_tag_set("cache_seek")) {
-      char target_key_str[33];
-      key.toHexStr(target_key_str);
-      Debug("cache_seek", "Read # %d @ %" PRId64 "/%d for %" PRId64, fragment, doc_pos, doc->len, bytes);
+    // advance to target if we're not already there.
+    while (target > fragment) {
+      next_CacheKey(&key, &key);
+      ++fragment;
+      if (frags[fragment].m_key.is_zero())
+        frags[fragment].m_key = key; // might as well store it as we go.
+      else ink_assert(frags[fragment].m_key == key);
     }
-#endif
   }
-  if (ntodo <= 0)
-    return EVENT_CONT;
-  if (vio.buffer.writer()->max_read_avail() > vio.buffer.writer()->water_mark && vio.ndone) // initiate read of first block
+}
+
+int
+CacheVC::frag_idx_for_offset(uint64_t offset)
+{
+  FragmentDescriptorTable* frags = alternate.get_frag_table();
+  int count = alternate.get_frag_count();
+  uint32_t ffs = alternate.get_frag_fixed_size();
+  int idx = count / 2;
+
+  ink_assert(offset < doc_len);
+
+  if (ffs) idx = offset / ffs; // good guess as to the right offset.
+
+  if (count > 1 && 0 == (*frags)[1].m_offset) ++idx;
+
+  do {
+    uint64_t upper = idx >= count ? doc_len : (*frags)[idx+1].m_offset;
+    uint64_t lower = idx <= 0 ? 0 : (*frags)[idx].m_offset;
+    if (offset < lower) idx = idx / 2;
+    else if (offset >= upper) idx = (count + idx + 1)/2;
+    else break;
+  } while (true);
+  return idx;
+}
+
+/* There is a fragment available, decide what do to next.
+ */
+int
+CacheVC::openReadMain(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
+{
+  cancel_trigger();
+  Doc *doc = (Doc *) buf->data();
+  int64_t bytes = vio.ntodo();
+  IOBufferBlock *b = NULL;
+  uint64_t target_offset = resp_range.getOffset();
+  uint64_t lower_bound = frag_upper_bound - doc->data_len();
+
+  if (bytes <= 0)
     return EVENT_CONT;
-  if ((bytes <= 0) && vio.ntodo() >= 0)
-    goto Lread;
-  if (bytes > vio.ntodo())
-    bytes = vio.ntodo();
-  b = new_IOBufferBlock(buf, bytes, doc_pos);
-  b->_buf_end = b->_end;
-  vio.buffer.writer()->append_block(b);
-  vio.ndone += bytes;
-  doc_pos += bytes;
-  if (vio.ntodo() <= 0)
-    return calluser(VC_EVENT_READ_COMPLETE);
-  else {
-    if (calluser(VC_EVENT_READ_READY) == EVENT_DONE)
+
+  // Start shipping
+  while (bytes > 0 && lower_bound <= target_offset && target_offset < frag_upper_bound) {
+    if (vio.buffer.writer()->max_read_avail() > vio.buffer.writer()->water_mark && vio.ndone) // wait for reader
+      return EVENT_CONT;
+
+    if (resp_range.hasPendingRangeShift()) { // in new range, shift to start location.
+      int b_len;
+      char const* b_str = resp_range.getBoundaryStr(&b_len);
+      size_t r_idx = resp_range.getIdx();
+
+      doc_pos = doc->prefix_len() + (target_offset - lower_bound);
+      
+      vio.ndone += HTTPRangeSpec::writePartBoundary(vio.buffer.writer(), b_str, b_len
+                                                    , doc_len, resp_range[r_idx]._min, resp_range[r_idx]._max
+                                                    , resp_range.getContentTypeField(), r_idx >= (resp_range.count() - 1)
+        );
+      resp_range.consumeRangeShift();
+      Debug("amc", "Range boundary for range %" PRIu64, r_idx);
+    }
+
+    bytes = std::min(doc->len - doc_pos, static_cast<int64_t>(resp_range.getRemnantSize()));
+    bytes = std::min(bytes, vio.ntodo());
+    if (bytes > 0) {
+      b = new_IOBufferBlock(buf, bytes, doc_pos);
+      b->_buf_end = b->_end;
+      vio.buffer.writer()->append_block(b);
+      vio.ndone += bytes;
+      doc_pos += bytes;
+      resp_range.consume(bytes);
+      Debug("amc", "shipped %" PRId64 " bytes at target offset %" PRIu64, bytes, target_offset);
+      target_offset = resp_range.getOffset();
+    }
+
+    if (vio.ntodo() <= 0)
+      return calluser(VC_EVENT_READ_COMPLETE);
+    else if (calluser(VC_EVENT_READ_READY) == EVENT_DONE)
       return EVENT_DONE;
-    // we have to keep reading until we give the user all the
-    // bytes it wanted or we hit the watermark.
-    if (vio.ntodo() > 0 && !vio.buffer.writer()->high_water())
-      goto Lread;
-    return EVENT_CONT;
   }
-Lread : {
-  if (vio.ndone >= (int64_t)doc_len)
+
+
+#ifdef HTTP_CACHE
+  if (resp_range.getRemnantSize()) {
+    FragmentDescriptorTable* frags = alternate.get_frag_table();
+    int n_frags = alternate.get_frag_count();
+
+    // Quick check for offset in next fragment - very common
+    if (target_offset >= frag_upper_bound && (!frags || fragment > n_frags || target_offset < (*frags)[fragment].m_offset)) {
+      Debug("amc", "Non-seeking continuation to next fragment");
+    } else {
+      int target = -1; // target fragment index.
+
+      if (is_debug_tag_set("amc")) {
+        char b[33], c[33];
+        Debug("amc", "Seek @ %" PRIu64 " [r#=%d] in %s from #%d @ %" PRIu64 "/%d/%" PRId64 ":%s%s",
+              target_offset, resp_range.getIdx(), first_key.toHexStr(b), fragment, frag_upper_bound, doc->len, doc->total_len, doc->key.toHexStr(c)
+              , (frags ? "" : "no frag table")
+          );
+      }
+
+      target = this->frag_idx_for_offset(target_offset);
+      this->update_key_to_frag_idx(target);
+      /// one frag short, because it gets bumped when the fragment is actually read.
+      frag_upper_bound = target > 0 ? (*frags)[target].m_offset : 0;
+      Debug("amc", "Fragment seek from %d to %d target offset %" PRIu64, fragment - 1, target, target_offset);
+    }
+  }
+#endif
+
+  if (vio.ntodo() > 0 && 0 == resp_range.getRemnantSize())
     // reached the end of the document and the user still wants more
     return calluser(VC_EVENT_EOS);
   last_collision = 0;
@@ -754,41 +815,84 @@ Lread : {
   if (dir_probe(&key, vol, &dir, &last_collision)) {
     SET_HANDLER(&CacheVC::openReadReadDone);
     int ret = do_read_call(&key);
-    if (ret == EVENT_RETURN)
-      goto Lcallreturn;
+    if (ret == EVENT_RETURN) {
+      lock.release();
+      return handleEvent(AIO_EVENT_DONE, 0);
+    }
     return EVENT_CONT;
   } else if (write_vc) {
+    ink_release_assert(! "[amc] Handle case where fragment is not in the directory during read");
+    /* Check for an OpenDirEntry - if none, no hope. If so, see if any of the writers in it are still
+       planning to write this fragment (might check against all fragments - if any aren't going to
+       be written, might just give up at that point rather than hope some other user agent will happen
+       to ask for the missing fragments. Although, if there's a herd, that might not be so far fetched).
+    */
+# if 0
     if (writer_done()) {
       last_collision = NULL;
       while (dir_probe(&earliest_key, vol, &dir, &last_collision)) {
         if (dir_offset(&dir) == dir_offset(&earliest_dir)) {
-          DDebug("cache_read_agg", "%p: key: %X ReadMain complete: %d", this, first_key.slice32(1), (int)vio.ndone);
+          DDebug("cache_read_agg", "%p: key: %X ReadMain complete: %d",
+                 this, first_key.slice32(1), (int)vio.ndone);
           doc_len = vio.ndone;
           goto Leos;
         }
       }
-      DDebug("cache_read_agg", "%p: key: %X ReadMain writer aborted: %d", this, first_key.slice32(1), (int)vio.ndone);
+      DDebug("cache_read_agg", "%p: key: %X ReadMain writer aborted: %d",
+             this, first_key.slice32(1), (int)vio.ndone);
       goto Lerror;
     }
     DDebug("cache_read_agg", "%p: key: %X ReadMain retrying: %d", this, first_key.slice32(1), (int)vio.ndone);
     SET_HANDLER(&CacheVC::openReadMain);
     VC_SCHED_WRITER_RETRY();
+# endif
   }
   if (is_action_tag_set("cache"))
     ink_release_assert(false);
-  Warning("Document %X truncated at %d of %d, missing fragment %X", first_key.slice32(1), (int)vio.ndone, (int)doc_len,
-          key.slice32(1));
+  Warning("Document %X truncated at %d of %d, missing fragment %X", first_key.slice32(1), (int)vio.ndone, (int)doc_len, key.slice32(1));
   // remove the directory entry
   dir_delete(&earliest_key, vol, &earliest_dir);
-}
-Lerror:
+  lock.release();
+//Lerror:
   return calluser(VC_EVENT_ERROR);
-Leos:
+//Leos:
   return calluser(VC_EVENT_EOS);
-Lcallreturn:
-  return handleEvent(AIO_EVENT_DONE, 0);
 }
 
+int
+CacheVC::openReadWaitEarliest(int evid, Event*)
+{
+  int zret = EVENT_CONT;
+  cancel_trigger();
+
+  CACHE_TRY_LOCK(lock, vol->mutex, mutex->thread_holding);
+  if (!lock.is_locked())
+    VC_SCHED_LOCK_RETRY();
+  Debug("amc", "[CacheVC::openReadWaitEarliest] [%d]", evid);
+  if (NULL == vol->open_read(&first_key)) {
+    // Writer is gone, so no more data for which to wait.
+    // Best option is to just start over from the first frag.
+    // Most likely scenario - object turned out to be a resident alternate so
+    // there's no explicit earliest frag.
+    lock.release();
+    SET_HANDLER(&self::openReadStartHead);
+    od = NULL;
+    key = first_key;
+    return handleEvent(EVENT_IMMEDIATE, 0);
+  } else if (dir_probe(&key, vol, &earliest_dir, &last_collision) ||
+      dir_lookaside_probe(&key, vol, &earliest_dir, NULL))
+  {
+    dir = earliest_dir;
+    SET_HANDLER(&self::openReadStartEarliest);
+    if ((zret = do_read_call(&key)) == EVENT_RETURN) {
+      lock.release();
+      return handleEvent(AIO_EVENT_DONE, 0);
+    }
+  }
+  return zret;
+}
+  
+
 /*
   This code follows CacheVC::openReadStartHead closely,
   if you change this you might have to change that.
@@ -843,6 +947,8 @@ CacheVC::openReadStartEarliest(int /* event ATS_UNUSED */, Event * /* e ATS_UNUS
     earliest_key = key;
     doc_pos = doc->prefix_len();
     next_CacheKey(&key, &doc->key);
+    fragment = 1;
+    frag_upper_bound = doc->data_len();
     vol->begin_read(this);
     if (vol->within_hit_evacuate_window(&earliest_dir) &&
         (!cache_config_hit_evacuate_size_limit || doc_len <= (uint64_t)cache_config_hit_evacuate_size_limit)
@@ -859,7 +965,7 @@ CacheVC::openReadStartEarliest(int /* event ATS_UNUSED */, Event * /* e ATS_UNUS
     if (dir_probe(&key, vol, &earliest_dir, &last_collision) || dir_lookaside_probe(&key, vol, &earliest_dir, NULL)) {
       dir = earliest_dir;
 #if TS_USE_INTERIM_CACHE == 1
-      if (dir_ininterim(&dir) && alternate.get_frag_offset_count() > 1) {
+      if (dir_ininterim(&dir) && alternate.get_frag_count() > 1) {
         dir_delete(&key, vol, &dir);
         last_collision = NULL;
         goto Lread;
@@ -872,7 +978,19 @@ CacheVC::openReadStartEarliest(int /* event ATS_UNUSED */, Event * /* e ATS_UNUS
 // read has detected that alternate does not exist in the cache.
 // rewrite the vector.
 #ifdef HTTP_CACHE
-    if (!f.read_from_writer_called && frag_type == CACHE_FRAG_TYPE_HTTP) {
+    // It's OK if there's a writer for this alternate, we can wait on it.
+    if (od && od->has_writer(earliest_key)) {
+      wake_up_thread = mutex->thread_holding;
+      od->waiting_for(earliest_key, this, 0);
+      lock.release();
+      // The SM must be signaled that the cache read is open even if we haven't got the earliest frag
+      // yet because otherwise it won't set up the read side of the tunnel before the write side finishes
+      // and terminates the SM (in the case of a resident alternate). But the VC can't be left with this
+      // handler or it will confuse itself when it wakes up from the earliest frag read. So we put it
+      // in a special wait state / handler and then signal the SM.
+      SET_HANDLER(&self::openReadWaitEarliest);
+      return callcont(CACHE_EVENT_OPEN_READ); // must signal read is open
+    } else if (frag_type == CACHE_FRAG_TYPE_HTTP) {
       // don't want any writers while we are evacuating the vector
       if (!vol->open_write(this, false, 1)) {
         Doc *doc1 = (Doc *)first_buf->data();
@@ -1000,6 +1118,10 @@ Lrestart:
 /*
   This code follows CacheVC::openReadStartEarliest closely,
   if you change this you might have to change that.
+
+  This handles the I/O completion of reading the first doc of the object.
+  If there are alternates, we chain to openreadStartEarliest to read the
+  earliest doc.
 */
 int
 CacheVC::openReadStartHead(int event, Event *e)
@@ -1097,13 +1219,16 @@ CacheVC::openReadStartHead(int event, Event *e)
         err = ECACHE_BAD_META_DATA;
         goto Ldone;
       }
-      if (cache_config_select_alternate) {
+      // If @a params is @c NULL then we're a retry from a range request pair so don't do alt select.
+      // Instead try the @a earliest_key - if that's a match then that's the correct alt, written
+      // by the paired write VC.
+      if (cache_config_select_alternate && params) {
         alternate_index = HttpTransactCache::SelectFromAlternates(&vector, &request, params);
         if (alternate_index < 0) {
           err = ECACHE_ALT_MISS;
           goto Ldone;
         }
-      } else
+      } else if (CACHE_ALT_INDEX_DEFAULT == (alternate_index = get_alternate_index(&vector, earliest_key)))
         alternate_index = 0;
       alternate_tmp = vector.get(alternate_index);
       if (!alternate_tmp->valid()) {
@@ -1117,12 +1242,30 @@ CacheVC::openReadStartHead(int event, Event *e)
       alternate.copy_shallow(alternate_tmp);
       alternate.object_key_get(&key);
       doc_len = alternate.object_size_get();
-      if (key == doc->key) { // is this my data?
+
+      // Handle any range related setup.
+      if (!resp_range.init(&request)) { // shouldn't this have been checked earlier?
+        err = ECACHE_INVALID_RANGE;
+        goto Ldone;
+      }
+      // If the object length is known we can check the range.
+      // Otherwise we have to leave it vague and talk to the origin to get full length info.
+      if (alternate.m_alt->m_flag.content_length_p && !resp_range.apply(doc_len)) {
+        err = ECACHE_UNSATISFIABLE_RANGE;
+        goto Ldone;
+      }
+      if (resp_range.isMulti())
+        resp_range.setContentTypeFromResponse(alternate.response_get()).generateBoundaryStr(earliest_key);
+//      alternate.get_uncached(resp_range.getRangeSpec(), uncached_range.getRangeSpec());
+
+      if (key == doc->key) {      // is this my data?
         f.single_fragment = doc->single_fragment();
         ink_assert(f.single_fragment); // otherwise need to read earliest
         ink_assert(doc->hlen);
         doc_pos = doc->prefix_len();
         next_CacheKey(&key, &doc->key);
+        fragment = 1;
+        frag_upper_bound = doc->data_len();
       } else {
         f.single_fragment = false;
       }
@@ -1130,6 +1273,8 @@ CacheVC::openReadStartHead(int event, Event *e)
 #endif
     {
       next_CacheKey(&key, &doc->key);
+      fragment = 1;
+      frag_upper_bound = doc->data_len();
       f.single_fragment = doc->single_fragment();
       doc_pos = doc->prefix_len();
       doc_len = doc->total_len;
@@ -1140,7 +1285,7 @@ CacheVC::openReadStartHead(int event, Event *e)
       Debug("cache_read", "CacheReadStartHead - read %s target %s - %s %d of %" PRId64 " bytes, %d fragments",
             doc->key.toHexStr(xt), key.toHexStr(yt), f.single_fragment ? "single" : "multi", doc->len, doc->total_len,
 #ifdef HTTP_CACHE
-            alternate.get_frag_offset_count()
+            alternate.get_frag_count()
 #else
             0
 #endif

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/CacheWrite.cc
----------------------------------------------------------------------
diff --git a/iocore/cache/CacheWrite.cc b/iocore/cache/CacheWrite.cc
index 4e6319e..18db8c1 100644
--- a/iocore/cache/CacheWrite.cc
+++ b/iocore/cache/CacheWrite.cc
@@ -33,13 +33,18 @@
 // used to get the alternate which is actually present in the document
 #ifdef HTTP_CACHE
 int
-get_alternate_index(CacheHTTPInfoVector *cache_vector, CacheKey key)
+get_alternate_index(CacheHTTPInfoVector *cache_vector, CacheKey key, int idx)
 {
   int alt_count = cache_vector->count();
   CacheHTTPInfo *obj;
   if (!alt_count)
     return -1;
+  // See if the hint is correct.
+  if (0 <= idx && idx < alt_count && cache_vector->get(idx)->compare_object_key(&key))
+    return idx;
+  // Otherwise scan the vector.
   for (int i = 0; i < alt_count; i++) {
+    if (i == idx) continue; // already checked that one.
     obj = cache_vector->get(i);
     if (obj->compare_object_key(&key)) {
       // Debug("cache_key", "Resident alternate key  %X", key.slice32(0));
@@ -63,20 +68,21 @@ CacheVC::updateVector(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
     VC_SCHED_LOCK_RETRY();
   int ret = 0;
   {
-    CACHE_TRY_LOCK(lock, vol->mutex, mutex->thread_holding);
+    CACHE_TRY_LOCK(lock, od->mutex, mutex->thread_holding);
     if (!lock.is_locked() || od->writing_vec)
       VC_SCHED_LOCK_RETRY();
 
     int vec = alternate.valid();
     if (f.update) {
       // all Update cases. Need to get the alternate index.
-      alternate_index = get_alternate_index(write_vector, update_key);
-      Debug("cache_update", "updating alternate index %d frags %d", alternate_index,
-            alternate_index >= 0 ? write_vector->get(alternate_index)->get_frag_offset_count() : -1);
+      alternate_index = get_alternate_index(write_vector, update_key, alternate_index);
+      Debug("cache_update", "updating alternate index %d frags %d", alternate_index, alternate_index >=0 ? write_vector->get(alternate_index)->get_frag_count() : -1);
       // if its an alternate delete
       if (!vec) {
         ink_assert(!total_len);
         if (alternate_index >= 0) {
+          MUTEX_TRY_LOCK(stripe_lock, vol->mutex, mutex->thread_holding);
+          if (!stripe_lock.is_locked()) VC_SCHED_LOCK_RETRY();
           write_vector->remove(alternate_index, true);
           alternate_index = CACHE_ALT_REMOVED;
           if (!write_vector->count())
@@ -98,12 +104,6 @@ CacheVC::updateVector(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
       write_vector->remove(0, true);
     }
     if (vec) {
-      /* preserve fragment offset data from old info. This method is
-         called iff the update is a header only update so the fragment
-         data should remain valid.
-      */
-      if (alternate_index >= 0)
-        alternate.copy_frag_offsets_from(write_vector->get(alternate_index));
       alternate_index = write_vector->insert(&alternate, alternate_index);
     }
 
@@ -134,7 +134,7 @@ CacheVC::updateVector(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
     od->writing_vec = 1;
     f.use_first_key = 1;
     SET_HANDLER(&CacheVC::openWriteCloseHeadDone);
-    ret = do_write_call();
+    ret = do_write_lock_call();
   }
   if (ret == EVENT_RETURN)
     return handleEvent(AIO_EVENT_DONE, 0);
@@ -161,7 +161,7 @@ CacheVC::updateVector(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
      (f.use_fist_key || f.evac_vector) is set. Write_vector is written to disk
    - alternate_index. Used only if write_vector needs to be written to disk.
      Used to find out the VC's alternate in the write_vector and set its
-     length to tatal_len.
+     length to total_len.
    - write_len. The number of bytes for this fragment.
    - total_len. The total number of bytes for the document so far.
      Doc->total_len and alternate's total len is set to this value.
@@ -316,8 +316,8 @@ Vol::aggWriteDone(int event, Event *e)
     header->last_write_pos = header->write_pos;
     header->write_pos += io.aiocb.aio_nbytes;
     ink_assert(header->write_pos >= start);
-    DDebug("cache_agg", "Dir %s, Write: %" PRIu64 ", last Write: %" PRIu64 "\n", hash_text.get(), header->write_pos,
-           header->last_write_pos);
+    Debug("cache_agg", "Dir %s, Write: %" PRIu64 ", last Write: %" PRIu64 "\n",
+          hash_text.get(), header->write_pos, header->last_write_pos);
     ink_assert(header->write_pos == header->agg_pos);
     if (header->write_pos + EVACUATION_SIZE > scan_pos)
       periodic_scan();
@@ -726,7 +726,7 @@ agg_copy(char *p, CacheVC *vc)
     IOBufferBlock *res_alt_blk = 0;
 
     uint32_t len = vc->write_len + vc->header_len + vc->frag_len + sizeofDoc;
-    ink_assert(vc->frag_type != CACHE_FRAG_TYPE_HTTP || len != sizeofDoc);
+    ink_assert(vc->frag_type != CACHE_FRAG_TYPE_HTTP || len != sizeofDoc || 0 == vc->fragment);
     ink_assert(vol->round_to_approx_size(len) == vc->agg_len);
     // update copy of directory entry for this document
     dir_set_approx_size(&vc->dir, vc->agg_len);
@@ -788,16 +788,21 @@ agg_copy(char *p, CacheVC *vc)
 #ifdef HTTP_CACHE
       if (vc->frag_type == CACHE_FRAG_TYPE_HTTP) {
         ink_assert(vc->write_vector->count() > 0);
-        if (!vc->f.update && !vc->f.evac_vector) {
-          ink_assert(!(vc->first_key == zero_key));
-          CacheHTTPInfo *http_info = vc->write_vector->get(vc->alternate_index);
-          http_info->object_size_set(vc->total_len);
-        }
-        // update + data_written =>  Update case (b)
-        // need to change the old alternate's object length
-        if (vc->f.update && vc->total_len) {
-          CacheHTTPInfo *http_info = vc->write_vector->get(vc->alternate_index);
-          http_info->object_size_set(vc->total_len);
+        if (vc->resp_range.hasRanges()) {
+          int64_t size = vc->alternate.object_size_get();
+          if (size >= 0) doc->total_len = size;
+        } else {
+          if (!vc->f.update && !vc->f.evac_vector) {
+            ink_assert(!(vc->first_key == zero_key));
+            CacheHTTPInfo *http_info = vc->write_vector->get(vc->alternate_index);
+            http_info->object_size_set(vc->total_len);
+          }
+          // update + data_written =>  Update case (b)
+          // need to change the old alternate's object length
+          if (vc->f.update && vc->total_len) {
+            CacheHTTPInfo *http_info = vc->write_vector->get(vc->alternate_index);
+            http_info->object_size_set(vc->total_len);
+          }
         }
         ink_assert(!(((uintptr_t)&doc->hdr()[0]) & HDR_PTR_ALIGNMENT_MASK));
         ink_assert(vc->header_len == vc->write_vector->marshal(doc->hdr(), vc->header_len));
@@ -1068,7 +1073,36 @@ Lwait:
 }
 
 int
-CacheVC::openWriteCloseDir(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
+CacheVC::openWriteEmptyEarliestDone(int event, Event *e)
+{
+  cancel_trigger();
+  if (event == AIO_EVENT_DONE)
+    set_io_not_in_progress();
+  else if (is_io_in_progress())
+    return EVENT_CONT;
+
+  {
+    MUTEX_LOCK(lock, od->mutex, this_ethread());
+    alternate_index = get_alternate_index(write_vector, this->earliest_key);
+    od->write_complete(key, this, io.ok()); // in any case, the IO is over.
+    key = od->key_for(earliest_key, write_pos);
+  }
+
+  SET_HANDLER(&CacheVC::openWriteMain);
+
+  // on error terminate if we're already closed, otherwise notify external continuation.
+  if (!io.ok()) {
+    if (closed) {
+      closed = -1;
+      return die();
+    }
+    return calluser(VC_EVENT_ERROR);
+  }
+  return this->openWriteMain(event, e); // go back to writing our actual data.
+}
+
+int
+CacheVC::openWriteCloseDir(int /* event ATS_UNUSED */, Event */* e ATS_UNUSED */)
 {
   cancel_trigger();
   {
@@ -1078,6 +1112,7 @@ CacheVC::openWriteCloseDir(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED *
       ink_assert(!is_io_in_progress());
       VC_SCHED_LOCK_RETRY();
     }
+    od->close_writer(earliest_key, this);
     vol->close_write(this);
     if (closed < 0 && fragment)
       dir_delete(&earliest_key, vol, &earliest_dir);
@@ -1179,13 +1214,16 @@ CacheVC::openWriteCloseHead(int event, Event *e)
   cancel_trigger();
   f.use_first_key = 1;
   if (io.ok())
-    ink_assert(fragment || (length == (int64_t)total_len));
+    ink_assert(fragment || (length == (int64_t)total_len) || (resp_range.hasRanges() && alternate.object_size_get() > alternate.get_frag_fixed_size()));
   else
     return openWriteCloseDir(event, e);
-  if (f.data_done)
+  if (f.data_done) {
     write_len = 0;
-  else
+  } else {
     write_len = length;
+    // If we're writing data in the first / header doc, then it's a resident alt.
+    alternate.m_alt->m_flag.complete_p = true;
+  }
 #ifdef HTTP_CACHE
   if (frag_type == CACHE_FRAG_TYPE_HTTP) {
     SET_HANDLER(&CacheVC::updateVector);
@@ -1216,35 +1254,33 @@ CacheVC::openWriteCloseDataDone(int event, Event *e)
     CACHE_TRY_LOCK(lock, vol->mutex, this_ethread());
     if (!lock.is_locked())
       VC_LOCK_RETRY_EVENT();
-    if (!fragment) {
-      ink_assert(key == earliest_key);
-      earliest_dir = dir;
-#ifdef HTTP_CACHE
-    } else {
-      // Store the offset only if there is a table.
-      // Currently there is no alt (and thence no table) for non-HTTP.
-      if (alternate.valid())
-        alternate.push_frag_offset(write_pos);
-#endif
-    }
-    fragment++;
-    write_pos += write_len;
     dir_insert(&key, vol, &dir);
-    blocks = iobufferblock_skip(blocks, &offset, &length, write_len);
-    next_CacheKey(&key, &key);
-    if (length) {
-      write_len = length;
-      if (write_len > MAX_FRAG_SIZE)
-        write_len = MAX_FRAG_SIZE;
-      if ((ret = do_write_call()) == EVENT_RETURN)
-        goto Lcallreturn;
-      return ret;
-    }
-    f.data_done = 1;
-    return openWriteCloseHead(event, e); // must be called under vol lock from here
   }
-Lcallreturn:
-  return handleEvent(AIO_EVENT_DONE, 0);
+
+  if (key == earliest_key)
+    earliest_dir = dir;
+
+  {
+    MUTEX_LOCK(lock, od->mutex, mutex->thread_holding);
+    write_vector->write_complete(earliest_key, this, true);
+  }
+  
+  write_pos += write_len;
+  blocks = iobufferblock_skip(blocks, &offset, &length, write_len);
+  next_CacheKey(&key, &key);
+  if (length) {
+    write_len = length;
+    if (write_len > MAX_FRAG_SIZE)
+      write_len = MAX_FRAG_SIZE;
+    if ((ret = do_write_call()) == EVENT_RETURN)
+      return handleEvent(AIO_EVENT_DONE, 0);
+    return ret;
+  }
+
+  f.data_done = 1;
+  return openWriteCloseHead(event, e); // must be called under vol lock from here
+                                       // [amc] don't see why, guess we'll find out.
+
 }
 
 int
@@ -1277,8 +1313,9 @@ CacheVC::openWriteClose(int event, Event *e)
       return openWriteCloseDir(event, e);
 #endif
     }
-    if (length && (fragment || length > MAX_FRAG_SIZE)) {
+    if (length && (fragment || length > MAX_FRAG_SIZE || alternate.object_size_get() > alternate.get_frag_fixed_size())) {
       SET_HANDLER(&CacheVC::openWriteCloseDataDone);
+      this->updateWriteStateFromRange();
       write_len = length;
       if (write_len > MAX_FRAG_SIZE)
         write_len = MAX_FRAG_SIZE;
@@ -1306,48 +1343,92 @@ CacheVC::openWriteWriteDone(int event, Event *e)
     SET_HANDLER(&CacheVC::openWriteMain);
     return calluser(VC_EVENT_ERROR);
   }
+
   {
     CACHE_TRY_LOCK(lock, vol->mutex, mutex->thread_holding);
     if (!lock.is_locked())
       VC_LOCK_RETRY_EVENT();
-    // store the earliest directory. Need to remove the earliest dir
-    // in case the writer aborts.
-    if (!fragment) {
-      ink_assert(key == earliest_key);
-      earliest_dir = dir;
-#ifdef HTTP_CACHE
-    } else {
-      // Store the offset only if there is a table.
-      // Currently there is no alt (and thence no table) for non-HTTP.
-      if (alternate.valid())
-        alternate.push_frag_offset(write_pos);
-#endif
-    }
-    ++fragment;
-    write_pos += write_len;
     dir_insert(&key, vol, &dir);
-    DDebug("cache_insert", "WriteDone: %X, %X, %d", key.slice32(0), first_key.slice32(0), write_len);
-    blocks = iobufferblock_skip(blocks, &offset, &length, write_len);
-    next_CacheKey(&key, &key);
   }
+
+  if (key == earliest_key)
+    earliest_dir = dir;
+
+  {
+    MUTEX_LOCK(lock, od->mutex, mutex->thread_holding);
+    write_vector->write_complete(earliest_key, this, true);
+  }
+
+  DDebug("cache_insert", "WriteDone: %X, %X, %d", key.slice32(0), first_key.slice32(0), write_len);
+
+  resp_range.consume(write_len);
+  blocks = iobufferblock_skip(blocks, &offset, &length, write_len);
+
   if (closed)
     return die();
   SET_HANDLER(&CacheVC::openWriteMain);
   return openWriteMain(event, e);
 }
 
-static inline int
-target_fragment_size()
-{
+int64_t
+CacheProcessor::get_fixed_fragment_size() const {
   return cache_config_target_fragment_size - sizeofDoc;
 }
 
+
+Action*
+CacheVC::do_init_write()
+{
+  Debug("amc", "[do_init_write] vc=%p", this);
+  SET_CONTINUATION_HANDLER(this, &CacheVC::openWriteInit);
+  return EVENT_DONE == this->openWriteInit(EVENT_IMMEDIATE, 0) ? ACTION_RESULT_DONE : &_action;
+}
+
+/* Do some initial setup and then switch over to openWriteMain
+ */
+int
+CacheVC::openWriteInit(int eid, Event* event)
+{
+  Debug("amc", "[openWriteInit] vc=%p", this);
+  {
+    CACHE_TRY_LOCK(lock, od->mutex, mutex->thread_holding);
+    if (!lock.is_locked()) {
+      trigger = mutex->thread_holding->schedule_in_local(this, HRTIME_MSECONDS(cache_config_mutex_retry_delay), eid);
+      return EVENT_CONT;
+    }
+
+    // If we're not already in the alt vector, insert.
+    if (-1 == (alternate_index = get_alternate_index(write_vector, first_key))) {
+      alternate_index = write_vector->insert(&alternate);
+    }
+    // mark us as an writer.
+    write_vector->data[alternate_index]._writers.push(this);
+
+    if (this == od->open_writer) {
+      od->open_writer = NULL;
+      CacheVC* reader;
+      while (NULL != (reader = od->open_waiting.pop())) {
+        Debug("amc", "[CacheVC::openWriteInit] wake up %p", reader);
+        reader->wake_up_thread->schedule_imm(reader);
+      }
+    }
+  }
+
+  if (resp_range.hasRanges()) resp_range.start();
+//  write_pos = resp_range.getOffset();
+//  key = alternate.get_frag_key_of(write_pos);
+  SET_HANDLER(&CacheVC::openWriteMain);
+  return openWriteMain(eid, event);
+//  return EVENT_DONE;
+}
+
 int
 CacheVC::openWriteMain(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
 {
   cancel_trigger();
   int called_user = 0;
   ink_assert(!is_io_in_progress());
+  Debug("amc", "[CacheVC::openWriteMain]");
 Lagain:
   if (!vio.buffer.writer()) {
     if (calluser(VC_EVENT_WRITE_READY) == EVENT_DONE)
@@ -1363,10 +1444,15 @@ Lagain:
     if (vio.ntodo() <= 0)
       return EVENT_CONT;
   }
+
   int64_t ntodo = (int64_t)(vio.ntodo() + length);
   int64_t total_avail = vio.buffer.reader()->read_avail();
   int64_t avail = total_avail;
   int64_t towrite = avail + length;
+  int64_t ffs = cacheProcessor.get_fixed_fragment_size();
+
+  Debug("amc", "[CacheVC::openWriteMain] ntodo=%" PRId64 " avail=%" PRId64 " towrite=%" PRId64, ntodo, avail, towrite);
+
   if (towrite > ntodo) {
     avail -= (towrite - ntodo);
     towrite = ntodo;
@@ -1379,17 +1465,20 @@ Lagain:
     blocks = vio.buffer.reader()->block;
     offset = vio.buffer.reader()->start_offset;
   }
+
   if (avail > 0) {
     vio.buffer.reader()->consume(avail);
     vio.ndone += avail;
     total_len += avail;
   }
   length = (uint64_t)towrite;
-  if (length > target_fragment_size() && (length < target_fragment_size() + target_fragment_size() / 4))
-    write_len = target_fragment_size();
+  // [amc] Need to change this to be exactly the fixed fragment size for this alternate.
+  if (length > ffs &&
+      (length < ffs + ffs / 4))
+    write_len = ffs;
   else
     write_len = length;
-  bool not_writing = towrite != ntodo && towrite < target_fragment_size();
+  bool not_writing = towrite != ntodo && towrite < ffs;
   if (!called_user) {
     if (not_writing) {
       called_user = 1;
@@ -1401,12 +1490,56 @@ Lagain:
   }
   if (not_writing)
     return EVENT_CONT;
+
+  ink_assert(static_cast<int64_t>(write_pos) == alternate.get_frag_offset(fragment));
+  {
+    CacheHTTPInfo *alt = 0;
+    MUTEX_LOCK(lock, od->mutex, this_ethread());
+
+    this->updateWriteStateFromRange();
+    alternate_index = get_alternate_index(write_vector, earliest_key);
+    if (alternate_index < 0)
+      alternate_index = write_vector->insert(&alternate, alternate_index);
+
+    alt = write_vector->get(alternate_index);
+
+    if (fragment != 0 && !alt->m_alt->m_earliest.m_flag.cached_p) {
+      SET_HANDLER(&CacheVC::openWriteEmptyEarliestDone);
+      if (!od->is_write_active(earliest_key, 0)) {
+        write_len = 0;
+        key = earliest_key;
+        Debug("amc", "[CacheVC::openWriteMain] writing empty earliest");
+      } else {
+        // go on the wait list
+        od->waiting_for(earliest_key, this, 0);
+        not_writing = true;
+      }
+    } else if (od->is_write_active(earliest_key, write_pos)) {
+      od->waiting_for(earliest_key, this, write_pos);
+      not_writing = true;
+    } else if (alternate.is_frag_cached(fragment)) {
+      not_writing = true;
+      resp_range.consume(length); // just drop it.
+      Debug("amc", "Fragment %d already cached", fragment);
+      // Need to consume the actual data too (in blocks/offset).
+    } else {
+      od->write_active(earliest_key, this, write_pos);
+    }
+  }
+
+  if (0 == write_len) // need to set up the write not under OpenDir lock.
+    return do_write_lock_call();
+
   if (towrite == ntodo && f.close_complete) {
     closed = 1;
     SET_HANDLER(&CacheVC::openWriteClose);
     return openWriteClose(EVENT_NONE, NULL);
+  } else if (not_writing) {
+    return EVENT_CONT;
   }
+
   SET_HANDLER(&CacheVC::openWriteWriteDone);
+  Debug("amc", "[CacheVC::openWriteMain] doing write call");
   return do_write_lock_call();
 }
 
@@ -1444,7 +1577,7 @@ Lcollision : {
   }
 }
 Ldone:
-  SET_HANDLER(&CacheVC::openWriteMain);
+  SET_HANDLER(&CacheVC::openWriteInit);
   return callcont(CACHE_EVENT_OPEN_WRITE);
 Lcallreturn:
   return handleEvent(AIO_EVENT_DONE, 0); // hopefully a tail call
@@ -1539,8 +1672,8 @@ CacheVC::openWriteStartDone(int event, Event *e)
         goto Lfailure;
       if (od->has_multiple_writers()) {
         MUTEX_RELEASE(lock);
-        SET_HANDLER(&CacheVC::openWriteMain);
-        return callcont(CACHE_EVENT_OPEN_WRITE);
+        SET_HANDLER(&CacheVC::openWriteInit);
+        return this->openWriteInit(EVENT_IMMEDIATE, 0);
       }
     }
     // check for collision
@@ -1560,8 +1693,8 @@ Lsuccess:
   od->reading_vec = 0;
   if (_action.cancelled)
     goto Lcancel;
-  SET_HANDLER(&CacheVC::openWriteMain);
-  return callcont(CACHE_EVENT_OPEN_WRITE);
+  SET_HANDLER(&CacheVC::openWriteInit);
+  return this->openWriteInit(EVENT_IMMEDIATE, 0);
 
 Lfailure:
   CACHE_INCREMENT_DYN_STAT(base_stat + CACHE_STAT_FAILURE);
@@ -1598,7 +1731,7 @@ CacheVC::openWriteStartBegin(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED
     return openWriteOverwrite(EVENT_IMMEDIATE, 0);
   } else {
     // write by key
-    SET_HANDLER(&CacheVC::openWriteMain);
+    SET_HANDLER(&CacheVC::openWriteInit);
     return callcont(CACHE_EVENT_OPEN_WRITE);
   }
 }
@@ -1658,7 +1791,7 @@ Cache::open_write(Continuation *cont, CacheKey *key, CacheFragType frag_type, in
     return &c->_action;
   }
   if (!c->f.overwrite) {
-    SET_CONTINUATION_HANDLER(c, &CacheVC::openWriteMain);
+    SET_CONTINUATION_HANDLER(c, &CacheVC::openWriteInit);
     c->callcont(CACHE_EVENT_OPEN_WRITE);
     return ACTION_RESULT_DONE;
   } else {
@@ -1670,6 +1803,21 @@ Cache::open_write(Continuation *cont, CacheKey *key, CacheFragType frag_type, in
   }
 }
 
+int
+CacheVC::updateWriteStateFromRange()
+{
+  if (resp_range.hasPendingRangeShift())
+    resp_range.consumeRangeShift();
+  write_pos = resp_range.getOffset();
+  fragment = alternate.get_frag_index_of(write_pos);
+  key = alternate.get_frag_key(fragment);
+  {
+    char tmp[64];
+    Debug("amc", "[writeMain] pos=%" PRId64 " frag=%d/%" PRId64 " key=%s", write_pos, fragment, alternate.get_frag_offset(fragment), key.toHexStr(tmp));
+  }
+  return write_pos;
+}
+
 #ifdef HTTP_CACHE
 // main entry point for writing of http documents
 Action *
@@ -1762,6 +1910,8 @@ Cache::open_write(Continuation *cont, CacheKey *key, CacheHTTPInfo *info, time_t
           goto Lfailure;
         }
         // document doesn't exist, begin write
+        ink_assert(NULL == c->od->open_writer);
+        c->od->open_writer = c;
         goto Lmiss;
       } else {
         c->od->reading_vec = 1;
@@ -1784,7 +1934,8 @@ Cache::open_write(Continuation *cont, CacheKey *key, CacheHTTPInfo *info, time_t
   }
 
 Lmiss:
-  SET_CONTINUATION_HANDLER(c, &CacheVC::openWriteMain);
+//  return c->do_init_write();
+  SET_CONTINUATION_HANDLER(c, &CacheVC::openWriteInit);
   c->callcont(CACHE_EVENT_OPEN_WRITE);
   return ACTION_RESULT_DONE;
 

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/I_Cache.h
----------------------------------------------------------------------
diff --git a/iocore/cache/I_Cache.h b/iocore/cache/I_Cache.h
index af99d30..7ee6b37 100644
--- a/iocore/cache/I_Cache.h
+++ b/iocore/cache/I_Cache.h
@@ -49,6 +49,7 @@
 #define CACHE_COMPRESSION_LIBZ 2
 #define CACHE_COMPRESSION_LIBLZMA 3
 
+struct CacheVConnection;
 struct CacheVC;
 struct CacheDisk;
 #ifdef HTTP_CACHE
@@ -56,6 +57,7 @@ class CacheLookupHttpConfig;
 class URL;
 class HTTPHdr;
 class HTTPInfo;
+class HTTPRangeSpec;
 
 typedef HTTPHdr CacheHTTPHdr;
 typedef URL CacheURL;
@@ -80,11 +82,35 @@ struct CacheProcessor : public Processor {
                             CacheFragType frag_type = CACHE_FRAG_TYPE_NONE, char *hostname = 0, int host_len = 0);
   inkcoreapi Action *open_read(Continuation *cont, CacheKey *key, bool cluster_cache_local,
                                CacheFragType frag_type = CACHE_FRAG_TYPE_NONE, char *hostname = 0, int host_len = 0);
-  inkcoreapi Action *open_write(Continuation *cont, CacheKey *key, bool cluster_cache_local,
-                                CacheFragType frag_type = CACHE_FRAG_TYPE_NONE, int expected_size = CACHE_EXPECTED_SIZE,
-                                int options = 0, time_t pin_in_cache = (time_t)0, char *hostname = 0, int host_len = 0);
-  inkcoreapi Action *remove(Continuation *cont, CacheKey *key, bool cluster_cache_local,
-                            CacheFragType frag_type = CACHE_FRAG_TYPE_NONE, bool rm_user_agents = true, bool rm_link = false,
+
+  /** Open a cache reader from an already open writer.
+
+      This is used for partial content on a cache miss to open a reader corresponding to the
+      partial content writer.
+  */
+  inkcoreapi Action* open_read(Continuation* cont, CacheVConnection* writer, HTTPHdr* client_request_hdr);
+
+  Action *open_read_buffer(Continuation *cont, MIOBuffer *buf, CacheKey *key,
+                           CacheFragType frag_type = CACHE_FRAG_TYPE_NONE, char *hostname = 0, int host_len = 0);
+
+  inkcoreapi Action *open_write(Continuation *cont,
+                                CacheKey *key,
+                                bool cluster_cache_local,
+                                CacheFragType frag_type = CACHE_FRAG_TYPE_NONE,
+                                int expected_size = CACHE_EXPECTED_SIZE,
+                                int options = 0,
+                                time_t pin_in_cache = (time_t) 0,
+                                char *hostname = 0, int host_len = 0);
+  Action *open_write_buffer(Continuation *cont, MIOBuffer *buf,
+                            CacheKey *key,
+                            CacheFragType frag_type = CACHE_FRAG_TYPE_NONE,
+                            int options = 0,
+                            time_t pin_in_cache = (time_t) 0,
+                            char *hostname = 0, int host_len = 0);
+  inkcoreapi Action *remove(Continuation *cont, CacheKey *key,
+                            bool cluster_cache_local,
+                            CacheFragType frag_type = CACHE_FRAG_TYPE_NONE,
+                            bool rm_user_agents = true, bool rm_link = false,
                             char *hostname = 0, int host_len = 0);
   Action *scan(Continuation *cont, char *hostname = 0, int host_len = 0, int KB_per_second = SCAN_KB_PER_SECOND);
 #ifdef HTTP_CACHE
@@ -126,6 +152,9 @@ struct CacheProcessor : public Processor {
   */
   bool has_online_storage() const;
 
+  /** Get the target fragment size. */
+  int64_t get_fixed_fragment_size() const;
+
   static int IsCacheEnabled();
 
   static bool IsCacheReady(CacheFragType type);
@@ -190,6 +219,57 @@ struct CacheVConnection : public VConnection {
 #ifdef HTTP_CACHE
   virtual void set_http_info(CacheHTTPInfo *info) = 0;
   virtual void get_http_info(CacheHTTPInfo **info) = 0;
+
+  /** Get the boundary string for a multi-part range response.
+      The length of the string is returned in @a len.
+
+      @return A point to the string.
+   */
+  virtual char const* get_http_range_boundary_string(int* len) const = 0;
+
+  /** Get the effective content size.
+
+      This is the amount of actual data based on any range or framing.  Effectively this is the
+      value to be passed to the @c VIO while the content length is used in the HTTP header.
+  */
+  virtual int64_t get_effective_content_size() = 0;
+
+  /** Set the origin reported content size.
+
+      This is the content length reported by the origin server and should be considered a hint, not
+      definitive. The object size, as stored in the cache, is the actual amount of data received and
+      cached.
+
+      @note This is the total content length as reported in the HTTP header, not the partial (range based) response size.
+      Also this is the length of the HTTP content, which may differ from the size of the data stream.
+  */
+  virtual void set_full_content_length(int64_t) = 0;
+
+  /** Get the range spec for the response (request ranges modifed by content length).
+      @internal Need better comment - this is the range spec used for a response from ATS to the user agent
+      from cached data. Even better we have potentially 2 response ranges - that from the origin server to
+      ATS and that from ATS to the user agent which are only somewhat similar, depending on what exactly
+      is in the cache at the moment.
+   */
+  virtual HTTPRangeSpec& get_http_range_spec() = 0;
+
+  /// Check if this is HTTP partial content (range request/response).
+  virtual bool is_http_partial_content() = 0;
+
+  /// Get the unchanged ranges.
+  /// @return @c true if the @a result is not empty.
+  /// @internal Currently this just returns the single range that is convex hull of the uncached request.
+  /// Someday we may want to do the exact range spec but we use the type for now because it's easier.
+  virtual bool get_uncached(HTTPRangeSpec& result) = 0;
+
+  /** Set the range for the input (response content).
+      The incoming bytes will be written to this section of the object.
+      @note This range @b must be absolute.
+      @note The range is inclusive.
+      @return The # of bytes in the range.
+  */
+  virtual int64_t set_inbound_range(int64_t min, int64_t max) { return 1 + (max - min); }
+
 #endif
 
   virtual bool is_ram_cache_hit() const = 0;

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/799a83bb/iocore/cache/I_CacheDefs.h
----------------------------------------------------------------------
diff --git a/iocore/cache/I_CacheDefs.h b/iocore/cache/I_CacheDefs.h
index 93b0cac..df62eca 100644
--- a/iocore/cache/I_CacheDefs.h
+++ b/iocore/cache/I_CacheDefs.h
@@ -21,6 +21,7 @@
   limitations under the License.
  */
 
+#include <vector>
 
 #ifndef _I_CACHE_DEFS_H__
 #define _I_CACHE_DEFS_H__
@@ -32,7 +33,7 @@
 #define CACHE_ALT_INDEX_DEFAULT -1
 #define CACHE_ALT_REMOVED -2
 
-#define CACHE_DB_MAJOR_VERSION 24
+#define CACHE_DB_MAJOR_VERSION 25
 #define CACHE_DB_MINOR_VERSION 0
 
 #define CACHE_DIR_MAJOR_VERSION 18
@@ -126,4 +127,5 @@ typedef CryptoHash CacheKey;
    word(2) - tag (lower bits), hosttable hash (upper bits)
    word(3) - ram cache hash, lookaside cache
  */
+
 #endif // __CACHE_DEFS_H__