예제 #1
0
func realHandleGet(cmd common.GetRequest, dataOut chan common.GetResponse, errorOut chan error, rw *bufio.ReadWriter) {
	// read index
	// make buf
	// for numChunks do
	//   read chunk directly into buffer
	// send response

	defer close(errorOut)
	defer close(dataOut)

outer:
	for idx, key := range cmd.Keys {
		missResponse := common.GetResponse{
			Miss:   true,
			Quiet:  cmd.Quiet[idx],
			Opaque: cmd.Opaques[idx],
			Flags:  0,
			Key:    key,
			Data:   nil,
		}

		_, metaData, err := getMetadata(rw, key)
		if err != nil {
			if err == common.ErrKeyNotFound {
				metrics.IncCounter(MetricCmdGetMissesMeta)
				dataOut <- missResponse
				continue outer
			}

			errorOut <- err
			return
		}

		missResponse.Flags = metaData.OrigFlags

		cmdSize := int(metaData.NumChunks)*(len(key)+4 /* key suffix */ +binprot.ReqHeaderLen) + binprot.ReqHeaderLen /* for the noop */
		cmdbuf := bytes.NewBuffer(make([]byte, 0, cmdSize))
		// Write all the get commands before reading
		for i := 0; i < int(metaData.NumChunks); i++ {
			chunkKey := chunkKey(key, i)
			// bytes.Buffer doesn't error
			binprot.WriteGetQCmd(cmdbuf, chunkKey)
		}

		// The final command must be Get or Noop to guarantee a response
		// We use Noop to make coding easier, but it's (very) slightly less efficient
		// since we send 24 extra bytes in each direction
		// bytes.Buffer doesn't error
		binprot.WriteNoopCmd(cmdbuf)

		// bufio's ReadFrom will end up doing an io.Copy(cmdbuf, socket), which is more
		// efficient than writing directly into the bufio or using cmdbuf.WriteTo(rw)
		if _, err := rw.ReadFrom(cmdbuf); err != nil {
			errorOut <- err
			return
		}

		// Flush to make sure all the get commands are sent to the server.
		if err := rw.Flush(); err != nil {
			errorOut <- err
			return
		}

		dataBuf := make([]byte, metaData.Length)
		tokenBuf := make([]byte, tokenSize)

		// Now that all the headers are sent, start reading in the data chunks. We read until the
		// header for the Noop command comes back, keeping track of how many chunks are read. This
		// means that there is no fast fail when a chunk is missing, but at least all the data is
		// read in so there's no problem with unread, buffered data that should have been discarded.
		// If the number of chunks doesn't match, we throw away the data and call it a miss.
		chunk := 0
		miss := false
		var lastErr error

		for {
			opcodeNoop, err := getLocalIntoBuf(rw.Reader, metaData, tokenBuf, dataBuf, chunk, int(metaData.ChunkSize))
			if err != nil {
				if err == common.ErrKeyNotFound {
					if !miss {
						metrics.IncCounter(MetricCmdGetMissesChunk)
						miss = true
					}
					continue
				} else {
					lastErr = err
				}
			}

			if opcodeNoop {
				break
			}

			if !bytes.Equal(metaData.Token[:], tokenBuf) {
				//fmt.Println(id, "Get miss because of invalid chunk token. Cmd:", cmd)
				//fmt.Printf("Expected: %v\n", metaData.Token)
				//fmt.Printf("Got:      %v\n", tokenBuf)
				if !miss {
					metrics.IncCounter(MetricCmdGetMissesToken)
					miss = true
				}
			}

			chunk++
		}

		if lastErr != nil {
			errorOut <- lastErr
			return
		}
		if miss {
			//fmt.Println("Get miss because of missing chunk")
			dataOut <- missResponse
			continue outer
		}

		dataOut <- common.GetResponse{
			Miss:   false,
			Quiet:  cmd.Quiet[idx],
			Opaque: cmd.Opaques[idx],
			Flags:  metaData.OrigFlags,
			Key:    key,
			Data:   dataBuf,
		}
	}
}