On Tue, Apr 21, 2020 at 11:44:59AM +0100, Richard W.M. Jones wrote:
Thanks: Dan Berrangé
XXX UNFINISHED:
- Is using uintptr for the handle a good idea? Plugins must return
something != 0. In other languages we would allow plugins to
return an arbitrary object here, but this is not possible in golang
because of lack of GC roots.
Yeah, this is the bit that looks wierd to me when thinking about this
from a Go dev POV. You asked me on IRC whether we should have a separate
interface for a connection object, and this makes me think that we should
indeed do that.
Of course we still have the problem that C code is forbidden to keep
a pointer to a Go object across method calls.
IIUC, the C layuer in nbdkit treats the "void *" as a completely
opaque value, so we don't actually have to store any valid pointer
as the handle at all. It can be literally any value that fits in
a 32-bit pointer. Thus we can keep a global variable mapping the
GO objects to fake C "pointers"
So it would look like this:
// The plugin interface.
type PluginInterface interface {
// Open, GetSize and PRead are required for all plugins.
// Other methods are optional.
Config(key string, value string) error
ConfigComplete() error
Open(readonly bool) (PluginConnectionInterface, error)
}
type PluginConnectionInterface interface {
// Open, GetSize and PRead are required for all plugins.
// Other methods are optional.
Close()
GetSize() (uint64, error)
PRead(buf []byte, offset uint64, flags uint32) error
}
Now, we need todo some mapping
var connectionID uint
var connectionMap map[uint]PluginConnectionInterface
//export implOpen
func implOpen(c_readonly C.int) unsafe.Pointer {
readonly := false
if c_readonly != 0 {
readonly = true
}
h, err := pluginImpl.Open(readonly)
if err != nil {
set_error(err)
return nil
}
if h == 0 {
panic("Open method: handle must be != 0")
}
id := connectionID++
connectionMap[id] = h
return unsafe.Pointer(uintptr(id))
}
func getConn(handle unsafe.Pointer) PluginConnectionInterface {
id := uint(uintptr(handle))
conn, ok = connectionMap[id]
if !ok {
panic("Connection %d was not open", id)
}
}
//export implClose
func implClose(handle unsafe.Pointer) {
conn := getConn(handle)
conn.Close()
delete(connectionMap, id)
}
//export implGetSize
func implGetSize(handle unsafe.Pointer) C.int64_t {
conn := getConn(handle)
size, err := conn.GetSize()
if err != nil {
set_error(err)
return -1
}
return C.int64_t(size)
}
diff --git a/plugins/golang/test/test.go
b/plugins/golang/test/test.go
new file mode 100644
index 00000000..f5b1a33b
--- /dev/null
+++ b/plugins/golang/test/test.go
type TestPlugin struct {
nbdkit.Plugin
}
type TestPluginConnection struct {
nbdkit.PluginConnection
...other per-connection fields...
}
var pluginName = "test"
var size uint64
var size_set = false
func (p TestPlugin) Config(key string, value string) error {
if key == "size" {
var err error
size, err = strconv.ParseUint(value, 0, 64)
if err != nil {
return err
}
size_set = true
return nil
} else {
return nbdkit.PluginError{Errmsg: "unknown parameter"}
}
}
func (p TestPlugin) ConfigComplete() error {
if !size_set {
+ return nbdkit.PluginError{Errmsg: "size parameter is
required"}
}
return nil
}
func (p TestPlugin) Open(readonly bool) (nbdkit.PluginConnectionInterface, error) {
nbdkit.Debug("golang code running in the .open callback")
return &TestPluginConnection{}, nil
}
func (p TestPluginConnection) GetSize() (uint64, error) {
nbdkit.Debug("golang code running in the .get_size callback")
return size, nil
}
func (p TestPluginConnection) PRead(buf []byte, offset uint64, flags uint32) error {
nbdkit.Debug("golang code running in the .pread callback")
for i := 0; i < len(buf); i++ {
buf[i] = 0
}
return nil
}
Regards,
Daniel
--
|:
https://berrange.com -o-
https://www.flickr.com/photos/dberrange :|
|:
https://libvirt.org -o-
https://fstop138.berrange.com :|
|:
https://entangle-photo.org -o-
https://www.instagram.com/dberrange :|