Go: plugin: gopath type != vendor type

Created on 28 Jan 2017  路  17Comments  路  Source: golang/go

Please answer these questions before submitting your issue. Thanks!

What version of Go are you using (go version)?

go version go1.8rc3 linux/amd64

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/fsenart/projects"
GORACE=""
GOROOT="/home/fsenart/tools/go1.8rc3"
GOTOOLDIR="/home/fsenart/tools/go1.8rc3/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build101924921=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"

What did you do?

If possible, provide a recipe for reproducing the error.
A complete runnable program is good.
A link on play.golang.org is best.

Scenario:

  1. Build a main program with some dependency located in the GOPATH
  2. Build a plugin program with the same dependency vendored
  3. Load the plugin from main

Result:

  • At runtime, the GOPATH dependency is not the same as the vendored depedency.
// $GOPATH/src/issues/issue_vendor/main .go

package main

import (
    "foo"
    "plugin"
    "reflect"
)

func main() {
    p, err := plugin.Open("plugin.so")
    if err != nil {
        panic(err)
    }
    f, err := p.Lookup("F")
    if err != nil {
        panic(err)
    }

    println(reflect.TypeOf(f).Elem() == reflect.TypeOf((*foo.Bar)(nil)))
}
// $GOPATH/src/issues/issue_vendor/plugin.go

package main

import "C"
import "foo"

var F *foo.Bar
// Copy the file one time in the GOPATH and one time in the vendor directory
// $GOPATH/src/foo/bar.go
// $GOPATH/src/issues/issue_vendor/vendor/foo/bar.go

package foo

type Bar struct{}
# $GOPATH/src/issues/issue_vendor/Makefile

test1: build1
    @./main

test2: build2
    @./main

build1: clean
    @go build -o main main.go
    @go build -buildmode=plugin -o plugin.so plugin.go

build2: clean
    @mv vendor _vendor
    @go build -o main main.go
    @mv _vendor vendor
    @go build -buildmode=plugin -o plugin.so plugin.go

clean:
    @rm -rf main

What did you expect to see?

  • make test1 should print true
  • make test2 should print true

What did you see instead?

  • make test1 prints true
  • make test2 prints false
NeedsInvestigation help wanted

Most helpful comment

As this issue has been assigned to a milestone, what exactly is proposed to be changed/addressed?

@crawshaw says:

It would also be impossible to build a plugin that vendors two different versions of a package under different subtrees.

If this is the case, I'm struggling to find the relevance in the Plugin package entirely. If plugins are to always be tightly coupled with the dependencies of the project that loads it, why would I not just include that code in the parent project's source.

All 17 comments

@ianlancetaylor is there any chance to add this issue to the 1.8.1 milestone as it makes the usage of vendoring nearly impossible in the context of plugins?

Adding the 1.8.1 milestone to this issue won't fix the problem. When and if we have a fix on tip, then if the fix is simple and safe, we can consider adding it to 1.8.1.

As mentioned in https://github.com/golang/go/issues/19233, this sounds like the behavior described at https://github.com/golang/go/issues/12432. Unless this behavior is set to change in 1.9, this seems like a non-issue, albeit annoying in the context of plugins.

@edaniels it really seems like the behavior you mentioned but the nuance resides in the fact that this issue happens on run time. I understand that this is the "plugin" version of the behavior, but this is also far more annoying for end users as it can't be detected at compile time.
Plugins come with a lot of advantages but also open the pandora's box. IMHO, the main advantage is to allow the separation between a core program and a myriad of run time functionalities developed by different folks but this kind of issues constraint both parties to know about each others and conform to a set of common dependencies rules.
Maybe we should come up with a freshen look about vendoring in the context of plugins.

I totally agree. It's probably a discussion for another issue but it seems like this is an issue for vendoring in general and maybe the go tool chain should be more aware of package identity in the future. To make plugins more useful something will need to be done.

If you built this as a single program, there would be two separate packages for foo. There would be two instances of any global variables, and the types you are comparing would be different.

It seems very strange to me that plugins would de-duplicate vendored packages. What if a plugin has vendored a different version of a package because it wants to use a different version? The only thing we could do in plugin.Open is fail in this case, because the packages wouldn't be compatible.

It would also be impossible to build a plugin that vendors two different versions of a package under different subtrees. I can't say I've ever done this, but all of this is possible with the vendor directory in usual programs, so I think it should work in plugins also.

Sorry that we didn't look at this issue in the Go1.10 cycle, but I think we might have to move this to Go1.11. What do you think @crawshaw?

@crawshaw is on leave, so that's a yes: moving to Go 1.11.

Oh, right, I had forgotten. Thanks @bradfitz.

As this issue has been assigned to a milestone, what exactly is proposed to be changed/addressed?

@crawshaw says:

It would also be impossible to build a plugin that vendors two different versions of a package under different subtrees.

If this is the case, I'm struggling to find the relevance in the Plugin package entirely. If plugins are to always be tightly coupled with the dependencies of the project that loads it, why would I not just include that code in the parent project's source.

I am not sure if this behaviour is a bug or a feature. On one hand it prevents a base application and a plugin from passing shared types to each other, but on the other hand it allows two plugins to vendor different versions of the same dependency and still be loaded. Without this behaviour, the plugin loader will complain that the second plugin was built with a different version of the shared dependency.

The issue with a shared type being returned by a plugin function to the base could be worked around by either pulling that specific dependency out of vendor/ into the GOPATH or by using a combination of interfaces, type assertion and reflection to convert the two types.

Also should the discussion on this issue happen here or in #20481?

I tried using plugins a while ago and gave up. I had 2+ plugins (and the main application too) that use kubernetes libraries and kubernetes uses glog. So, 2+ glog copies, each trying to add a flag to the flag package. A flag cannot be added more than once -> glog/stdlib panics. Plugins are completely unuseable in such situation.
I think shared mutable state should be removed from standard library in Go 2.

A similar thing happens for plugins that import x/net/trace which in its init() adds some http handlers to the http.DefaultServeMux. So if you load 2 plugins that have vendored x/net/trace, the init() will panic because it gets executed twice and you can't have two handlers registered at the same route.

The only way to "resolve" this right now seems to be to reassign http.DefaultServeMux before loading each plugin.

Are there any updates on this? I agree with the commenters above that this renders plugins useless for a lot of use cases. There should at least be a warning at the top of the documentation of the plugins package that when working with vendored code you cannot share types between different packages.

Is there any progress? It's still very difficult to use plugins

It seems like plugins are effectively dead, support wise. They've received little mention since their release.

then are there any other alternative we can leverage?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dominikh picture dominikh  路  3Comments

enoodle picture enoodle  路  3Comments

bradfitz picture bradfitz  路  3Comments

mingrammer picture mingrammer  路  3Comments

natefinch picture natefinch  路  3Comments