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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }