Speaking of plug-ins, many people are not unfamiliar with this thing, in general, plug-in has several benefits, one is to increase the program scalability, rich functionality. In addition, you can also achieve hot updates, some large applications, often several GB of installation procedures, if a small update will need to re-download the entire program, at this time we can update the module plug-in often, so that when the update only need to download a small update file. For example, usually our Chrome browser will be installed with some plug-ins, which can extend the browser to achieve more functions, but also the flexibility to install and uninstall.

Golang provides a Plugin mechanism after version 1.8 to dynamically load so files and implement plugins, which is not very mature, but still very useful in certain situations.

Currently plugins are only supported on Linux, FreeBSD, and macOS.

1. Quick start

The plug-in code is not different from the normal code, only that it is compiled differently, but the requirement is that there must be only one main package.

1
2
3
4
5
6
7
package main

var Name = "Plugin Name"

func GetName() string {
    return Name
}

Compile with go build -buildmode=plugin, you will get a so file, how to use this file?

Very simple, in three steps.

  1. first open the so file, if a plug-in has been opened, then it will return the existing plugin
  2. Use Lookup to find the need to call the variable or function, the name must begin with capitalization
  3. Assert and call
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func main() {
    //Open the load plugin, the parameter is the storage location of the plugin, it can be a relative path
    open, err := plugin.Open("/home/jwang/Documents/plg.so")
    if err != nil {
        panic(err)
    }
    //Find Identifier
    lookup, err := open.Lookup("GetName")
    if err != nil {
        panic(err)
    }
    res := lookup.(func() string)()
    fmt.Printf("%v\n", res)

    name, err := open.Lookup("Name")
    if err != nil {
        panic(err)
    }
    fmt.Printf("%v\n", *name.(*string))
}

As you can see from the code above, the use of plug-ins is very plain and simple to understand.

In general, in order to achieve plug-in, you can define some interfaces in advance, and then the plug-in to implement these interfaces, so as to ensure consistency, but the definition of the interface can not be written in the plug-in package or call package inside. This time you need to define a special public package to write the definition of the interface in it, so that the plug-in package and the call package can be referenced.

2. Notes

The reason why this plugin solution is immature is mainly due to the strong dependencies between the main program and the plugin program, such as

  1. the compiled GO version must be exactly the same
  2. Both sides rely on the public third-party library version must be identical
  3. GOPATH must also be consistent, which can be solved by using the trimpath parameter at compile time
  4. plug-ins can not be uninstalled after loading

These problems do not seem to be officially solved in a short time, or can not be solved. In short, Go plugin is currently used very little, after all, as a web programming language, it is an easy thing to update the program in an environment where containerization is all the rage, unless there is a special need.

3. Hidden traps

1
2
3
4
5
6
// Open opens a Go plugin.
// If a path has already been opened, then the existing *Plugin is returned.
// It is safe for concurrent use by multiple goroutines.
func Open(path string) (*Plugin, error) {
    return open(path)
}

Note the above comment, what does this mean?

Assuming your service is a resident process, it will regularly check if the plugin is updated, and if there is an update, it will pull the new plugin to the specified directory and load the plugin.

The above idea is very nice, but you will find that in fact the plug-in is not updated at all, you are still using the old plug-in, although the so file is already the latest, but Golang does not load the new plug-in.

Because it only recognizes the path, in other words, the file name, so if the plug-in needs to be updated, be sure to bring the version number on top of the file name, in other words, in a process, plug-ins can only be added, not updated, unless your service restart.

This is really very inconvenient. The reason for this may be that the official does not care about it!