private void getCacheDataForOneSlice(int stripeIx, FileData cached, FileData result,
      BooleanRef gotAllData, boolean[] includes, LowLevelCacheCounters qfCounters) {
    StripeData cStripe = cached.stripes.get(stripeIx);
    if (LlapIoImpl.CACHE_LOGGER.isTraceEnabled()) {
      LlapIoImpl.CACHE_LOGGER.trace("Got stripe in cache " + cStripe);
    }
    StripeData stripe = StripeData.duplicateStructure(cStripe);
    result.stripes.add(stripe);
    boolean isMissed = false;
    for (int colIx = 0; colIx < cached.colCount; ++colIx) {
      if (!includes[colIx]) continue;
      if (cStripe.encodings[colIx] == null || cStripe.data[colIx] == null) {
        if (cStripe.data[colIx] != null) {
          throw new AssertionError(cStripe);
          // No encoding => must have no data.
        }
        isMissed = true;
        if (gotAllData != null) {
          gotAllData.value = false;
        }
        continue;
      }
      stripe.encodings[colIx] = cStripe.encodings[colIx];
      LlapSerDeDataBuffer[][] cColData = cStripe.data[colIx];
      assert cColData != null;
      for (int streamIx = 0;
          cColData != null && streamIx < cColData.length; ++streamIx) {
        LlapSerDeDataBuffer[] streamData = cColData[streamIx];
        // Note: this relies on the fact that we always evict the entire column, so if
        //       we have the column data, we assume we have all the streams we need.
        if (streamData == null) continue;
        for (int i = 0; i < streamData.length; ++i) { // Finally, we are going to use "i"!
          if (!lockBuffer(streamData[i], true)) {
            LlapIoImpl.CACHE_LOGGER.info("Couldn't lock data for stripe at "
                + stripeIx + ", colIx " + colIx + ", stream type " + streamIx);

            handleRemovedColumnData(cColData);
            cColData = null;
            isMissed = true;
            if (gotAllData != null) {
              gotAllData.value = false;
            }
            break;
          }
        }
      }
      // At this point, we have arrived at the level where we need all the data, and the
      // arrays never change. So we will just do a shallow assignment here instead of copy.
      stripe.data[colIx] = cColData;
      if (cColData == null) {
        stripe.encodings[colIx] = null;
      }
    }
    doMetricsStuffForOneSlice(qfCounters, stripe, isMissed);
  }
RW lock ensures we have a consistent view of the file data, which is important given that
we generate "stripe" boundaries arbitrarily. Reading buffer data itself doesn't require
that this lock is held; however, everything else in stripes list does.
TODO: make more granular? We only care that each one reader sees consistent boundaries.
      So, we could shallow-copy the stripes list, then have individual locks inside each.
llap-server/src/java/org/apache/hadoop/hive/llap/cache/SerDeLowLevelCacheImpl.java