This is a creation in Article, where the information may have evolved or changed.
The plugin package is added to Go 1.8, but only the Linux operating system is supported, and there are some known bugs. It can be said that the implementation of this plug-in system has not reached the "product-level" level.
The plugin are currently incomplete, only supports Linux, and have known bugs.
Some known bugs have been pushed to Go1.10 even later in the release.
Today, when testing the functions in Go 1.9, I encountered a bug in plugin.
According to the official documentation, developing a plugin is simple:
Plugin1/main.go
1234567 |
Package mainimport"FMT"varintfunc F () {fmt. Printf ("Hello, Number%d\n", V)} |
Variables and methods are defined in the plug-in V
F
, and a file can be generated using the following command so
:
1 |
Go build-buildmode=plugin-o. /p1.so Main.go |
The plugin
plugin can then be loaded via the package:
main.go
1234567891011121314 |
p, err: = plugin. Open ( "p1.so" ) if err! = nil {panic (Err)}v, err: = P.lookup () if err! = nil {panic (Err) }f, err: = P.lookup () if err! = Nil {panic (Err)}*v. (*int ) = 7 F. (func ()) () //prints "Hello, Number 7 " |
Of course, as a plug-in system, we want to be able to load a new plug-in to replace the existing plug-in, if you copy p1.so
as p2.so
, and then load the above test code p2.so
will be error:
main.go
123456789101112 |
P, err: = plugin. Open ("p1.so")ifnil {panic(Err)}......p, err = plugin. Open ("p2.so")ifnil {panic(ERR)} |
The error message is as follows:
12345 |
go run main. Go Hello, number 7plugin:plugin plugin/unnamed-f0c47a2a99a0d8e8fb40defabb50f238c78f5d58 already loadedfatal Error:plugin:plugin already loaded ... |
This step we can also understand that the same plugin even if the file name changes, loaded into the same, so will report plugin already loaded
errors.
Let plugin1/main.go
's change the code in a little bit :
Plugin1/main.go
1234567 |
Package mainimport"FMT"varintfunc F () {fmt. Printf ("Hello World, %d\n", V)} |
Then build the plugin p2.so
:
1 |
Go build-buildmode=plugin-o. /p2.so Main.go |
Supposedly this time we changed the code, generated a new plug-in, if the code load both plug-ins at the same time, because there is no problem, but run the above load two plug-in test code, found error:
12345 |
Run Number 7 error: Plugin:plugin already loaded ... |
Weird bar, two different code generation plugin, incredibly is considered to be the same plug-in (plugin/unnamed-f0c47a2a99a0d8e8fb40defabb50f238c78f5d58).
Use nm
the symbol table to view two plugins:
123456789101112131415161718192021 |
[Root@colobu T]# nm p1.so |grep unname00000000001ACFC0 R go.link.pkghashbytes.plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d5800000000003f9620 D go.link.pkghash.plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d580000000000199080T local.plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58. F0000000000199130T local.plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58.init0000000000199080T plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58. F0000000000199130T plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58.init000000000048e027 B plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58.initdone000000000048e088 B plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58. V[root@colobu T]#[Root@colobu T]#[Root@colobu T]# nm p2.so |grep unname00000000001ACFC0 R go.link.pkghashbytes.plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d5800000000003f9620 D go.link.pkghash.plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d580000000000199080T local.plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58. F0000000000199130T local.plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58.init0000000000199080T plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58. F0000000000199130T plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58.init000000000048e027 B plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58.initdone000000000048e088 B plugin/unnamed- F0c47a2a99a0d8e8fb40defabb50f238c78f5d58. V[root@colobu T]# |
You can see that the symbol table symbol tables generated in the two plugins are the same, so they are mistaken for the same plugin.
This situation is generated in special cases, if the file name of two plug-ins are different, or the reference package is different, or the reference cgo is different, it will generate a different plug-in, loading will not be a problem. However, if the file name is the same and the related references are the same, the same plug-in may be generated, although the plug-in contains different methods and variables, and the implementation is different.
This is when go plugin generated a bug:issue#19358, expect to solve in Go 1.10, the current solution is that the plugin's go file uses a different name, or compile the time specified pluginpath
:
12 |
"-PLUGINPATH=P1" "-pluginpath=p2"-buildmode=plugin-o. /p2.so Main.go |
The cause of the problem is as Lionnatsu pointed out in the bug, Go to determine whether the two plug-ins are the same by comparison Pluginpath implementation, if you specify a different when compiling pluginpath
, the compiled plug-in is different, but if not specified pluginpath
, is generated by an internal algorithm, and the resulting format is plugin/unnamed-" + root.Package.Internal.BuildID
.
The Func Computebuildid (P *package) generates a SHA-1 hash value as a buildid.
Go/src/cmd/go/internal/load/pkg.go
12345678910111213141516171819202122232425262728293031323334353637383940414243444546 |
funcComputebuildid (P *package) {h: = SHA1. New ()//Include The list of files compiled as part of the package.//This lets us detect removed files. See issue 3895.Inputfiles: = str. Stringlist (p.gofiles,p.cgofiles,p.cfiles,p.cxxfiles,p.mfiles,p.hfiles,p.sfiles,p.sysofiles,p.swigfiles,p. Swigcxxfiles,) for_, File: =RangeInputfiles {fmt. fprintf (H,"File%s\n", file)}//Include The content of Runtime/internal/sys/zversion.go in the hash//For the package runtime. This would give package runtime a//different build ID in each Go release.ifP.standard && P.importpath = ="Runtime/internal/sys"&& Cfg.BuildContext.Compiler! ="Gccgo"{data, err: = Ioutil. ReadFile (filepath. Join (P.dir,"Zversion.go"))ifErr! =Nil{base. Fatalf ("Go:%s", err)}fmt. fprintf (H,"Zversion%q\n",string(data))}//Include The build IDs of any dependencies in the hash.//This, combined with the runtime/zversion content,//would cause packages to has different build IDs when//compiled with different Go releases.//This helps the "Go command know to recompile " when//People use the same gopath but switch between//different Go releases. See issue 10702.//This was also a better fix for issue 8290. for_, p1: =Rangep.internal.deps {fmt. fprintf (H,"dep%s%s\n", p1. Importpath, p1. Internal.buildid)}p.internal.buildid = FMT. Sprintf ("%x", H.sum (Nil))} |
The latter part of the function generates different hashes for different versions of Go, preventing the user from using different go versions to generate the same ID. Focus on the first half, you can find that the calculation of the hash only depends on the file name, do not care about the contents of the document, which is the same as before we slightly modify the plugin code will generate the same reason, if you in the code will import _ "fmt"
also produce a different plug-in.
In short, before go 1.10, in order to avoid plug-ins conflict, it is best to compile the time specified pluginpath
, such as:
1 |
"-pluginpath=plugin/hot-$ (date +%s)" -buildmode=plugin-o hotload.so hotload.go |