-
Notifications
You must be signed in to change notification settings - Fork 17.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
plugin: Utility of Go plug-ins diminished by vendored dependencies #20481
Comments
I also discovered this oddity: in addition to a plug-in needing to be built against the same sources, the value of # extract the GOPATH to two temp dirs
$ tar xzf ../rexray/gopath-0.9.0+15-1276481.tar.gz -C /tmp/tmp.9jaJCm8Aqd
$ tar xzf ../rexray/gopath-0.9.0+15-1276481.tar.gz -C /tmp/tmp.Na8C0iypRG
# no differences
$ diff -qr /tmp/tmp.9jaJCm8Aqd /tmp/tmp.Na8C0iypRG
# build plugin with tmp.9jaJCm8Aqd
$ GOPATH="/tmp/tmp.9jaJCm8Aqd:$GOPATH" go build \
-tags "pflag gofig" -o /home/akutz/.libstorage/var/lib/mod/mock.so \
-buildmode plugin ./mod
# build lsx-linux with tmp.Na8C0iypRG
$ GOPATH="/tmp/tmp.Na8C0iypRG:$GOPATH" ./gob ./cli/lsx/lsx-linux
# plug-in fails to load
$ ./lsx-linux
ERRO[0000] error opening module error=plugin.Open: plugin was built with a different version of package github.com/spf13/pflag path=/home/akutz/.libstorage/var/lib/mod/mock.so time=1495663533919
# build lsx-linux with tmp.9jaJCm8Aqd, same GOPATH used to build plug-in
$ GOPATH="/tmp/tmp.9jaJCm8Aqd:$GOPATH" ./gob ./cli/lsx/lsx-linux
# plug-in loads successfully
$ ./lsx-linux
usage: ./lsx-linux <executor> supported
instanceID
nextDevice
localDevices <scanType>
wait <scanType> <attachToken> <timeout>
mounts
mount [-l label] [-o options] device path
umount path
executor: mock
vfs
scanType: 0,quick | 1,deep
attachToken: <token>
timeout: 30s | 1h | 5m Even when using a common # remove the GOPATH used to build lsx-linux & mock.so
$ rm -fr /tmp/tmp.9jaJCm8Aqd
# recreate the GOPATH with identical contents
$ cp -fr /tmp/tmp.Na8C0iypRG /tmp/tmp.9jaJCm8Aqd
# rebuild lsx-linux
$ GOPATH="/tmp/tmp.9jaJCm8Aqd:$GOPATH" ./gob ./cli/lsx/lsx-linux
# the plug-in is loaded successfully
$ ./lsx-linux
usage: ./lsx-linux <executor> supported
instanceID
nextDevice
localDevices <scanType>
wait <scanType> <attachToken> <timeout>
mounts
mount [-l label] [-o options] device path
umount path
executor: mock
vfs
scanType: 0,quick | 1,deep
attachToken: <token>
timeout: 30s | 1h | 5m tl;dr: For plug-ins to work, not only must the sources in the |
Hi, It just occurred to me that the behavior outlined in the above comment results in the inability to build Go programs targeting a UNIX |
This sort of linking is directly supported by the Mach-O binary format used by Darwin (macOS/iOS/etc). The linker flag is
This is used when building a special "dynamically bound bundle" file type, which is a separate file type from any of a shared library, static library, or executable. I guess that bodes well for when plugin support gets added for Darwin, but I don't know that ELF has baked in support for plugins using their host binary's names like that. |
Hi @jeremy-w, Interesting. That is very useful...for Darwin. Which doesn't yet have Go plug-in support :) Which sucks, because Darwin is my primary development platform... For now, this is what I'm doing:
- glide install
- export PROJECT_NAME="rexray"
- export GOPATH_OLD="$GOPATH"
- export GOPATH="/tmp/go"
- mkdir -p "$GOPATH"/{bin,pkg,src}
- mv "$GOPATH_OLD"/bin/* "$GOPATH"/bin/
- export PATH="${GOPATH}/bin:${PATH}"
- mkdir -p "$GOPATH"/src/github.com/codedellemc
- rsync -ax vendor/ "$GOPATH"/src/ && rm -fr vendor
- cd .. && mv "$PROJECT_NAME" "$GOPATH"/src/github.com/codedellemc/
- cd "$GOPATH"/src/github.com/codedellemc/"$PROJECT_NAME"
Darwintar \
--exclude '.git' \
--exclude 'vendor' \
-czf "gopath.tar.gz" \
-s ',^./,src/,' \
-C vendor . Linuxtar \
--exclude '.git' \
--exclude 'vendor' \
-czf "gopath.tar.gz" \
--xform 's,^./,src/,S' \
-C vendor . |
@akutz What does a dependency update strategy look like for an application and its plugins? |
Hi @mattfarina, I'm moving ahead with a different approach that renders your question moot. You'll see why in a minute. |
Hi @spf13 / @mattfarina / @bradfitz / @jeremy-w, As I just indicated to @mattfarina, I am moving forward with a solution that essentially circumvents the original problem. The complete details can be found at akutz/gpds. For convenience I am copying the document below. For the members of the Go team, I am really curious about the section Curious Exception. It's something I never considered, and if that's not weird enough, check out Exception to the Exception. Go Plug-ins & Vendored Dependencies: A SolutionThis document outlines a solution for the problem described in golang/go#20481. Please review the original problem description before continuing. The problem was fairly straight-forward and ultimately so is the solution: a Go plug-in should not depend on a host process's symbols. That means:
Unidirectional ModelGo supports the dependency inversion principle (DIP) through the use of interface abstractions, but there still must exist a mechanism to provide implementations of the abstractions on which a program depends. One such solution can be found in the list of suggested implementations of inversion of control (IoC): the service locator pattern. The service locator pattern is very easy to implement in Go as a simple type registry. Consumers that require an implementation of some interface are able to query the type registry and receive an object instance that fulfills the abstraction. There are two models that can be used to prime the registry with types: bidirectional and unidirectional. The above diagram is an example of the bidirectional model, but it fails when used in concert with Go plug-ins due to the issues with dependencies outlined in golang/go#20481. The solution is a unidirectional model: Illustrated in the diagram above, the unidirectional model provides the same type registry that the bidirectional model does but relocates type registration from the plug-ins' Interface In / Interface OutGo interfaces are really powerful, but they are also quick to cause issues when used with plug-ins for two reasons:
Interface EqualityThe following examples demonstrate the power and peril of using Go interfaces interchangeably having assumed equality. The first example defines two, identical interfaces, package main
import (
"fmt"
)
type dog interface {
bark() string
}
type fox interface {
bark() string
}
type copper struct{}
func (c *copper) bark() string { return "woof!" }
type todd struct{}
func (t *todd) bark() string { return "woof!" }
func barkWithDog(d dog) { fmt.Println(d.bark()) }
func barkWithFox(f fox) { fmt.Println(f.bark()) }
func main() {
var d dog = &copper{}
var f fox = &todd{}
barkWithDog(d)
barkWithFox(f)
} The above code, when executed, will print Does that mean that func main() {
var d dog = &todd{}
var f fox = &copper{}
barkWithDog(f)
barkWithFox(d)
} How can Todd be a fox and Copper a dog? According to Go's interface rules, a variable of type It would appear then that multiple Go interfaces, if they define the same abstraction, are identical. However, thanks to Go's strong type system, interfaces are not as interchangeable as they first appear (run example): package main
import (
"fmt"
)
type dog interface {
bark() string
same(d dog) bool
}
type fox interface {
bark() string
same(f fox) bool
}
type copper struct{}
func (c *copper) bark() string { return "woof!" }
func (c *copper) same(d dog) bool { return c == d }
type todd struct{}
func (t *todd) bark() string { return "woof!" }
func (t *todd) same(f fox) bool { return t == f }
func barkWithDog(d dog) { fmt.Println(d.bark()) }
func barkWithFox(f fox) { fmt.Println(f.bark()) }
func main() {
var d dog = &todd{}
var f fox = &copper{}
barkWithDog(f)
barkWithFox(d)
} The above example will no longer emit the sound of two friends barking, but rather the following errors: tmp/sandbox006620983/main.go:31: cannot use todd literal (type *todd) as type dog in assignment:
*todd does not implement dog (wrong type for same method)
have same(fox) bool
want same(dog) bool
tmp/sandbox006620983/main.go:32: cannot use copper literal (type *copper) as type fox in assignment:
*copper does not implement fox (wrong type for same method)
have same(dog) bool
want same(fox) bool
tmp/sandbox006620983/main.go:33: cannot use f (type fox) as type dog in argument to barkWithDog:
fox does not implement dog (wrong type for same method)
have same(fox) bool
want same(dog) bool
tmp/sandbox006620983/main.go:34: cannot use d (type dog) as type fox in argument to barkWithFox:
dog does not implement fox (wrong type for same method)
have same(dog) bool
want same(fox) bool The relevant piece of information from the above error text is the following: have same(fox) bool
want same(dog) bool In other words, even though Go interfaces Because of this rule, without a shared types library, even with Go interfaces, it's not possible for Go plug-ins to expect to share or use symbols provided by the host process. Fully-Qualified Type PathHowever, even redefining interfaces inside plug-ins to match types found in the host process will fail if those interfaces are used by exported symbols. This section uses this project's $ go get github.com/akutz/gpds && \
cd $GOPATH/src/github.com/akutz/gpds/dog && \
for d in $(find . -maxdepth 1 -type d | grep -v '^.$'); do \
go build -buildmode plugin -o $d.so $d; \
done Run the program using the $ go run main.go dog.go sit.so
error: invalid Command func: func(main.dog)
exit status 1 An error occurs because the Therefore invoking the Curious ExceptionThere is one curious exception to this rule: when an interface is defined in the $ go run main.go dog.go speak.so
Lucy The program should have printed the name "Lucy". However, if the code is examined, the Both interfaces have a fully-qualified package path of When interfaces are defined in the Exception to the ExceptionWhat happens if the $ go run main.go self.go stay.so
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
runtime stack:
runtime.throw(0x534aad, 0xe)
/home/akutz/.go/1.8.1/src/runtime/panic.go:596 +0x95
runtime.newstack(0x0)
/home/akutz/.go/1.8.1/src/runtime/stack.go:1089 +0x3f2
runtime.morestack()
/home/akutz/.go/1.8.1/src/runtime/asm_amd64.s:398 +0x86
goroutine 1 [running]:
runtime.(*_type).string(0x7f2488279520, 0x0, 0x0)
/home/akutz/.go/1.8.1/src/runtime/type.go:45 +0xad fp=0xc44009c358 sp=0xc44009c350
runtime.typesEqual(0x7f2488279520, 0x51d0c0, 0x50a310)
/home/akutz/.go/1.8.1/src/runtime/type.go:543 +0x73 fp=0xc44009c480 sp=0xc44009c358
runtime.typesEqual(0x7f2488270740, 0x5137c0, 0x5137c0)
/home/akutz/.go/1.8.1/src/runtime/type.go:586 +0x368 fp=0xc44009c5a8 sp=0xc44009c480
runtime.typesEqual(0x7f2488279520, 0x51d0c0, 0x50a310)
/home/akutz/.go/1.8.1/src/runtime/type.go:615 +0x740 fp=0xc44009c6d0 sp=0xc44009c5a8
...additional frames elided...
goroutine 17 [syscall, locked to thread]:
runtime.goexit()
/home/akutz/.go/1.8.1/src/runtime/asm_amd64.s:2197 +0x1
exit status 2 The above program fails due to a Go runtime panic where Go is recursively trying to determine if the The SolutionThe proposed solution adheres to the crucial restriction outlined at the beginning of this document -- Go plug-ins should not depend on a host program's symbols. This project is used to demonstrate a program and plug-ins that:
To get started please clone this repository: $ go get github.com/akutz/gpds && cd $GOPATH/src/github.com/akutz/gpds Running the program will emit a little ditty by Mr. Chapin: $ go run main.go
Yes, we have no bananas,
We have no bananas today. Next, build the plug-in $ go build -buildmode plugin -o mod.so ./mod Running the program with the plug-in will cause the output to be somewhat altered: $ go run main.go mod.so
*main.v2Config
Yes there were thirty, thousand, pounds...
Of...bananas.
*main.v2Config
Bottom-line, sh*t kicking country choir
You'll see your part come by The above steps do not appear to illustrate anything too fancy, but under the covers is a model that enables Go plug-ins to work alongside vendoered dependencies with ease. Pulling back the covers ever so slightly reveals how it all works. LibFor starters, this is the // Module is the interface implemented by types that
// register themselves as modular plug-ins.
type Module interface {
// Init initializes the module.
//
// The config argument can be asserted as an implementation of the
// of the github.com/akutz/gpds/lib/v2.Config interface or older.
Init(ctx context.Context, config interface{}) error
} A
The documentation indicates which type the argument can be asserted as, and more importantly explains that the object provided can be asserted as a specific version of that type or older. All types should be versioned and plug-ins that assert v1.Type should continue to work even if v1+.Type is provided. Please note that this model could be further enhanced so that plug-ins provide a symbol that contains the expected API version so that host programs can eventually deprecate older types by restricting which plug-ins get loaded based on their supported API type. ModThe file // Types is the symbol the host process uses to
// retrieve the plug-in's type map
var Types = map[string]func() interface{}{
"mod_go": func() interface{} { return &module{} },
} The The module type module struct{}
func (m *module) Init(ctx context.Context, configObj interface{}) error {
config, configOk := configObj.(Config)
if !configOk {
return errInvalidConfig
}
fmt.Fprintf(os.Stdout, "%T\n", config)
fmt.Fprintln(os.Stdout, config.Get(ctx, "bananas"))
return nil
} Since this is an example the plug-in's module only defines a barebones implementation of the But wait, how is the plug-in able to assert the interface ConclusionHopefully this document has not only shown how to solve the issue of Go plug-ins and vendored dependencies, but also clearly articulated the reasoning behind the decisions that led to the proposed solution. |
I believe this is a duplicate of #18827, no? |
I just made a comment over on #18827. Ideally we would have one issue for discussing this. If the problem here is the same as that one (packages under the vendor directory are not the same at run time, just like in non-plugin programs), let's discuss it over there and please close this. That said, @akutz I cannot follow how your solution "a Go plug-in should not depend on a host process's symbols" would work. If we don't use the host symbols, then there is no overlap between packages at all. A plugin cannot implement an http handler. |
Hi @crawshaw, Thank you very much for the response. Please allow me to address a few things:
The point I was clumsily trying to make is that it's so overwhelmingly cumbersome to build plug-ins that can be loaded into host programs and share data via shared types that the utility of what plug-ins generally provide is diminished. Having thought about this matter quite a bit, these are the occasions when, in my mind, Go plug-ins make sense:
Hopefully this helps clear up some of my original intent. Thank you again for your response! -- |
Currently plugin is very hard to setup, because of issues in golang of exposing vendored interfaces to the plugins. See golang/go#20481
Hi, I'm also getting same error, github.com/hyperledger/fabric/vendor/github.com/miekg/pkcs11exec: "s390x-linux-gnu-gcc": executable file not found in $PATH github.com/hyperledger/fabric/vendor/plugin../github.com/hyperledger/fabric/vendor/plugin/plugin.go:32: undefined: open Could u tell me solution? |
@SreekanthSimplyfi That is a different problem. Please see https://golang.org/wiki/Questions . |
@ianlancetaylor problem is solved from this link |
API is found at https://github.com/metacosm/odo-event-api. This, however, doesn't work well with vendoring so right now, both the app and plugin can only be built against an api that's in the same spot in $GOPATH. See golang/go#20481 for more details.
API is found at https://github.com/metacosm/odo-event-api. This, however, doesn't work well with vendoring so right now, both the app and plugin can only be built against an api that's in the same spot in $GOPATH. See golang/go#20481 for more details.
Would it be feasible to add a command line option to |
Note that the |
API is found at https://github.com/metacosm/odo-event-api. This, however, doesn't work well with vendoring so right now, both the app and plugin can only be built against an api that's in the same spot in $GOPATH. See golang/go#20481 for more details.
API is found at https://github.com/metacosm/odo-event-api. This, however, doesn't work well with vendoring so right now, both the app and plugin can only be built against an api that's in the same spot in $GOPATH. See golang/go#20481 for more details.
API is found at https://github.com/metacosm/odo-event-api. This, however, doesn't work well with vendoring so right now, both the app and plugin can only be built against an api that's in the same spot in $GOPATH. See golang/go#20481 for more details.
API is found at https://github.com/metacosm/odo-event-api. This, however, doesn't work well with vendoring so right now, both the app and plugin can only be built against an api that's in the same spot in $GOPATH. See golang/go#20481 for more details.
API is found at https://github.com/metacosm/odo-event-api. This, however, doesn't work well with vendoring so right now, both the app and plugin can only be built against an api that's in the same spot in $GOPATH. See golang/go#20481 for more details.
this doesn't work. golang/go#20481 seems related. Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
easyjson does not work with main packages. Workaround changing the package name as suggested in mailru/easyjson#236 Some code has been moved from skydive to graffity: common.Getter Clean and update gomod: k8s deps should be added with replace, kubernetes/kubernetes#79384 Same problem with networkservicemesh viper is not being used add skydive as dep, not local replace Once compiled it fails to run with skydive because problems with the Go plugin framework. The error is: plugin was built with a different version of package golang.org/x/sys/unix golang/go#20481 golang/go#27751
easyjson does not work with main packages. Workaround changing the package name as suggested in mailru/easyjson#236 Some code has been moved from skydive to graffity: common.Getter Clean and update gomod: k8s deps should be added with replace, kubernetes/kubernetes#79384 Same problem with networkservicemesh viper is not being used add skydive as dep, not local replace Once compiled it fails to run with skydive because problems with the Go plugin framework. The error is: plugin was built with a different version of package golang.org/x/sys/unix golang/go#20481 golang/go#27751 Tested adding to the skydive.yml config file: plugin: plugins_dir: /home/adrian/go/src/github.com/skydive-project/skydive-plugins/memory topology: probes: memory
Go plugins, unfortunately, have severe limitations and are not ideal for plugins that may need wide distribution. golang/go#20481 This patch get rids of the tokenizer plugin system entirely and just bundles the available tokenizers (phonetic: Kannada, Malayalam) into the core. Widely usable tokenizers can henceforth be bundled into the core just like how Postgres come with bundled TSVECTOR dictionaries. Also, it is possible to write custom tokenizers as Postgres plugins and load them into Postgres dynamically, making the Go tokenizer plugin system superfluous.
Duplicate of #18827 |
This issue is tied to the example project gpd.
cc @spf13
The Problem
The utility of Go plug-ins is almost completely erased by fact that many Go projects rely on vendored dependencies in order to ensure consistent build results.
The problem is pretty straight-forward. When an application (
app
) vendors a library (lib
), the package path of the library is nowpath/to/app/vendor/path/to/lib
. However, the plug-in is likely built against eitherpath/to/lib
or, if the plug-in vendors dependencies as well,path/to/plugin/vendor/path/to/lib
.This of course makes total sense and behaves exactly as one would expect with regards to Go packages. Despite the intent, these three packages are not the same:
path/to/lib
path/to/app/vendor/path/to/lib
path/to/plugin/vendor/path/to/lib
While the behavior is consistent with regards to Go packages, it flies in the face of the utility provided by a combination of vendored dependencies and the new Go plug-in model.
Reproduction
This project makes it easy to reproduce the above issue.
Requirements
To reproduce this issue Go 1.8.x and a Linux host are required:
Download
On a Linux host use
go get
to fetch the gpd project:Run the program
The root of the project is a Go command-line program. Running it will emit a message to the console:
Build the plug-in
If the program is run with a single argument it is treated as the path to a Go plug-in. That plug-in is loaded and will emit a different message to the console. First, build the plug-in:
To verify that the produced file is a plug-in, use the
file
command:The file is reported as a shared object, verifying that it is indeed a Go plug-in.
Run the program with the plug-in
Run the program using the plug-in:
It works!
Vendor the shared
dep
packageHowever, what happens when the program vendors the shared
dep
package?The program fails!
This is because the
dep
package includes a type that is used by both the sharedlib
package and the plug-in package,mod
.The plug-in linked against the
lib
package atgithub.com/akutz/gpd/lib
which itself linked against thedep
package atgithub.com/akutz/gpd/dep
.However, vendoring the
dep
package for the program causes thelib
package as compiled into the program to link againstgithub.com/akutz/gpd/vendor/github.com/akutz/gpd/dep
, resulting in the program and the plug-in having two different versions of thelib
package!Vendor the shared
lib
packageHowever, what happens when the program vendors the shared
lib
package?The program fails! This is because the
lib
package contains a type registry that can be used to both register types and construct new instances of those types.However, because the program's type registry is located in the package
github.com/akutz/gpd/vendor/github.com/akutz/gpd/lib
and the plug-in registered its type withgithub.com/akutz/gpd/lib
, when the program requests a new object for the typemod_go
, a nil exception occurs because the program and plug-in were accessing two different type registries!The Hack
At the moment the only solution available is to create a build toolchain using a list of transitive dependencies generated from the application that is responsible for loading the plug-ins. This list of dependencies can be used to create a custom
GOPATH
against which any projects participating in the application must be built, including the application itself, any shared libraries, and the plug-ins.The Solution
Is there one? Two possible solutions are:
src
directory at the root of avendor
directory so that plug-ins can be built directly against a program'svendor
directory. Today that would require a bind mount.Hopefully the Golang team can solve this issue as it really does prevent Go plug-ins from being useful in a world where applications are often required to vendor dependencies.
The text was updated successfully, but these errors were encountered: