hotswap

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 19, 2023 License: BSD-3-Clause Imports: 21 Imported by: 0

README

Banner

简体中文版

Hotswap provides you a complete solution to reload your go code without restarting your server, interrupting or blocking any ongoing procedure. Hotswap is built upon the plugin mechanism.

Major Features

  • Reload your code like a breeze
  • Run different versions of a plugin in complete isolation
  • Invoke an in-plugin function from its host program with Plugin.InvokeFunc()
  • Expose in-plugin data and functions with PluginManager.Vault.Extension and/or PluginManager.Vault.DataBag
  • Handle asynchronous jobs using the latest code with live function, live type, and live data
  • Link plugins statically for easy debugging
  • Expose functions to other plugins with Export()
  • Depend on other plugins with Import()

Getting Started

go install github.com/edwingeng/hotswap/cli/hotswap

Build a Plugin from Source Code

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

Demos

You can find these examples under the demo directory. To have a direct experience, start a server with run.sh and reload its plugin(s) with reload.sh.

  1. hello demonstrates the basic usage, including how to organize host and plugin, how to build them, how to load plugin on server startup, how to use InvokeEach, and how to reload.
  2. extension shows how to define a custom extension and how to use PluginManager.Vault.Extension. A small hint: WithExtensionNewer()
  3. livex is somewhat complex. It shows how to work with live function, live type, and live data.
  4. slink is an example of plugin static-linking, with which debugging a plugin with a debugger (delve) under MacOS and Windows becomes possible.
  5. trine is the last example. It demonstrates the plugin dependency mechanism.

Required Functions

A plugin must have the following functions defined in its root 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
}

Order of Execution during Plugin Reload

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

Attentions

  • Build your host program with the environmental variable CGO_ENABLED=1 and the -trimpath flag.
  • Do not define any global variable in a reloadable plugin unless it can be discarded at any time or it actually never changes.
  • Do not create any long-running goroutine in a plugin, it's error-prone.
  • The same type in different versions of a plugin are actually not the same at runtime. Use live function, live type, and live data to avoid the trap.
  • The code of your host program should never import any package of any plugin and the code of a plugin should never import any package of other plugins.
  • Old versions won't be removed from the memory due to the limitation of golang plugin. However, Hotswap offers you a chance, the OnFree function, to clear caches.
  • It is required to manage your code with git and go module.
  • It is highly recommended to keep the code of your host program and all its plugins in a same repository.

Live Things

  • live function is a type of function whose name is prefixed with live_ (case-insensitive). Live functions are automatically collected and stored in PluginManager.Vault.LiveFuncs. For example:
func live_Foo(jobData live.Data) error {
      return nil
}
  • live type is a type of struct whose name is prefixed with live_ (case-insensitive). Live types are automatically collected and stored in PluginManager.Vault.LiveTypes. For example:
type Live_Bar struct {
      N int
}
  • live data is a type guardian. You can convert your data into a live data object when scheduling an asynchronous job and restore your data from the live data object when handling the job.
  • See the demo livex for details.

Documentation

Index

Constants

This section is empty.

Variables

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

Functions

This section is empty.

Types

type Details

type Details map[string]string

func (Details) String

func (d Details) String() string

type Option

type Option func(mgr *PluginManagerSwapper)

func WithExtensionNewer

func WithExtensionNewer(newExt func() interface{}) Option

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

func WithFreeDelay

func WithFreeDelay(d time.Duration) Option

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

func WithLogger

func WithLogger(log slog.Logger) Option

WithLogger replaces the default logger with your own.

func WithReloadCallback

func WithReloadCallback(cb ReloadCallback) Option

WithReloadCallback sets the callback function of reloading.

func WithStaticPlugins

func WithStaticPlugins(plugins map[string]*StaticPlugin) Option

WithStaticPlugins sets the static plugins for static linking.

func WithWhitelist

func WithWhitelist(pluginNames ...string) Option

WithWhitelist sets the plugins to load explicitly

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 ...Option) *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) StaticLinkingMode

func (sw *PluginManagerSwapper) StaticLinkingMode() bool

type ReloadCallback

type ReloadCallback func(newManager, oldManager *PluginManager) error

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