hotswap

package module
v1.1.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2024 License: BSD-3-Clause Imports: 22 Imported by: 0

README

Banner

Hotswapgo 语言提供了一套相当完整的代码热更解决方案,热更过程不会中断或阻塞任何执行中的函数,更不会重启服务器。此方案建立在 go 语言的 plugin 机制之上。

核心功能

  • 轻松热更代码
  • 完全隔离新老版本
  • 通过 Plugin.InvokeFunc() 从宿主调用插件中的函数
  • 通过 PluginManager.Vault.ExtensionPluginManager.Vault.DataBag 向宿主暴露插件中的数据和函数
  • 借助 live functionlive typelive data 用最新代码执行异步任务
  • 支持静态链接插件,以方便调试
  • 通过 Export() 向其它插件暴露函数
  • 通过 Import() 声明、建立对其它插件的依赖

安装

go install github.com/sandwich-go/hotswap/cli/hotswap

编译插件

Usage:
  hotswap build [flags] <pluginDir> <outputDir> -- [buildFlags]

Examples:
hotswap build plugin/foo bin
hotswap build -v plugin/foo bin -- -race
hotswap build --staticLinking plugin/foo plugin

Flags:
      --debug               enable debug mode
      --exclude string      go-regexp matching files to exclude from included
      --goBuild             if --goBuild=false, skip the go build procedure (default true)
  -h, --help                help for build
      --include string      go-regexp matching files to include in addition to .go files
      --leaveTemps          do not delete temporary files
      --livePrefix string   case-insensitive name prefix of live functions and live types (default "live_")
      --staticLinking       generate code for static linking instead of building a plugin
  -v, --verbose             enable verbose mode

示例

你可以在 demo 目录下找到这些例子。为了更直观的体验,运行 run.sh 启动服务器,再运行 reload.sh 热更插件。

  1. hello 展示了这套方案的基本用法, 包括怎样组织宿主和插件、怎样编译宿主和插件、怎样在服务器启动时加载插件、怎样使用 InvokeEach、以及怎样热更。
  2. extension 是个关于自定义扩展的例子,它可以告诉你 PluginManager.Vault.Extension 的用法。小提示: WithExtensionNewer()
  3. livex 比较复杂. 它展示了 live function, live typelive data 的用法。
  4. slink 展示了静态链接的使用方法。在 MacOS 和 Windows 下,用静态链接才能上调试器(delve)调试。
  5. trine 是最后一个例子,它展示了插件的依赖机制。

必须定义的函数

每个插件都要在其根 package 下定义以下函数:

// OnLoad gets called after all plugins are successfully loaded and before the Vault is initialized.
func OnLoad(data interface{}) error {
    return nil
}

// OnInit gets called after the execution of all OnLoad functions. The Vault is ready now.
func OnInit(sharedVault *vault.Vault) error {
    return nil
}

// OnFree gets called at some time after a reload.
func OnFree() {
}

// Export returns an object to export to other plugins.
func Export() interface{} {
    return nil
}

// Import returns an object indicating the dependencies of the plugin.
func Import() interface{} {
    return nil
}

// InvokeFunc invokes the specified function.
func InvokeFunc(name string, params ...interface{}) (interface{}, error) {
    return nil, nil
}

// Reloadable indicates whether the plugin is reloadable.
func Reloadable() bool {
    return true
}

插件加载过程中上述函数的执行顺序

1. Reloadable
2. Export
3. Import
4. OnLoad
5. Vault Initialization
6. OnInit

注意事项

  • 编译宿主程序时,要加上环境变量 CGO_ENABLED=1,并指定编译参数 -trimpath
  • 不要在可热更的插件里定义全局变量,除非这些变量从不改变,或(随时)丢弃其值无不良影响。
  • 不要在插件里启动长时间运行的 goroutine,否则可能导致部分代码无法热更。
  • 小心那些在插件里定义的类型,程序运行时,go 认为不同插件版本中的同一类型是不同类型,跨版本赋值、拆箱是行不通的。你可以借助 live function, live typelive data 规避这一陷阱。
  • 宿主代码不要 import 任何插件的任何 package;任何插件都不要 import 其它插件的任何 package。
  • 热更后,旧版插件会继续留在内存中,永不释放,这是 plugin 的限制导致的。不过你有个清理缓存的机会:OnFree
  • 必须用 gitgo module 管理代码。
  • 强烈建议:用同一个代码仓库管理宿主程序和所有插件。

Live Things

  • live function 是以 live_ 为名字前缀(大小写不敏感)的函数,所有这类函数都会被自动收集起来并存入 PluginManager.Vault.LiveFuncs。例如:
func live_Foo(jobData live.Data) error {
      return nil
}
  • live type 是以 live_ 为名字前缀(大小写不敏感)的(struct)类型,所有这类 struct 都会被自动收集起来并存入 PluginManager.Vault.LiveTypes。例如:
type Live_Bar struct {
      N int
}
  • live data 是个类型隔离器。你可以在创建异步任务时把任务数据转成 live data 对象,再在执行任务时把数据恢复回来。
  • 例子 livex 包含更多细节。

FAQ

  • 怎样用调试器调试插件?

构建plugin时用静态链接 --staticLink,会使用模版生成hotswap.staticPlugins.go。在宿主代码中传入hotswap.WithStaticPlugins, 会将插件代码编译到宿主程序中运行,调用方式和plugin so模式一致。更多信息请参考演示程序 slink

  • hotswap 能在 Windows 上工作吗?

可以,构建时用静态链接 --staticLink。不过,在 Windows 上无法热更,因为 go 语言的 plugin 机制不支持 Windows。

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotExist = errors.New("symbol does not exist")
)

Functions

func InstallSpecWatchDog added in v1.1.5

func InstallSpecWatchDog(dog func(cc *Spec))

InstallSpecWatchDog the installed func will called when NewSpec called

func SpecOptionDeclareWithDefault added in v1.1.5

func SpecOptionDeclareWithDefault() interface{}

Types

type Details

type Details map[string]string

func (Details) String

func (d Details) String() string

type Plugin

type Plugin struct {
	Name     string
	File     string
	FileSha1 [sha1.Size]byte
	When     time.Time
	Note     string

	P           *plugin.Plugin `json:"-"`
	PluginFuncs `json:"-"`
	Deps        []string
	Refs        *atomic.Int64 `json:"-"`
	// contains filtered or unexported fields
}

func (*Plugin) Lookup

func (pl *Plugin) Lookup(symName string, out interface{}) error

type PluginFuncs

type PluginFuncs struct {
	InvokeFunc func(name string, params ...interface{}) (interface{}, error)
	// contains filtered or unexported fields
}

func NewPluginFuncs

func NewPluginFuncs(
	fExport func() interface{},
	hotswapLiveFuncs func() map[string]interface{},
	hotswapLiveTypes func() map[string]func() interface{},
	fImport func() interface{},
	InvokeFunc func(name string, params ...interface{}) (interface{}, error),
	fOnFree func(),
	fOnInit func(sharedVault *vault.Vault) error,
	fOnLoad func(data interface{}) error,
	fReloadable func() bool,
) PluginFuncs

type PluginManager

type PluginManager struct {
	slog.Logger

	vault.Vault
	// contains filtered or unexported fields
}

func (*PluginManager) FindPlugin

func (pm *PluginManager) FindPlugin(name string) *Plugin

func (*PluginManager) InvokeEach

func (pm *PluginManager) InvokeEach(name string, params ...interface{})

func (*PluginManager) InvokeEachBackward

func (pm *PluginManager) InvokeEachBackward(name string, params ...interface{})

func (*PluginManager) Plugins

func (pm *PluginManager) Plugins() []*Plugin

type PluginManagerSwapper

type PluginManagerSwapper struct {
	slog.Logger
	// contains filtered or unexported fields
}

func NewPluginManagerSwapper

func NewPluginManagerSwapper(pluginDir string, opts ...SpecOption) *PluginManagerSwapper

func (*PluginManagerSwapper) Current

func (sw *PluginManagerSwapper) Current() *PluginManager

func (*PluginManagerSwapper) LoadPlugins

func (sw *PluginManagerSwapper) LoadPlugins(data interface{}) (Details, error)

func (*PluginManagerSwapper) Reload

func (sw *PluginManagerSwapper) Reload(data interface{}) (Details, error)

func (*PluginManagerSwapper) ReloadCounter

func (sw *PluginManagerSwapper) ReloadCounter() int64

func (*PluginManagerSwapper) ReloadWithCallback

func (sw *PluginManagerSwapper) ReloadWithCallback(data interface{}, extra ReloadCallback) (Details, error)

func (*PluginManagerSwapper) ResetPluginDir

func (sw *PluginManagerSwapper) ResetPluginDir(pluginDir string)

func (*PluginManagerSwapper) StaticLinkingMode

func (sw *PluginManagerSwapper) StaticLinkingMode() bool

type ReloadCallback

type ReloadCallback func(newManager, oldManager *PluginManager) error

type Spec added in v1.1.5

type Spec struct {
	Logger         slog.Logger              `usage:"replaces the default logger with your own."`                                  // annotation@Logger(comment="replaces the default logger with your own.")
	FreeDelay      time.Duration            `usage:"the delay time of calling OnFree. The default value is 5 minutes."`           // annotation@FreeDelay(comment="the delay time of calling OnFree. The default value is 5 minutes.")
	ReloadCallback ReloadCallback           `usage:"the callback function of reloading."`                                         // annotation@ReloadCallback(comment="the callback function of reloading.")
	ExtensionNewer func() interface{}       `usage:"the function used to create a new object for PluginManager.Vault.Extension."` // annotation@ExtensionNewer(comment="the function used to create a new object for PluginManager.Vault.Extension.")
	StaticPlugins  map[string]*StaticPlugin `usage:"the static plugins for static linking. 宿主程序直接编译的插件 用做debug和windows"`          // annotation@StaticPlugins(comment="the static plugins for static linking. 宿主程序直接编译的插件 用做debug和windows")
	Whitelist      []string                 `usage:"the plugins to load explicitly 若不为空 只加载白名单里的插件"`                              // annotation@Whitelist(comment="the plugins to load explicitly 若不为空 只加载白名单里的插件")
}

Spec should use NewSpec to initialize it

func NewSpec added in v1.1.5

func NewSpec(opts ...SpecOption) *Spec

NewSpec new Spec

func (*Spec) ApplyOption added in v1.1.5

func (cc *Spec) ApplyOption(opts ...SpecOption) []SpecOption

ApplyOption apply multiple new option and return the old ones sample: old := cc.ApplyOption(WithTimeout(time.Second)) defer cc.ApplyOption(old...)

func (*Spec) GetExtensionNewer added in v1.1.5

func (cc *Spec) GetExtensionNewer() func() interface{}

func (*Spec) GetFreeDelay added in v1.1.5

func (cc *Spec) GetFreeDelay() time.Duration

func (*Spec) GetLogger added in v1.1.5

func (cc *Spec) GetLogger() slog.Logger

all getter func

func (*Spec) GetReloadCallback added in v1.1.5

func (cc *Spec) GetReloadCallback() ReloadCallback

func (*Spec) GetStaticPlugins added in v1.1.5

func (cc *Spec) GetStaticPlugins() map[string]*StaticPlugin

func (*Spec) GetWhitelist added in v1.1.5

func (cc *Spec) GetWhitelist() []string

type SpecInterface added in v1.1.5

type SpecInterface interface {
	SpecVisitor
	ApplyOption(...SpecOption) []SpecOption
}

SpecInterface visitor + ApplyOption interface for Spec

type SpecOption added in v1.1.5

type SpecOption func(cc *Spec) SpecOption

SpecOption option func

func WithExtensionNewer

func WithExtensionNewer(v func() interface{}) SpecOption

WithExtensionNewer the function used to create a new object for PluginManager.Vault.Extension.

func WithFreeDelay

func WithFreeDelay(v time.Duration) SpecOption

WithFreeDelay the delay time of calling OnFree. The default value is 5 minutes.

func WithLogger

func WithLogger(v slog.Logger) SpecOption

WithLogger replaces the default logger with your own.

func WithReloadCallback

func WithReloadCallback(v ReloadCallback) SpecOption

WithReloadCallback the callback function of reloading.

func WithStaticPlugins

func WithStaticPlugins(v map[string]*StaticPlugin) SpecOption

WithStaticPlugins the static plugins for static linking. 宿主程序直接编译的插件 用做debug和windows

func WithWhitelist

func WithWhitelist(v ...string) SpecOption

WithWhitelist the plugins to load explicitly 若不为空 只加载白名单里的插件

type SpecVisitor added in v1.1.5

type SpecVisitor interface {
	GetLogger() slog.Logger
	GetFreeDelay() time.Duration
	GetReloadCallback() ReloadCallback
	GetExtensionNewer() func() interface{}
	GetStaticPlugins() map[string]*StaticPlugin
	GetWhitelist() []string
}

SpecVisitor visitor interface for Spec

type StaticPlugin

type StaticPlugin struct {
	Name string
	PluginFuncs
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL