hzDocs

Auto-close the Closers

Managing the resource lifecycle

介绍

在应用程序终止时,也即当子命令的 OnAction 结束返回之后,cmdr 将会调用 OnPostAction 回调,然后结束 App 对象的生命周期,在最后 cmdr 也会通过依次调用所有的 Close() 来关闭所有 Closers。

Closers 是一个全局的对象数组,这里登记注册了一切你想要自动关闭的资源型对象。

例如一个数据库连接对象,你可以通过实现 Peripheral 接口 Close() 并将该对象登记到 Closers 中,然后则无需关心相关资源的回收问题了。

当然,实际上回收任务将在 Close() 中被调用,而调用的时机就在 cmdr 快要结束运行之前。

下面的代码展示了如何管理资源型对象:

./examples/auto-close-closers/main.go
package main
 
import (
	"context"
	"fmt"
	"os"
 
	loaders "github.com/hedzr/cmdr-loaders"
	"github.com/hedzr/cmdr/v2"
	"github.com/hedzr/cmdr/v2/cli"
	"github.com/hedzr/cmdr/v2/examples/devmode"
	"github.com/hedzr/is/basics"
	logz "github.com/hedzr/logg/slog"
 
	"database/sql"
 
	_ "github.com/lib/pq"
)
 
type dbConn struct {
	conn *sql.DB
}
 
func (s *dbConn) Close() {
	// here's cleanup operations to free the conn object
	if s.conn != nil {
		if err := s.conn.Close(); err != nil {
			cmdr.Recycle(err)
		} else {
			s.conn = nil
			logz.Info(`database connection closed`)
		}
	}
}
 
func (s *dbConn) Open(ctx context.Context) (err error) {
	// do stuffs to open connection to database here
 
	// locate to `app.resources.db.postgres`
	conf := cmdr.Set().WithPrefix("resources.db.postgres")
	host := conf.MustString("host", "127.0.0.1")
	port := conf.MustInt("port", 5432)
	user := conf.MustString("user", "postgres")
	password := conf.MustString("password", "postgres")
	dbname := conf.MustString("db", "postgres")
 
	psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
		"password=%s dbname=%s sslmode=disable",
		host, port, user, password, dbname)
 
	logz.Info(`opening database...`, "connString", psqlInfo)
 
	s.conn, err = sql.Open("postgres", psqlInfo)
	if err != nil {
		return
	}
 
	logz.Info(`database connection opened`)
	err = s.conn.Ping()
	return
}
 
func (s *dbConn) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
	if s.conn == nil {
		return nil, fmt.Errorf("database connection not ready")
	}
	return s.conn.QueryContext(ctx, query, args...)
}
 
func main() {
	// loaders.Create() will load config files if found.
	app := loaders.Create(appName, version, author, desc).WithOpts(
		cmdr.WithPeripherals(map[string]basics.Peripheral{"db": &dbConn{}}),
	).With(func(app cli.App) {
		logz.Debug("in dev mode?", "mode", devmode.InDevelopmentMode())
		app.OnAction(func(ctx context.Context, cmd cli.Cmd, args []string) (err error) {
			db := cmdr.PeripheralT[*dbConn]("db")
			if db != nil {
				var rows *sql.Rows
				if rows, err = db.QueryContext(ctx, `SELECT name FROM info`); err == nil {
					defer rows.Close()
					if rows.Next() {
						var str string
						if err = rows.Scan(&str); err == nil {
							println(fmt.Sprintf("info: %s", str))
							return
						}
					}
				}
			}
			println("onAction")
			return
		})
	}).WithAdders(
	// examples.AddTypedFlags,
	).Build()
 
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
 
	if err := app.Run(ctx); err != nil {
		logz.ErrorContext(ctx, "Application Error:", "err", err) // stacktrace if in debug mode/build
		os.Exit(app.SuggestRetCode())
	} else if rc := app.SuggestRetCode(); rc != 0 {
		os.Exit(rc)
	}
}
 
const (
	appName = "auto-close-closers"
	desc    = `a sample to show u how to manage the resource objects`
	version = cmdr.Version
	author  = `The Example Authors`
)

作为资源型对象的 dbConn 实例,由于它实现了 Open(context.Context) error,因此 cmdr.WithPeripherals() 完成两件事:

  1. 将其登记到 is.Closers() 之中,实现自动释放资源和关闭对象
  2. 将其登记到 pre-run 清单中(通过 WithTasksSetupPeripherals()),在子命令将要运行之前自动调用 Open()

说明

在上面的示例程序中,app.OnAction() 闭包定义了根命令的响应函数,其中展示了怎么获取一个 Peripheral 对象并操作它。

dbConn 是一个数据库连接的管理对象,它实现了 Peripheral.Close() 接口,并且也实现了 cmdr 要求的 Open(ctx) error 接口。

因此,在 cmdr 命令将要被实际执行之前,&dbConn{} 这个实例将获得 Open() 机会,从而完成其自身的资源初始化。

而当 cmdr 命令实际执行完成之后,将要退出到 OS 环境之前,其 Close() 将被调用,从而完成资源清理工作。

收益

对于微服务 app 来说,WithPeripherals 是一个有利的辅助管理工具。

How is this guide?

Edit on GitHub

Last updated on

On this page