func TestChannelEmptyConsumer(t *testing.T) { opts := nsqdNs.NewOptions() opts.Logger = newTestLogger(t) tcpAddr, _, nsqd, nsqdServer := mustStartNSQD(opts) defer os.RemoveAll(opts.DataPath) defer nsqdServer.Exit() conn, _ := mustConnectNSQD(tcpAddr) defer conn.Close() topicName := "test_channel_empty" + strconv.Itoa(int(time.Now().Unix())) topic := nsqd.GetTopicIgnPart(topicName) channel := topic.GetChannel("channel") client := nsqdNs.NewClientV2(0, conn, opts, nil) client.SetReadyCount(25) channel.AddClient(client.ID, client) for i := 0; i < 25; i++ { msg := nsqdNs.NewMessage(nsqdNs.MessageID(i), []byte("test")) channel.StartInFlightTimeout(msg, 0, "", opts.MsgTimeout) client.SendingMessage() } for _, cl := range channel.GetClients() { stats := cl.Stats() test.Equal(t, stats.InFlightCount, int64(25)) } channel.SetConsumeOffset(channel.GetChannelEnd().Offset(), channel.GetChannelEnd().TotalMsgCnt(), true) time.Sleep(time.Second) for _, cl := range channel.GetClients() { stats := cl.Stats() test.Equal(t, stats.InFlightCount, int64(0)) } }
func (p *protocolV2) IOLoop(conn net.Conn) error { var err error var line []byte var zeroTime time.Time left := make([]byte, 100) tmpLine := make([]byte, 100) clientID := p.ctx.nextClientID() client := nsqd.NewClientV2(clientID, conn, p.ctx.getOpts(), p.ctx.GetTlsConfig()) // synchronize the startup of messagePump in order // to guarantee that it gets a chance to initialize // goroutine local state derived from client attributes // and avoid a potential race with IDENTIFY (where a client // could have changed or disabled said attributes) messagePumpStartedChan := make(chan bool) msgPumpStoppedChan := make(chan bool) go p.messagePump(client, messagePumpStartedChan, msgPumpStoppedChan) <-messagePumpStartedChan for { if client.HeartbeatInterval > 0 { client.SetReadDeadline(time.Now().Add(client.HeartbeatInterval * 2)) } else { client.SetReadDeadline(zeroTime) } // ReadSlice does not allocate new space for the data each request // ie. the returned slice is only valid until the next call to it line, err = client.Reader.ReadSlice('\n') if err != nil { if err == io.EOF { err = nil } else { err = fmt.Errorf("failed to read command - %s", err) } break } if p.ctx.getOpts().Verbose || nsqd.NsqLogger().Level() >= levellogger.LOG_DETAIL { nsqd.NsqLogger().Logf("PROTOCOL(V2) got client command: %v ", line) } // handle the compatible for message id. // Since the new message id is id+traceid. we can not // use \n to check line. // REQ, FIN, TOUCH (with message id as param) should be handled. // FIN 16BYTES\n // REQ 16bytes time\n // TOUCH 16bytes\n isSpecial := false params := make([][]byte, 0) if len(line) >= 3 { if bytes.Equal(line[:3], []byte("FIN")) || bytes.Equal(line[:3], []byte("REQ")) { isSpecial = true if len(line) < 21 { left = left[:20-len(line)] nr := 0 nr, err = io.ReadFull(client.Reader, left) if err != nil { nsqd.NsqLogger().LogErrorf("read param err:%v", err) } line = append(line, left[:nr]...) tmpLine = tmpLine[:len(line)] copy(tmpLine, line) // the readslice will overwrite the slice line, // so we should copy it and copy back. extra, extraErr := client.Reader.ReadSlice('\n') tmpLine = append(tmpLine, extra...) line = append(line[:0], tmpLine...) if extraErr != nil { nsqd.NsqLogger().LogErrorf("read param err:%v", extraErr) } } params = append(params, line[:3]) if len(line) >= 21 { params = append(params, line[4:20]) // it must be REQ if bytes.Equal(line[:3], []byte("REQ")) { if len(line) >= 22 { params = append(params, line[21:len(line)-1]) } } else { params = append(params, line[20:]) } } else { params = append(params, []byte("")) } } else if len(line) >= 5 { if bytes.Equal(line[:5], []byte("TOUCH")) { isSpecial = true if len(line) < 23 { left = left[:23-len(line)] nr := 0 nr, err = io.ReadFull(client.Reader, left) if err != nil { nsqd.NsqLogger().Logf("TOUCH param err:%v", err) } line = append(line, left[:nr]...) } params = append(params, line[:5]) if len(line) >= 23 { params = append(params, line[6:22]) } else { params = append(params, []byte("")) } } } } if p.ctx.getOpts().Verbose || nsqd.NsqLogger().Level() >= levellogger.LOG_DETAIL { nsqd.NsqLogger().Logf("PROTOCOL(V2) got client command: %v ", line) } if !isSpecial { // trim the '\n' line = line[:len(line)-1] // optionally trim the '\r' if len(line) > 0 && line[len(line)-1] == '\r' { line = line[:len(line)-1] } params = bytes.Split(line, separatorBytes) } if p.ctx.getOpts().Verbose || nsqd.NsqLogger().Level() >= levellogger.LOG_DETAIL { nsqd.NsqLogger().Logf("PROTOCOL(V2): [%s] %v, %v", client, string(params[0]), params) } var response []byte response, err = p.Exec(client, params) err = handleRequestReponseForClient(client, response, err) if err != nil { break } } if err != nil { nsqd.NsqLogger().Logf("PROTOCOL(V2): client [%s] exiting ioloop with error: %v", client, err) } nsqd.NsqLogger().LogDebugf("PROTOCOL(V2): client [%s] exiting ioloop", client) close(client.ExitChan) p.ctx.nsqd.CleanClientPubStats(client.RemoteAddr().String(), "tcp") <-msgPumpStoppedChan if client.Channel != nil { client.Channel.RequeueClientMessages(client.ID, client.String()) client.Channel.RemoveClient(client.ID) } client.FinalClose() return err }