英文:
Using golang's defer in a separate method
问题
我正在使用Go语言的RabbitMQ库进行项目开发,并且在一个单独的包中有一个名为Connect的函数。我在main函数中调用Connect,但是由于我在一个单独的函数中连接到RabbitMQ,所以defer conn.Close()函数被调用,这会在Connect函数内部关闭连接。这是有道理的,但是问题是,我应该在哪里调用conn.Close()呢?
你可以看到上面的代码中,我在建立连接后调用了defer conn.Close(),但是这会立即再次关闭连接。
这里有一个Go Playground的示例,模拟了我所说的情况... 链接
英文:
I'm using the golang RabbitMQ library in a project, and I have a Connect function in a separate package. I'm calling Connect, in my main function, however because I connect to RabbitMQ in a separate function, the defer conn.Close() function is called, which closes the connection within the Connect function. Which makes perfect sense, but that begs the question, where then, do I call conn.Close()?
package drivers
import (
	// Core
	"log"
	"os"
	"time"
	// Third party
	"github.com/streadway/amqp"
)
type Queue struct {
	Channel *amqp.Channel
}
func NewQueue() *Queue {
	return &Queue{}
}
// Queue interface
type IQueue interface {
	Connect(args ...interface{})
	Publish(queue string, payload []byte) error
	Listen(queue string) (<-chan amqp.Delivery, error)
	Declare(queue string) (amqp.Queue, error)
}
// Connect - Connects to RabbitMQ
func (queue *Queue) Connect(args ...interface{}) {
	var uri string
	if args == nil {
		// Get from env vars
		uri = os.Getenv("RABBIT_MQ_URI")
		if uri == "" {
			log.Panic("No uri for queue given")
		}
	} else {
		uri = args[0].(string)
	}
	// Make max 5 connection attempts, with a 1 second timeout
	for i := 0; i < 5; i++ {
		log.Println("Connecting to:", uri)
		// If connection is successful, return new instance
		conn, err := amqp.Dial(uri)
        defer conn.Close()
		if err == nil {
			log.Println("Successfully connected to queue!")
			channel, _ := conn.Channel()
			queue.Channel = channel
			return
		}
		log.Println("Failed to connect to queue, retrying...", err)
		// Wait 1 second
		time.Sleep(5 * time.Second)
	}
}
// Declare a new queue
func (queue *Queue) Declare(queueName string) (amqp.Queue, error) {
	return queue.Channel.QueueDeclare(
		queueName,
		true,
		false,
		false,
		false,
		nil,
	)
}
// Publish a message
func (queue *Queue) Publish(queueName string, payload []byte) error {
	return queue.Channel.Publish(
		"",
		queueName,
		false,
		false,
		amqp.Publishing{
			DeliveryMode: amqp.Persistent,
			ContentType:  "application/json",
			Body:         payload,
		},
	)
}
// Listen for a new message
func (queue *Queue) Listen(queueName string) (<-chan amqp.Delivery, error) {
	return queue.Channel.Consume(
		queueName,
		"",
		true,
		false,
		false,
		false,
		nil,
	)
}
As you can see in the code above, I'm calling defer conn.Close() after making a connection, however, this immediately closes the connection again.
Here's a Go Playground spoofing what I'm talking about... https://play.golang.org/p/5cz2D4gDgn
答案1
得分: 1
简单的解决方案是从其他地方调用conn.Close()。也许只是我觉得有点奇怪,你不会在Queue中将连接暴露出来,即作为一个字段。从队列中暴露关闭连接的能力将解决这个问题并提供更多的灵活性。
所以可以这样做:
type Queue struct {
    // 你原来的字段
    Conn amqp.Connection
}
// 在其他地方
queue.Conn.Close()
你的另一个选择是连接,然后使用该连接执行所有你想要的操作,然后关闭连接。我想的是这样的:
func action(conn amqp.Connection, args ...interface{}) (<-chan bool) {
    done := make(chan bool)
    go func(amqpConn amqp.Connection, dChan chan bool){
        // 使用连接执行你想要的操作
        dChan <- true
    }(conn, done)
    return done
}
func (queue *Queue) Connect(args ...interface{}) {
     // 你的连接代码
     doneChans := make([](chan bool), 5)
     for i := 0; i < 5; i++ {
          conn, err := amqp.Dial(uri)
          defer conn.Close()
          if err != nil {
              // 处理错误
          }
          done := action(conn)
     }
     // 这个循环将阻塞,直到5个操作调用完成
     for j := range doneChans {
         isFinish := <-doneChans[j]
         if !isFinish {
             // 处理错误状态
         }
     }
}
英文:
The simple solution is to call conn.Close() from elsewhere.  This might just be me, but I think it's kinda odd that you wouldn't expose the connection elsewhere, i.e. as a field in Queue.  Exposing the ability to close the connection from the Queue would solve this and give you more flexibility.
So this:
type Queue struct {
    // your original fields
    Conn amqp.Connection
}
// Somewhere else
queue.Conn.Close()
You're other option is connecting, then doing all the actions you want with that connection, then closing. I'm thinking something like:
func action(conn amqp.Connection, args ...interface{}) (<-chan bool) {
    done := make(chan bool)
    go func(amqpConn amqp.Connection, dChan chan bool){
        // Do what you want with the connection
        dChan <- true
    }(conn, done)
    return done
}
func (queue *Queue) Connect(args ...interface{}) {
     // your connection code
     doneChans := make([](chan bool), 5)
     for i := 0; i < 5; i++ {
          conn, err := amqp.Dial(uri)
          defer conn.Close()
          if err != nil {
              // handle error
          }
          done := action(conn)
     }
     // This for loop will block until the 5 action calls are done
     for j := range doneChans {
         isFinish := <-doneChans[j]
         if !isFinish {
             // handle bad state
         }
     }
}
答案2
得分: 1
一种选择是让Connect函数返回conn,并在调用者中使用defer conn.Close()。
package driver
// 导入等
func (queue *Queue) Connect(args ...interface{}) (amqp.Connection, error) {
    // ...
    conn, err := amqp.Dial(uri)
    if err != nil {
        return nil, err
    }
    // ...
    return conn, nil
}
然后在另一个包中:
package stuff
// 导入等
func doStuff() {
    queue := driver.NewQueue()
    conn, err := queue.Connect(args...)
    if err != nil {
        log.Fatalf("出错了!%v!", err)
    }
    defer conn.Close()
    // 做一些事情
}
英文:
One option is to have Connect return conn, and call defer conn.Close() in the caller.
package driver
// imports, etc
func (queue *Queue) Connect(args ...interface{}) amqp.Connection, error {
    // ...
    conn, err := amqp.Dial(uri)
    if err != nil {
        return nil, err
    }
    // ...
    return conn, nil
}
Then in another package:
package stuff
// imports, etc
func doStuff() {
    queue = driver.NewQueue()
    conn, err := queue.Connect(args...)
    if err != nil {
        log.Fatalf("oh no! %v!", err)
    }
    defer conn.Close()
    // Do stuff
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论