예제 #1
0
func (channel *Channel) basicConsume(method *amqp.BasicConsume) *amqp.AMQPError {
	var classId, methodId = method.MethodIdentifier()
	// Check queue
	if len(method.Queue) == 0 {
		if len(channel.lastQueueName) == 0 {
			return amqp.NewSoftError(404, "Queue not found", classId, methodId)
		} else {
			method.Queue = channel.lastQueueName
		}
	}
	// TODO: do not directly access channel.conn.server.queues
	var queue, found = channel.conn.server.queues[method.Queue]
	if !found {
		// Spec doesn't say, but seems like a 404?
		return amqp.NewSoftError(404, "Queue not found", classId, methodId)
	}
	if len(method.ConsumerTag) == 0 {
		method.ConsumerTag = util.RandomId()
	}
	amqpErr := channel.addConsumer(queue, method)
	if amqpErr != nil {
		return amqpErr
	}
	if !method.NoWait {
		channel.SendMethod(&amqp.BasicConsumeOk{method.ConsumerTag})
	}

	return nil
}
예제 #2
0
func (channel *Channel) queuePurge(method *amqp.QueuePurge) *amqp.AMQPError {
	fmt.Println("Got queuePurge")
	var classId, methodId = method.MethodIdentifier()

	// Check queue
	if len(method.Queue) == 0 {
		if len(channel.lastQueueName) == 0 {
			return amqp.NewSoftError(404, "Queue not found", classId, methodId)
		} else {
			method.Queue = channel.lastQueueName
		}
	}

	var queue, foundQueue = channel.server.queues[method.Queue]
	if !foundQueue {
		return amqp.NewSoftError(404, "Queue not found", classId, methodId)
	}

	if queue.ConnId != -1 && queue.ConnId != channel.conn.id {
		return amqp.NewSoftError(405, "Queue is locked to another connection", classId, methodId)
	}

	numPurged := queue.Purge()
	if !method.NoWait {
		channel.SendMethod(&amqp.QueuePurgeOk{numPurged})
	}
	return nil
}
예제 #3
0
func (channel *Channel) nackOne(tag uint64, requeue bool, commitTx bool) *amqp.AMQPError {
	channel.ackLock.Lock()
	defer channel.ackLock.Unlock()
	var unacked, found = channel.awaitingAcks[tag]
	if !found {
		var msg = fmt.Sprintf("Precondition Failed: Delivery Tag not found: %d", tag)
		return amqp.NewSoftError(406, msg, 60, 120)
	}
	// Transaction mode
	if channel.txMode && !commitTx {
		channel.txLock.Lock()
		defer channel.txLock.Unlock()
		channel.txAcks = append(channel.txAcks, amqp.NewTxAck(tag, true, requeue, false))
		return nil
	}
	// Non-transaction mode
	// Init
	consumer, cFound := channel.consumers[unacked.ConsumerTag]
	queue, qFound := channel.server.queues[unacked.QueueName]

	// Initialize resource holders array
	var rhs = []amqp.MessageResourceHolder{channel}
	if cFound {
		rhs = append(rhs, consumer)
	}

	// requeue and release the approriate resources
	if requeue && qFound {
		// If we're requeueing we release the resources but don't remove the
		// reference.
		queue.Readd(unacked.QueueName, unacked.Msg)
		for _, rh := range rhs {
			rh.ReleaseResources(unacked.Msg)
		}
	} else {
		// If we aren't re-adding, remove the ref and all associated
		// resources
		err := channel.server.msgStore.RemoveRef(unacked.Msg, unacked.QueueName, rhs)
		if err != nil {
			return amqp.NewSoftError(500, err.Error(), 60, 120)
		}
	}

	// Remove this unacked message from the ones
	// we're waiting for acks on and ping the consumer
	// since there might be a message available now
	delete(channel.awaitingAcks, tag)
	if cFound {
		consumer.Ping()
	}

	return nil
}
예제 #4
0
func (channel *Channel) handleContentBody(frame *amqp.WireFrame) *amqp.AMQPError {
	if channel.currentMessage == nil {
		return amqp.NewSoftError(500, "Unexpected content body frame. No method content-having method called yet!", 0, 0)
	}
	if channel.currentMessage.Header == nil {
		return amqp.NewSoftError(500, "Unexpected content body frame! No header yet", 0, 0)
	}
	channel.currentMessage.Payload = append(channel.currentMessage.Payload, frame)
	// TODO: store this on message
	var size = uint64(0)
	for _, body := range channel.currentMessage.Payload {
		size += uint64(len(body.Payload))
	}
	if size < channel.currentMessage.Header.ContentBodySize {
		return nil
	}

	// We have the whole contents, let's publish!
	defer stats.RecordHisto(channel.statRoute, stats.Start())
	var server = channel.server
	var message = channel.currentMessage

	exchange, _ := server.exchanges[message.Method.Exchange]

	if channel.txMode {
		// TxMode, add the messages to a list
		queues, err := exchange.QueuesForPublish(channel.currentMessage)
		if err != nil {
			return err
		}

		channel.txLock.Lock()
		for queueName, _ := range queues {
			var txmsg = amqp.NewTxMessage(message, queueName)
			channel.txMessages = append(channel.txMessages, txmsg)
		}
		channel.txLock.Unlock()
	} else {
		// Normal mode, publish directly
		returnMethod, amqpErr := server.publish(exchange, channel.currentMessage)
		if amqpErr != nil {
			channel.currentMessage = nil
			return amqpErr
		}
		if returnMethod != nil {
			channel.SendContent(returnMethod, channel.currentMessage)
		}
	}

	channel.currentMessage = nil
	return nil
}
예제 #5
0
func (channel *Channel) handleContentHeader(frame *amqp.WireFrame) *amqp.AMQPError {
	if channel.currentMessage == nil {
		return amqp.NewSoftError(500, "Unexpected content header frame!", 0, 0)
	}
	if channel.currentMessage.Header != nil {
		return amqp.NewSoftError(500, "Unexpected content header frame! Already saw header", 0, 0)
	}
	var headerFrame = &amqp.ContentHeaderFrame{}
	var err = headerFrame.Read(bytes.NewReader(frame.Payload), channel.server.strictMode)
	if err != nil {
		return amqp.NewHardError(500, "Error parsing header frame: "+err.Error(), 0, 0)
	}
	channel.currentMessage.Header = headerFrame
	return nil
}
예제 #6
0
func (channel *Channel) ackBelow(tag uint64, commitTx bool) *amqp.AMQPError {
	if channel.txMode && !commitTx {
		channel.txLock.Lock()
		defer channel.txLock.Unlock()
		channel.txAcks = append(channel.txAcks, amqp.NewTxAck(tag, false, false, true))
		return nil
	}
	channel.ackLock.Lock()
	defer channel.ackLock.Unlock()
	for k, unacked := range channel.awaitingAcks {
		if k <= tag || tag == 0 {
			consumer, cFound := channel.consumers[unacked.ConsumerTag]
			// Initialize resource holders array
			var rhs = []amqp.MessageResourceHolder{channel}
			if cFound {
				rhs = append(rhs, consumer)
			}
			err := channel.server.msgStore.RemoveRef(unacked.Msg, unacked.QueueName, rhs)
			// TODO: if this was an error do I still delete the ack we're waiting for?
			// The resources probably haven't been released.
			if err != nil {
				return amqp.NewSoftError(500, err.Error(), 60, 80)
			}
			delete(channel.awaitingAcks, k)

			if cFound {
				consumer.Ping()
			}
		}
	}
	// TODO: should this be an error if nothing was actually deleted and tag != 0?
	return nil
}
예제 #7
0
func (channel *Channel) basicGet(method *amqp.BasicGet) *amqp.AMQPError {
	// var classId, methodId = method.MethodIdentifier()
	// channel.conn.connectionErrorWithMethod(540, "Not implemented", classId, methodId)
	var queue, found = channel.conn.server.queues[method.Queue]
	if !found {
		// Spec doesn't say, but seems like a 404?
		var classId, methodId = method.MethodIdentifier()
		return amqp.NewSoftError(404, "Queue not found", classId, methodId)
	}
	var qm = queue.GetOneForced()
	if qm == nil {
		channel.SendMethod(&amqp.BasicGetEmpty{})
		return nil
	}

	var rhs = []amqp.MessageResourceHolder{channel}
	msg, err := channel.server.msgStore.GetAndDecrRef(qm, queue.Name, rhs)
	if err != nil {
		// TODO: return 500 error
		channel.SendMethod(&amqp.BasicGetEmpty{})
		return nil
	}

	channel.SendContent(&amqp.BasicGetOk{
		DeliveryTag:  channel.nextDeliveryTag(),
		Redelivered:  qm.DeliveryCount > 0,
		Exchange:     msg.Exchange,
		RoutingKey:   msg.Key,
		MessageCount: 1,
	}, msg)
	return nil
}
예제 #8
0
func (channel *Channel) basicPublish(method *amqp.BasicPublish) *amqp.AMQPError {
	defer stats.RecordHisto(channel.statPublish, stats.Start())
	var _, found = channel.server.exchanges[method.Exchange]
	if !found {
		var classId, methodId = method.MethodIdentifier()
		return amqp.NewSoftError(404, "Exchange not found", classId, methodId)
	}
	channel.startPublish(method)
	return nil
}
예제 #9
0
func (channel *Channel) exchangeDelete(method *amqp.ExchangeDelete) *amqp.AMQPError {
	var classId, methodId = method.MethodIdentifier()
	var errCode, err = channel.server.deleteExchange(method)
	if err != nil {
		return amqp.NewSoftError(errCode, err.Error(), classId, methodId)
	}
	if !method.NoWait {
		channel.SendMethod(&amqp.ExchangeDeleteOk{})
	}
	return nil
}
예제 #10
0
func (channel *Channel) basicCancel(method *amqp.BasicCancel) *amqp.AMQPError {

	if err := channel.removeConsumer(method.ConsumerTag); err != nil {
		var classId, methodId = method.MethodIdentifier()
		return amqp.NewSoftError(404, "Consumer not found", classId, methodId)
	}

	if !method.NoWait {
		channel.SendMethod(&amqp.BasicCancelOk{method.ConsumerTag})
	}
	return nil
}
예제 #11
0
func (channel *Channel) ackOne(tag uint64, commitTx bool) *amqp.AMQPError {
	channel.ackLock.Lock()
	defer channel.ackLock.Unlock()

	var unacked, found = channel.awaitingAcks[tag]
	if !found {
		var msg = fmt.Sprintf("Precondition Failed: Delivery Tag not found: %d", tag)
		return amqp.NewSoftError(406, msg, 60, 80)
	}
	// Tx mode
	if channel.txMode && !commitTx {
		channel.txLock.Lock()
		defer channel.txLock.Unlock()
		channel.txAcks = append(channel.txAcks, amqp.NewTxAck(tag, false, false, false))
		return nil
	}
	// Normal mode
	// Init
	consumer, cFound := channel.consumers[unacked.ConsumerTag]

	// Initialize resource holders array
	var rhs = []amqp.MessageResourceHolder{channel}
	if cFound {
		rhs = append(rhs, consumer)
	}
	err := channel.server.msgStore.RemoveRef(unacked.Msg, unacked.QueueName, rhs)
	// TODO: if this is an error, do I still delete the tag? the resources
	// probably haven't been freed
	if err != nil {
		return amqp.NewSoftError(500, err.Error(), 60, 80)
	}
	delete(channel.awaitingAcks, tag)

	if cFound {
		consumer.Ping()
	}
	return nil
}
예제 #12
0
func (channel *Channel) queueDelete(method *amqp.QueueDelete) *amqp.AMQPError {
	fmt.Println("Got queueDelete")
	var classId, methodId = method.MethodIdentifier()

	// Check queue
	if len(method.Queue) == 0 {
		if len(channel.lastQueueName) == 0 {
			return amqp.NewSoftError(404, "Queue not found", classId, methodId)
		} else {
			method.Queue = channel.lastQueueName
		}
	}

	numPurged, errCode, err := channel.server.deleteQueue(method, channel.conn.id)
	if err != nil {
		return amqp.NewSoftError(errCode, err.Error(), classId, methodId)
	}

	if !method.NoWait {
		channel.SendMethod(&amqp.QueueDeleteOk{numPurged})
	}
	return nil
}
예제 #13
0
func (channel *Channel) queueBind(method *amqp.QueueBind) *amqp.AMQPError {
	var classId, methodId = method.MethodIdentifier()

	if len(method.Queue) == 0 {
		if len(channel.lastQueueName) == 0 {
			return amqp.NewSoftError(404, "Queue not found", classId, methodId)
		} else {
			method.Queue = channel.lastQueueName
		}
	}

	// Check exchange
	var exchange, foundExchange = channel.server.exchanges[method.Exchange]
	if !foundExchange {
		return amqp.NewSoftError(404, "Exchange not found", classId, methodId)
	}

	// Check queue
	var queue, foundQueue = channel.server.queues[method.Queue]
	if !foundQueue || queue.Closed {
		return amqp.NewSoftError(404, fmt.Sprintf("Queue not found: %s", method.Queue), classId, methodId)
	}

	if queue.ConnId != -1 && queue.ConnId != channel.conn.id {
		return amqp.NewSoftError(405, fmt.Sprintf("Queue is locked to another connection"), classId, methodId)
	}

	// Create binding
	b, err := binding.NewBinding(method.Queue, method.Exchange, method.RoutingKey, method.Arguments, exchange.IsTopic())
	if err != nil {
		return amqp.NewSoftError(500, err.Error(), classId, methodId)
	}

	// Add binding
	err = exchange.AddBinding(b, channel.conn.id)
	if err != nil {
		return amqp.NewSoftError(500, err.Error(), classId, methodId)
	}

	// Persist durable bindings
	if exchange.Durable && queue.Durable {
		var err = b.Persist(channel.server.db)
		if err != nil {
			return amqp.NewSoftError(500, err.Error(), classId, methodId)
		}
	}

	if !method.NoWait {
		channel.SendMethod(&amqp.QueueBindOk{})
	}
	return nil
}
예제 #14
0
func (channel *Channel) queueUnbind(method *amqp.QueueUnbind) *amqp.AMQPError {
	var classId, methodId = method.MethodIdentifier()

	// Check queue
	if len(method.Queue) == 0 {
		if len(channel.lastQueueName) == 0 {
			return amqp.NewSoftError(404, "Queue not found", classId, methodId)
		} else {
			method.Queue = channel.lastQueueName
		}
	}

	var queue, foundQueue = channel.server.queues[method.Queue]
	if !foundQueue {
		return amqp.NewSoftError(404, "Queue not found", classId, methodId)
	}

	if queue.ConnId != -1 && queue.ConnId != channel.conn.id {
		return amqp.NewSoftError(405, "Queue is locked to another connection", classId, methodId)
	}

	// Check exchange
	var exchange, foundExchange = channel.server.exchanges[method.Exchange]
	if !foundExchange {
		return amqp.NewSoftError(404, "Exchange not found", classId, methodId)
	}

	var binding, err = binding.NewBinding(
		method.Queue,
		method.Exchange,
		method.RoutingKey,
		method.Arguments,
		exchange.IsTopic(),
	)

	if err != nil {
		return amqp.NewSoftError(500, err.Error(), classId, methodId)
	}

	if queue.Durable && exchange.Durable {
		err := binding.Depersist(channel.server.db)
		if err != nil {
			return amqp.NewSoftError(500, "Could not de-persist binding!", classId, methodId)
		}
	}

	if err := exchange.RemoveBinding(binding); err != nil {
		return amqp.NewSoftError(500, err.Error(), classId, methodId)
	}
	channel.SendMethod(&amqp.QueueUnbindOk{})
	return nil
}
예제 #15
0
func (channel *Channel) addConsumer(q *queue.Queue, method *amqp.BasicConsume) *amqp.AMQPError {
	var classId, methodId = method.MethodIdentifier()
	// Create consumer
	var consumer = consumer.NewConsumer(
		channel.server.msgStore,
		method.Arguments,
		channel,
		method.ConsumerTag,
		method.Exclusive,
		method.NoAck,
		method.NoLocal,
		q,
		q.Name,
		channel.defaultPrefetchSize,
		channel.defaultPrefetchCount,
		channel.conn.id,
	)

	channel.consumerLock.Lock()
	defer channel.consumerLock.Unlock()
	// Make sure the doesn't exist on this channel
	_, found := channel.consumers[consumer.ConsumerTag]
	if found {
		return amqp.NewHardError(
			530,
			fmt.Sprintf("Consumer tag already exists: %s", consumer.ConsumerTag),
			classId,
			methodId,
		)
	}

	// Add the consumer to the queue, then channel
	code, err := q.AddConsumer(consumer, method.Exclusive)
	if err != nil {
		return amqp.NewSoftError(code, err.Error(), classId, methodId)
	}

	channel.consumers[consumer.ConsumerTag] = consumer
	consumer.Start()
	return nil
}
예제 #16
0
func (channel *Channel) commitTx() *amqp.AMQPError {
	channel.txLock.Lock()
	defer channel.txLock.Unlock()
	// messages
	queueMessagesByQueue, err := channel.server.msgStore.AddTxMessages(channel.txMessages)
	if err != nil {
		return amqp.NewSoftError(500, err.Error(), 60, 40)
	}

	for queueName, qms := range queueMessagesByQueue {
		queue, found := channel.server.queues[queueName]
		// the
		if !found {
			continue
		}
		for _, qm := range qms {
			if !queue.Add(qm) {
				// If we couldn't add it means the queue is closed and we should
				// remove the ref from the message store. The queue being closed means
				// it is going away, so worst case if the server dies we have to process
				// and discard the message on boot.
				var rhs = []amqp.MessageResourceHolder{channel}
				channel.server.msgStore.RemoveRef(qm, queueName, rhs)
			}
		}
	}

	// Acks
	// todo: remove acked messages from persistent storage in a single
	// transaction
	for _, ack := range channel.txAcks {
		channel.txAckMessage(ack)
	}

	// Clear transaction
	channel.txMessages = make([]*amqp.TxMessage, 0)
	channel.txAcks = make([]*amqp.TxAck, 0)
	return nil
}
예제 #17
0
func (server *Server) publish(exchange *exchange.Exchange, msg *amqp.Message) (*amqp.BasicReturn, *amqp.AMQPError) {
	// Concurrency note: Since there is no lock we can, technically, have messages
	// published after the exchange has been closed. These couldn't be on the same
	// channel as the close is happening on, so that seems justifiable.
	if exchange.Closed {
		if msg.Method.Mandatory || msg.Method.Immediate {
			var rm = server.returnMessage(msg, 313, "Exchange closed, cannot route to queues or consumers")
			return rm, nil
		}
		return nil, nil
	}
	queues, amqpErr := exchange.QueuesForPublish(msg)
	if amqpErr != nil {
		return nil, amqpErr
	}

	if len(queues) == 0 {
		// If we got here the message was unroutable.
		if msg.Method.Mandatory || msg.Method.Immediate {
			var rm = server.returnMessage(msg, 313, "No queues available")
			return rm, nil
		}
	}

	var queueNames = make([]string, 0, len(queues))
	for k, _ := range queues {
		queueNames = append(queueNames, k)
	}

	// Immediate messages
	if msg.Method.Immediate {
		var consumed = false
		// Add message to message store
		queueMessagesByQueue, err := server.msgStore.AddMessage(msg, queueNames)
		if err != nil {
			return nil, amqp.NewSoftError(500, err.Error(), 60, 40)
		}
		// Try to immediately consumed it
		for queueName, _ := range queues {
			qms := queueMessagesByQueue[queueName]
			for _, qm := range qms {
				queue, found := server.queues[queueName]
				if !found {
					// The queue must have been deleted since the queuesForPublish call
					continue
				}
				var oneConsumed = queue.ConsumeImmediate(qm)
				var rhs = make([]amqp.MessageResourceHolder, 0)
				if !oneConsumed {
					server.msgStore.RemoveRef(qm, queueName, rhs)
				}
				consumed = oneConsumed || consumed
			}
		}
		if !consumed {
			var rm = server.returnMessage(msg, 313, "No consumers available for immediate message")
			return rm, nil
		}
		return nil, nil
	}

	// Add the message to the message store along with the queues we're about to add it to
	queueMessagesByQueue, err := server.msgStore.AddMessage(msg, queueNames)
	if err != nil {
		return nil, amqp.NewSoftError(500, err.Error(), 60, 40)
	}

	for queueName, _ := range queues {
		qms := queueMessagesByQueue[queueName]
		for _, qm := range qms {
			queue, found := server.queues[queueName]
			if !found || !queue.Add(qm) {
				// If we couldn't add it means the queue is closed and we should
				// remove the ref from the message store. The queue being closed means
				// it is going away, so worst case if the server dies we have to process
				// and discard the message on boot.
				var rhs = make([]amqp.MessageResourceHolder, 0)
				server.msgStore.RemoveRef(qm, queueName, rhs)
			}
		}
	}
	return nil, nil
}
예제 #18
0
func (channel *Channel) exchangeDeclare(method *amqp.ExchangeDeclare) *amqp.AMQPError {
	var classId, methodId = method.MethodIdentifier()
	// The client I'm using for testing thought declaring the empty exchange
	// was OK. Check later
	// if len(method.Exchange) > 0 && !method.Passive {
	// 	var msg = "The empty exchange name is reserved"
	// 	channel.channelErrorWithMethod(406, msg, classId, methodId)
	// 	return nil
	// }

	// Check the name format
	var err = amqp.CheckExchangeOrQueueName(method.Exchange)
	if err != nil {
		return amqp.NewSoftError(406, err.Error(), classId, methodId)
	}

	// Declare!
	var ex, amqpErr = exchange.NewFromMethod(method, false, channel.server.exchangeDeleter)
	if amqpErr != nil {
		return amqpErr
	}
	tp, err := exchange.ExchangeNameToType(method.Type)
	if err != nil || tp == exchange.EX_TYPE_HEADERS {
		return amqp.NewHardError(503, err.Error(), classId, methodId)
	}
	existing, hasKey := channel.server.exchanges[ex.Name]
	if !hasKey && method.Passive {
		return amqp.NewSoftError(404, "Exchange does not exist", classId, methodId)
	}
	if hasKey {
		// if diskLoad {
		// 	panic(fmt.Sprintf("Can't disk load a key that exists: %s", ex.Name))
		// }
		if existing.ExType != ex.ExType {
			return amqp.NewHardError(530, "Cannot redeclare an exchange with a different type", classId, methodId)
		}
		if existing.EquivalentExchanges(ex) {
			if !method.NoWait {
				channel.SendMethod(&amqp.ExchangeDeclareOk{})
			}
			return nil
		}
		// Not equivalent, error in passive mode
		if method.Passive {
			return amqp.NewSoftError(406, "Exchange with this name already exists", classId, methodId)
		}
	}
	if method.Passive {
		if !method.NoWait {
			channel.SendMethod(&amqp.ExchangeDeclareOk{})
		}
		return nil
	}

	// outside of passive mode you can't create an exchange starting with
	// amq.
	if strings.HasPrefix(method.Exchange, "amq.") {
		return amqp.NewSoftError(403, "Exchange names starting with 'amq.' are reserved", classId, methodId)
	}

	err = channel.server.addExchange(ex)
	if err != nil {
		return amqp.NewSoftError(500, err.Error(), classId, methodId)
	}
	err = ex.Persist(channel.server.db)
	if err != nil {
		return amqp.NewSoftError(500, err.Error(), classId, methodId)
	}
	if !method.NoWait {
		channel.SendMethod(&amqp.ExchangeDeclareOk{})
	}
	return nil
}
예제 #19
0
func (channel *Channel) queueDeclare(method *amqp.QueueDeclare) *amqp.AMQPError {
	var classId, methodId = method.MethodIdentifier()
	// No name means generate a name
	if len(method.Queue) == 0 {
		method.Queue = util.RandomId()
	}

	// Check the name format
	var err = amqp.CheckExchangeOrQueueName(method.Queue)
	if err != nil {
		return amqp.NewSoftError(406, err.Error(), classId, methodId)
	}

	// If this is a passive request, do the appropriate checks and return
	if method.Passive {
		queue, found := channel.conn.server.queues[method.Queue]
		if found {
			if !method.NoWait {
				var qsize = uint32(queue.Len())
				var csize = queue.ActiveConsumerCount()
				channel.SendMethod(&amqp.QueueDeclareOk{method.Queue, qsize, csize})
			}
			channel.lastQueueName = method.Queue
			return nil
		}
		return amqp.NewSoftError(404, "Queue not found", classId, methodId)
	}

	// Create the new queue
	var connId = channel.conn.id
	if !method.Exclusive {
		connId = -1
	}
	var queue = queue.NewQueue(
		method.Queue,
		method.Durable,
		method.Exclusive,
		method.AutoDelete,
		method.Arguments,
		connId,
		channel.server.msgStore,
		channel.server.queueDeleter,
	)

	// If the new queue exists already, ensure the settings are the same. If it
	// doesn't, add it and optionally persist it
	existing, hasKey := channel.server.queues[queue.Name]
	if hasKey {
		if existing.ConnId != -1 && existing.ConnId != channel.conn.id {
			return amqp.NewSoftError(405, "Queue is locked to another connection", classId, methodId)
		}
		if !existing.EquivalentQueues(queue) {
			return amqp.NewSoftError(406, "Queue exists and is not equivalent to existing", classId, methodId)
		}
	} else {
		err = channel.server.addQueue(queue)
		if err != nil { // pragma: nocover
			return amqp.NewSoftError(500, "Error creating queue", classId, methodId)
		}
		// Persist
		if queue.Durable {
			queue.Persist(channel.server.db)
		}
	}

	channel.lastQueueName = method.Queue
	if !method.NoWait {
		channel.SendMethod(&amqp.QueueDeclareOk{queue.Name, uint32(0), uint32(0)})
	}
	return nil
}