func (p *protocolV2) FIN(client *nsqd.ClientV2, params [][]byte) ([]byte, error) { state := atomic.LoadInt32(&client.State) if state != stateSubscribed && state != stateClosing { nsqd.NsqLogger().LogWarningf("[%s] command in wrong state: %v", client, state) return nil, protocol.NewFatalClientErr(nil, E_INVALID, "cannot FIN in current state") } if len(params) < 2 { nsqd.NsqLogger().LogDebugf("FIN error params: %v", params) return nil, protocol.NewFatalClientErr(nil, E_INVALID, "FIN insufficient number of params") } id, err := getFullMessageID(params[1]) if err != nil { nsqd.NsqLogger().LogDebugf("FIN error: %v, %v", params[1], err) return nil, protocol.NewFatalClientErr(nil, E_INVALID, err.Error()) } msgID := nsqd.GetMessageIDFromFullMsgID(*id) if int64(msgID) <= 0 { return nil, protocol.NewFatalClientErr(nil, E_INVALID, "Invalid Message ID") } if client.Channel == nil { nsqd.NsqLogger().LogDebugf("FIN error no channel: %v", msgID) return nil, protocol.NewFatalClientErr(nil, E_INVALID, "No channel") } if !p.ctx.checkForMasterWrite(client.Channel.GetTopicName(), client.Channel.GetTopicPart()) { nsqd.NsqLogger().Logf("topic %v fin message failed for not leader", client.Channel.GetTopicName()) return nil, protocol.NewFatalClientErr(nil, FailedOnNotLeader, "") } err = p.ctx.FinishMessage(client.Channel, client.ID, client.String(), msgID) if err != nil { client.IncrSubError(int64(1)) nsqd.NsqLogger().LogDebugf("FIN error : %v, err: %v, channel: %v, topic: %v", msgID, err, client.Channel.GetName(), client.Channel.GetTopicName()) if clusterErr, ok := err.(*consistence.CommonCoordErr); ok { if !clusterErr.IsLocalErr() { return nil, protocol.NewFatalClientErr(err, FailedOnNotWritable, "") } } return nil, protocol.NewClientErr(err, "E_FIN_FAILED", fmt.Sprintf("FIN %v failed %s", *id, err.Error())) } client.FinishedMessage() return nil, nil }
func (p *protocolV2) REQ(client *nsqd.ClientV2, params [][]byte) ([]byte, error) { state := atomic.LoadInt32(&client.State) if state != stateSubscribed && state != stateClosing { nsqd.NsqLogger().LogWarningf("[%s] command in wrong state: %v", client, state) return nil, protocol.NewFatalClientErr(nil, E_INVALID, "cannot REQ in current state") } if len(params) < 3 { return nil, protocol.NewFatalClientErr(nil, E_INVALID, "REQ insufficient number of params") } id, err := getFullMessageID(params[1]) if err != nil { return nil, protocol.NewFatalClientErr(nil, E_INVALID, err.Error()) } timeoutMs, err := protocol.ByteToBase10(params[2]) if err != nil { return nil, protocol.NewFatalClientErr(err, E_INVALID, fmt.Sprintf("REQ could not parse timeout %s", params[2])) } timeoutDuration := time.Duration(timeoutMs) * time.Millisecond if timeoutDuration < 0 || timeoutDuration > p.ctx.getOpts().MaxReqTimeout { return nil, protocol.NewFatalClientErr(nil, E_INVALID, fmt.Sprintf("REQ timeout %v out of range 0-%v", timeoutDuration, p.ctx.getOpts().MaxReqTimeout)) } if client.Channel == nil { return nil, protocol.NewFatalClientErr(nil, E_INVALID, "No channel") } err = client.Channel.RequeueMessage(client.ID, client.String(), nsqd.GetMessageIDFromFullMsgID(*id), timeoutDuration, true) if err != nil { client.IncrSubError(int64(1)) return nil, protocol.NewClientErr(err, "E_REQ_FAILED", fmt.Sprintf("REQ %v failed %s", *id, err.Error())) } client.RequeuedMessage(timeoutDuration > 0) return nil, nil }
func (p *protocolV2) internalSUB(client *nsqd.ClientV2, params [][]byte, enableTrace bool, ordered bool, startFrom *ConsumeOffset) ([]byte, error) { state := atomic.LoadInt32(&client.State) if state != stateInit { nsqd.NsqLogger().LogWarningf("[%s] command in wrong state: %v", client, state) return nil, protocol.NewFatalClientErr(nil, E_INVALID, "cannot SUB in current state") } if client.HeartbeatInterval <= 0 { return nil, protocol.NewFatalClientErr(nil, E_INVALID, "cannot SUB with heartbeats disabled") } if len(params) < 3 { return nil, protocol.NewFatalClientErr(nil, E_INVALID, "SUB insufficient number of parameters") } topicName := string(params[1]) if !protocol.IsValidTopicName(topicName) { return nil, protocol.NewFatalClientErr(nil, "E_BAD_TOPIC", fmt.Sprintf("SUB topic name %q is not valid", topicName)) } partition := -1 channelName := "" var err error channelName = string(params[2]) if !protocol.IsValidChannelName(channelName) { return nil, protocol.NewFatalClientErr(nil, "E_BAD_CHANNEL", fmt.Sprintf("SUB channel name %q is not valid", channelName)) } if len(params) == 4 { partition, err = strconv.Atoi(string(params[3])) if err != nil { return nil, protocol.NewFatalClientErr(nil, "E_BAD_PARTITION", fmt.Sprintf("topic partition is not valid: %v", err)) } } if err = p.CheckAuth(client, "SUB", topicName, channelName); err != nil { return nil, err } if partition == -1 { partition = p.ctx.getDefaultPartition(topicName) } topic, err := p.ctx.getExistingTopic(topicName, partition) if err != nil { nsqd.NsqLogger().Logf("sub to not existing topic: %v, err:%v", topicName, err.Error()) return nil, protocol.NewFatalClientErr(nil, E_TOPIC_NOT_EXIST, "") } if !p.ctx.checkForMasterWrite(topicName, partition) { nsqd.NsqLogger().Logf("sub failed on not leader: %v-%v, remote is : %v", topicName, partition, client.RemoteAddr()) // we need disable topic here to trigger a notify, maybe we failed to notify lookup last time. topic.DisableForSlave() return nil, protocol.NewFatalClientErr(nil, FailedOnNotLeader, "") } channel := topic.GetChannel(channelName) err = channel.AddClient(client.ID, client) if err != nil { nsqd.NsqLogger().Logf("sub failed to add client: %v, %v", client, err) return nil, protocol.NewFatalClientErr(nil, FailedOnNotWritable, "") } atomic.StoreInt32(&client.State, stateSubscribed) client.Channel = channel if enableTrace { nsqd.NsqLogger().Logf("sub channel %v with trace enabled, remote is : %v", channelName, client.RemoteAddr()) } if ordered { if atomic.LoadInt32(&client.SampleRate) != 0 { nsqd.NsqLogger().Errorf("%v", ErrOrderChannelOnSampleRate) return nil, protocol.NewFatalClientErr(nil, E_INVALID, ErrOrderChannelOnSampleRate.Error()) } channel.SetOrdered(true) } if startFrom != nil { cnt := channel.GetClientsCount() if cnt > 1 { nsqd.NsqLogger().LogDebugf("the consume offset: %v can only be set by the first client: %v", startFrom, cnt) } else { queueOffset, cnt, err := p.ctx.SetChannelOffset(channel, startFrom, false) if err != nil { return nil, protocol.NewFatalClientErr(nil, E_INVALID, err.Error()) } nsqd.NsqLogger().Logf("set the channel offset: %v (actual set : %v:%v), by client:%v, %v", startFrom, queueOffset, cnt, client.String(), client.UserAgent) } } client.EnableTrace = enableTrace // update message pump client.SubEventChan <- channel return okBytes, nil }
func (p *protocolV2) messagePump(client *nsqd.ClientV2, startedChan chan bool, stoppedChan chan bool) { var err error var buf bytes.Buffer var clientMsgChan chan *nsqd.Message var subChannel *nsqd.Channel // NOTE: `flusherChan` is used to bound message latency for // the pathological case of a channel on a low volume topic // with >1 clients having >1 RDY counts var flusherChan <-chan time.Time var sampleRate int32 subEventChan := client.SubEventChan identifyEventChan := client.IdentifyEventChan outputBufferTicker := time.NewTicker(client.OutputBufferTimeout) heartbeatTicker := time.NewTicker(client.HeartbeatInterval) heartbeatChan := heartbeatTicker.C heartbeatFailedCnt := 0 msgTimeout := client.MsgTimeout // v2 opportunistically buffers data to clients to reduce write system calls // we force flush in two cases: // 1. when the client is not ready to receive messages // 2. we're buffered and the channel has nothing left to send us // (ie. we would block in this loop anyway) // flushed := true // signal to the goroutine that started the messagePump // that we've started up close(startedChan) for { if subChannel == nil || !client.IsReadyForMessages() { // the client is not ready to receive messages... clientMsgChan = nil flusherChan = nil // force flush client.LockWrite() err = client.Flush() client.UnlockWrite() if err != nil { goto exit } flushed = true } else if flushed { // last iteration we flushed... // do not select on the flusher ticker channel clientMsgChan = subChannel.GetClientMsgChan() flusherChan = nil } else { // we're buffered (if there isn't any more data we should flush)... // select on the flusher ticker channel, too clientMsgChan = subChannel.GetClientMsgChan() flusherChan = outputBufferTicker.C } select { case <-client.ExitChan: goto exit case <-flusherChan: // if this case wins, we're either starved // or we won the race between other channels... // in either case, force flush client.LockWrite() err = client.Flush() client.UnlockWrite() if err != nil { goto exit } flushed = true case <-client.ReadyStateChan: case subChannel = <-subEventChan: // you can't SUB anymore nsqd.NsqLogger().Logf("client %v sub to channel: %v", client.ID, subChannel.GetName()) subEventChan = nil case identifyData := <-identifyEventChan: // you can't IDENTIFY anymore identifyEventChan = nil outputBufferTicker.Stop() if identifyData.OutputBufferTimeout > 0 { outputBufferTicker = time.NewTicker(identifyData.OutputBufferTimeout) } heartbeatTicker.Stop() heartbeatChan = nil if identifyData.HeartbeatInterval > 0 { heartbeatTicker = time.NewTicker(identifyData.HeartbeatInterval) heartbeatChan = heartbeatTicker.C } if identifyData.SampleRate > 0 { sampleRate = identifyData.SampleRate } msgTimeout = identifyData.MsgTimeout case <-heartbeatChan: if subChannel != nil && client.IsReadyForMessages() { // try wake up the channel subChannel.TryWakeupRead() } err = Send(client, frameTypeResponse, heartbeatBytes) nsqd.NsqLogger().LogDebugf("PROTOCOL(V2): [%s] send heartbeat", client) if err != nil { heartbeatFailedCnt++ nsqd.NsqLogger().LogWarningf("PROTOCOL(V2): [%s] send heartbeat failed %v times, %v", client, heartbeatFailedCnt, err) if heartbeatFailedCnt > 2 { goto exit } } else { heartbeatFailedCnt = 0 } case msg, ok := <-clientMsgChan: if !ok { goto exit } if sampleRate > 0 && rand.Int31n(100) > sampleRate { // FIN automatically, all message will not wait to confirm if not sending, // and the reader keep moving forward. offset, _, _, _ := subChannel.ConfirmBackendQueue(msg) // TODO: sync to replica nodes. _ = offset continue } // avoid re-send some confirmed message, // this may happen while the channel reader is reset to old position // due to some retry or leader change. if subChannel.IsConfirmed(msg) { continue } subChannel.StartInFlightTimeout(msg, client.ID, client.String(), msgTimeout) client.SendingMessage() err = SendMessage(client, msg, &buf, subChannel.IsOrdered()) if err != nil { goto exit } flushed = false } } exit: nsqd.NsqLogger().LogDebugf("PROTOCOL(V2): [%s] exiting messagePump", client) heartbeatTicker.Stop() outputBufferTicker.Stop() if err != nil { nsqd.NsqLogger().Logf("PROTOCOL(V2): [%s] messagePump error - %s", client, err) } close(stoppedChan) }