func handleReqAppend(r io.Reader, w io.Writer, pool *topicPool) error { dec := proto.NewDecoder(r) cid := dec.DecodeUint32() topic := dec.DecodeShortBytes() msgs := make([][]byte, dec.DecodeArrayLen()) if err := dec.Err(); err != nil { _ = writeErrResp(w, cid, "cannot decode request") return errwrap.Err(err, "cannot decode") } if len(topic) == 0 { _ = writeErrResp(w, cid, "topic name not given") return nil } if len(msgs) == 0 { return writeErrResp(w, cid, "no messages") } var mrd proto.MsgReader for i := range msgs { b, err := mrd.ReadBytes(r) if err != nil { _ = writeErrResp(w, cid, "cannot read message") return errwrap.Err(err, "cannot read message") } msgs[i] = b } s, err := pool.Topic(string(topic)) if err != nil { _ = writeErrResp(w, cid, "topic not available") return errwrap.Err(err, "cannot decode") } index, err := s.Append(msgs) if err != nil { _ = writeErrResp(w, cid, "cannot store messages") return errwrap.Err(err, "storage error: cannot append") } var b bytes.Buffer enc := proto.NewEncoder(&b) enc.EncodeUint8(proto.AppendResp) enc.EncodeUint32(cid) enc.EncodeUint64(index) if err := enc.Err(); err != nil { _ = writeErrResp(w, cid, "cannot encode response") return errwrap.Err(err, "cannot encode response") } if _, err := b.WriteTo(w); err != nil { _ = writeErrResp(w, cid, "cannot write response") return errwrap.Err(err, "cannot write response") } return nil }
func main() { log.SetOutput(os.Stdout) log.SetFlags(0) addr := flag.String("addr", "localhost:12345", "Server address") flag.Parse() if a := len(flag.Args()); a < 3 || a > 4 { fmt.Fprintf(os.Stderr, ` Usage: %s <topic> <offset> <limit> [<timeout>] `, os.Args[0]) os.Exit(2) } topic := flag.Args()[0] offset, err := strconv.ParseInt(flag.Args()[1], 10, 64) if err != nil { log.Fatalf("invalid offset value: %s", err) } limit, err := strconv.ParseInt(flag.Args()[2], 10, 64) if err != nil { log.Fatalf("invalid limit value: %s", err) } if limit < 1 { log.Fatal("limit must be greater than 0") } var timeout int64 if len(flag.Args()) == 4 { timeout, err = strconv.ParseInt(flag.Args()[3], 10, 64) if err != nil { log.Fatalf("invalid timeout value: %s", err) } } cid := uint32(time.Now().Unix()) var b bytes.Buffer enc := proto.NewEncoder(&b) enc.EncodeUint8(proto.SliceReq) enc.EncodeUint32(cid) enc.EncodeShortBytes([]byte(topic)) enc.EncodeUint64(uint64(offset)) enc.EncodeUint16(uint16(limit)) enc.EncodeUint16(uint16(timeout)) c, err := net.Dial("tcp", *addr) if err != nil { log.Fatalf("cannot connect to server: %s", err) } defer c.Close() if _, err := b.WriteTo(c); err != nil { log.Fatalf("cannot send request: %s", err) } dec := proto.NewDecoder(c) switch kind := dec.DecodeUint8(); kind { case proto.SliceResp: case proto.ErrResp: if rcid := dec.DecodeUint32(); rcid != cid { log.Printf("correlation ID missmatch, want %d, got %d", cid, rcid) } log.Fatalf("error response: %q", dec.DecodeBytes()) default: log.Fatalf("unknown response kind: %d", kind) } if rcid := dec.DecodeUint32(); rcid != cid { log.Printf("correlation ID missmatch, want %d, got %d", cid, rcid) } msgcnt := dec.DecodeArrayLen() var rw proto.MsgReader for i := 0; i < int(msgcnt); i++ { b, err := rw.ReadBytes(c) if err != nil { log.Fatalf("cannot read message: %s", err) } dec := proto.NewDecoder(bytes.NewBuffer(b)) show("[f%d k%d t%d #%d]", dec.DecodeUint8(), dec.DecodeUint16(), dec.DecodeUint64(), dec.DecodeUint64()) show("%q", dec.DecodeShortBytes()) show("%s", dec.DecodeBytes()) show("") } }
// Slicing with offset == number of messages will block for max timeout before // returning response. If during wait time any new message was written to // topic, it will be returned. Otherwise if timeout was reached and no new // messages was published, timeout error is returned. // Slicing with offset > number of messages instantly returns out of bound // error. func (s *store) Slice(offset, limit uint64, timeout time.Duration) ([][]byte, error) { if limit == 0 { return nil, nil } s.mu.RLock() amount := s.index.Count() s.mu.RUnlock() // TODO use notification instead of busy looping deadline := time.After(timeout) waitMessages: for { switch { case offset == amount: select { case <-deadline: return nil, errors.New("no new messages") case <-time.After(10 * time.Millisecond): s.mu.RLock() amount = s.index.Count() s.mu.RUnlock() } case offset > amount: return nil, errors.New("out of bound") default: break waitMessages } } s.mu.RLock() defer s.mu.RUnlock() if offset+limit > amount { limit = amount - offset } if pos, err := s.index.Offset(offset); err != nil { return nil, errwrap.Err(err, "cannot read index") } else { if _, err := s.log.Seek(pos, os.SEEK_SET); err != nil { return nil, errwrap.Err(err, "cannot seek") } } res := make([][]byte, 0, limit) var rd proto.MsgReader for i := 0; i < int(limit); i++ { b, err := rd.ReadBytes(s.log) switch err { case nil: case proto.ErrNotEnoughData, io.EOF: return res, nil default: return res, err } res = append(res, b) } return res, nil }