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 }