-
Notifications
You must be signed in to change notification settings - Fork 10
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
Preview feedback issue #5
Comments
@k33g - you should be able to access this repo now, but keep in mind all work is being done on a branch: For simplifying any experimentation, if it is difficult to use the private repo with go.mod or the Go toolchain, feel free to clone this and host it in another repo in your own account. |
Thank you for any & all feedback! |
Plugins tests👋 Hello, I did some tests with these plugins:
I can call all these plugins with the Extism CLI: extism call ./plugin.wasm \
handle --input "Bob Morane" \
--wasi I ran some Go tests like this one: func TestK33gRustHandler(t *testing.T) {
manifest := manifest("k33g_rust_handler_plugin.wasm")
if plugin, ok := plugin(t, manifest); ok {
defer plugin.Close()
exit, output, err := plugin.Call("handle", []byte("Bob Morane"))
if err != nil {
fmt.Println("😡", err.Error())
}
if assertCall(t, err, exit) {
actual := string(output)
fmt.Println("🙂", actual)
expected := `{"success":"🦀 Hello Bob Morane","failure":"no error"}`
assert.Equal(t, expected, actual)
}
}
} Everything is ok for the Rust and JavaScript plugin. But I get a |
🎉 ok, I found the problem (it's me again), my //export handle
func handle() {
receiver.SetHandler(func(param []byte) ([]byte, error) {
res := `{"message":"👋 Hello `+ string(param) + `", "number":42}`
return []byte(res), nil
})
} It works with this: //export handle
func handle() int32 {
receiver.SetHandler(func(param []byte) ([]byte, error) {
res := `{"message":"👋 Hello `+ string(param) + `", "number":42}`
return []byte(res), nil
})
return 0
} So the conclusion is that everything works like a charm 🥰 |
Host application tests (HTTP server)package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"sync"
"github.com/extism/extism"
"github.com/gofiber/fiber/v2"
"github.com/tetratelabs/wazero"
)
// store all your plugins in a normal Go hash map, protected by a Mutex
var m sync.Mutex
var plugins = make(map[string]extism.Plugin)
func StorePlugin(plugin extism.Plugin) {
// store all your plugins in a normal Go hash map, protected by a Mutex
plugins["code"] = plugin
}
func GetPlugin() (extism.Plugin, error) {
if plugin, ok := plugins["code"]; ok {
return plugin, nil
} else {
return extism.Plugin{}, errors.New("🔴 no plugin")
}
}
func main() {
wasmFilePath := os.Args[1:][0]
wasmFunctionName := os.Args[1:][1]
httpPort := os.Args[1:][2]
//ctx := extism.NewContext()
ctx := context.Background()
config := extism.PluginConfig{
ModuleConfig: wazero.NewModuleConfig().WithSysWalltime(),
EnableWasi: true,
}
//defer ctx.Free() // this will free the context and all associated plugins
manifest := extism.Manifest{
Wasm: []extism.Wasm{
extism.WasmFile{
Path: wasmFilePath},
},
}
//plugin, err := ctx.PluginFromManifest(manifest, []extism.Function{}, true)
plugin, err := extism.NewPlugin(ctx, manifest, config, nil)
if err != nil {
log.Println("🔴 !!! Error when loading the plugin", err)
os.Exit(1)
}
StorePlugin(plugin)
app := fiber.New(fiber.Config{DisableStartupMessage: true})
app.Post("/", func(c *fiber.Ctx) error {
params := c.Body()
m.Lock()
defer m.Unlock()
plugin, err := GetPlugin()
if err != nil {
log.Println("🔴 !!! Error when getting the plugin", err)
c.Status(http.StatusInternalServerError)
return c.SendString(err.Error())
}
_, out, err := plugin.Call(wasmFunctionName, params)
if err != nil {
fmt.Println(err)
c.Status(http.StatusConflict)
return c.SendString(err.Error())
} else {
c.Status(http.StatusOK)
return c.SendString(string(out))
}
})
fmt.Println("🌍 http server is listening on:", httpPort)
app.Listen(":" + httpPort)
} Build the applicationexport TAG="v0.0.0"
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o slingshot-${TAG}-linux-arm64 Run the application./slingshot-v0.0.0-linux-arm64 \
../go-handler-plugin/k33g_go_handler_plugin.wasm \
handle \
8080 Query the applicationcurl -X POST \
http://localhost:8080 \
-H 'content-type: text/plain; charset=utf-8' \
-d '😄 Bob Morane' Result: OK 🟢 Load testinghey -n 300 -c 100 -m POST \
-d 'John Doe' \
"http://localhost:8080" Result: OK 🟢 🎉🍾 wow 👏 (it will be perfect for my presentation at GoLab 😀) |
DockerizeFROM scratch
ADD slingshot-v0.0.0-linux-arm64 ./
ADD k33g_go_handler_plugin.wasm ./
EXPOSE 8080
CMD ["./slingshot-v0.0.0-linux-arm64", "./k33g_go_handler_plugin.wasm", "handle", "8080"] Size: |
Wow! thank you very much @k33g! Please let us know if you have any feedback on the API too |
I will do more tests in the coming days |
I have fixed the behavior to match the Rust SDK. if the plugin doesn't return anything, we assume it has succeeded as long as there are no errors reported by Wazero |
@mhmd-azeez I confirm it works 👍 |
Also adding @syke99 to the thread! She has a cool Go project and wants to take the new SDK for a spin. Quinn, you should be able to access this repo now, but keep in mind all work is being done on a branch: For simplifying any experimentation, if it is difficult to use the private repo with go.mod or the Go toolchain, feel free to clone this and host it in another repo in your own account. |
I'm trying to understand how to write a host function. Are |
@k33g this is an example of writing host functions: Line 224 in fc6f8f3
the plugin itself is here: https://github.com/extism/go-sdk/blob/feat/go-sdk/plugins/host/main.go
Yes. But you don't need to use them directly. When you call |
@mhmd-azeez 🤔 ok, I will try again (I wanted to exchange strings, but I should miss something) Before (with the former Extism go SDK), into the host function's body, I could read the memory to get (for example) a string parameter by using /*
#include <extism.h>
EXTISM_GO_FUNCTION(memory_get);
*/
import "C"
var memoryMap = map[string]string{
"hello": "👋 Hello World 🌍",
"message": "I 💜 Extism 😍",
}
//export memory_get
func memory_get(plugin unsafe.Pointer, inputs *C.ExtismVal, nInputs C.ExtismSize, outputs *C.ExtismVal, nOutputs C.ExtismSize, userData uintptr) {
inputSlice := unsafe.Slice(inputs, nInputs)
outputSlice := unsafe.Slice(outputs, nOutputs)
// Get memory pointed to by first element of input slice
currentPlugin := extism.GetCurrentPlugin(plugin)
keyStr := currentPlugin.InputString(unsafe.Pointer(&inputSlice[0]))
returnValue := memoryMap[keyStr]
currentPlugin.ReturnString(unsafe.Pointer(&outputSlice[0]), returnValue)
//outputSlice[0] = inputSlice[0]
} But in the a := api.DecodeI32(stack[0])
b := api.DecodeI32(stack[1]) My question is: what should I use to read a buffer from the shared memory? With Wazero, I use this: // printString : print a string to the console
var printString = api.GoModuleFunc(func(ctx context.Context, module api.Module, params []uint64) {
// Extract the position and size of the returned value
position := uint32(params[0])
length := uint32(params[1])
buffer, ok := module.Memory().Read(position, length)
if !ok {
log.Panicf("Memory.Read(%d, %d) out of range", position, length)
}
fmt.Println(string(buffer))
params[0] = 0 // return 0
})
func DefineHostFuncPrint(builder wazero.HostModuleBuilder) {
// hostPrintString
builder.NewFunctionBuilder().
WithGoModuleFunction(printString,
[]api.ValueType{
api.ValueTypeI32, // string position
api.ValueTypeI32, // string length
},
[]api.ValueType{api.ValueTypeI32}).
Export("hostPrintString")
} this is the code of the wasm program: //export hostPrintString
func hostPrintString(messagePosition, messageLength uint32) uint32
// Print : call host function: hostPrintString
// Print a string
func Print(message string) {
messagePosition, messageSize := getStringPosSize(message)
hostPrintString(messagePosition, messageSize)
} |
@k33g yeah, there were a few functions missing in this new SDK, I just wrote them and have also added an example of exchanging strings. Will push my changes soon and get back to you |
Sorry, I was in too much of a hurry (and excited) 🙇🏻 |
@k33g hahaha thank you very much for taking the time to try it out. I have added the necessary functions: Plugin: go-sdk/plugins/host_memory/main.go Line 14 in 1dcb2f1
Host: Line 253 in 1dcb2f1
Please note that the API might change, sorry for the inconvenience. Let me know if you faced any issues |
@mhmd-azeez 👏 great job! It works great! 🎉 package main
import (
receiver "go-handler-plugin/core"
"github.com/extism/go-pdk"
)
//go:wasm-module env
//export hostMemoryGet
func hostMemoryGet(offset uint64) uint64
func MemoryGet(key string) string {
// Call the host function
// 1- copy the key to the shared memory
memoryKey := pdk.AllocateString(key)
// call the host function
// memoryKey.Offset() is the position and the length of memoryKey into the memory (2 values into only one value)
offset := hostMemoryGet(memoryKey.Offset())
// read the value into the memory
// offset is the position and the length of the result (2 values into only one value)
// get the length and the position of the result in memory
memoryResult := pdk.FindMemory(offset)
/*
memoryResult is a struct instance
type Memory struct {
offset uint64
length uint64
}
*/
// create a buffer from memoryResult
// fill the buffer with memoryResult
buffResult := make([]byte, memoryResult.Length())
memoryResult.Load(buffResult)
return string(buffResult)
}
//export handle
func handle() {
val1 := MemoryGet("hello")
val2 := MemoryGet("message")
receiver.SetHandler(func(param []byte) ([]byte, error) {
res := `{"message":"👋 Hello `+ string(param) + `", "number":42, "message":"`+ val1 + " - " + val2 +`"}`
return []byte(res), nil
})
}
func main() {
} I'm working on a presentation for GoLab GIVE SUPER POWERS TO YOUR GOLANG APPLICATION WITH WEBASSEMBLY AND EXTISM, and I think I'm pretty confident to be able to rewrite all my demos with the new Go SDK. |
👋 Hello, I re-wrote all my examples (from the main branch of the sdk) - everything works perfectly |
Question(problem?) about memory (I don't know if it's a "normal" behaviour)Hello @mhmd-azeez, I did an Extism TinyGo plugin: package main
import (
"github.com/extism/go-pdk"
)
//export getName
func getName() int32 {
name := "GoMonster"
pdk.OutputMemory(pdk.AllocateString(name))
return 0
}
//export getAvatar
func getAvatar() int32 {
avatar := "🥶"
pdk.OutputMemory(pdk.AllocateString(avatar))
return 0
}
func main() {} When I call the function from the host: pluginMonster, err := extism.NewPlugin(ctx, manifest, config, hostFunctions) // new
_, monsterName, err := pluginMonster.Call("getName", nil)
_, monsterAvatar, err := pluginMonster.Call("getAvatar", nil)
fmt.Println("Monster ->", string(monsterAvatar)+" "+string(monsterName)) I get: Monster -> 🥶 🥶 instead of: If I modify the source code like this: pluginMonster, err := extism.NewPlugin(ctx, manifest, config, hostFunctions) // new
_, monsterName, err := pluginMonster.Call("getName", nil)
name := string(monsterName)
_, monsterAvatar, err := pluginMonster.Call("getAvatar", nil)
avatar := string(monsterAvatar)
fmt.Println("Monster ->", string(avatar)+" "+string(name)) I get: Monster -> 🥶 GoMonster So, I know how to avoid my problem, but I don't know if it's "normal" |
Problem with host function:If I export 2 host functions, every call of a host function triggers the first host function of the slice of HostFunction Host applicationpackage main
import (
"context"
"fmt"
"github.com/extism/extism"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
func main() {
ctx := context.Background() // new
path := "../100-go-plugin/simple.wasm"
config := extism.PluginConfig{
ModuleConfig: wazero.NewModuleConfig().WithSysWalltime(),
EnableWasi: true,
}
manifest := extism.Manifest{
Wasm: []extism.Wasm{
extism.WasmFile{
Path: path},
}}
print_message := extism.HostFunction{
Name: "hostPrintMessage",
Namespace: "env",
Callback: func(ctx context.Context, plugin *extism.CurrentPlugin, userData interface{}, stack []uint64) {
offset := stack[0]
bufferInput, err := plugin.ReadBytes(offset)
if err != nil {
fmt.Println("🥵", err.Error())
panic(err)
}
message := string(bufferInput)
fmt.Println("🟢:", message)
stack[0] = 0
},
Params: []api.ValueType{api.ValueTypeI64},
Results: []api.ValueType{api.ValueTypeI64},
}
display_message := extism.HostFunction{
Name: "hostDisplayMessage",
Namespace: "env",
Callback: func(ctx context.Context, plugin *extism.CurrentPlugin, userData interface{}, stack []uint64) {
offset := stack[0]
bufferInput, err := plugin.ReadBytes(offset)
if err != nil {
fmt.Println("🥵", err.Error())
panic(err)
}
message := string(bufferInput)
fmt.Println("🟣:", message)
stack[0] = 0
},
Params: []api.ValueType{api.ValueTypeI64},
Results: []api.ValueType{api.ValueTypeI64},
}
hostFunctions := []extism.HostFunction{
print_message,
display_message,
}
//fmt.Println("📦", hostFunctions)
pluginInst, err := extism.NewPlugin(ctx, manifest, config, hostFunctions)
if err != nil {
panic(err)
}
_, res, err := pluginInst.Call(
"say_hello",
[]byte("John Doe"),
)
fmt.Println("🙂", string(res))
_, res, err = pluginInst.Call(
"say_hey",
[]byte("Jane Doe"),
)
fmt.Println("🙂", string(res)
} Guest pluginpackage main
import (
"github.com/extism/go-pdk"
)
//export hostPrintMessage
func hostPrintMessage(offset uint64) uint64
func printMessage(message string) {
messageMemory := pdk.AllocateString(message)
hostPrintMessage(messageMemory.Offset())
}
//export hostDisplayMessage
func hostDisplayMessage(offset uint64) uint64
func displayMessage(message string) {
messageMemory := pdk.AllocateString(message)
hostDisplayMessage(messageMemory.Offset())
}
//export say_hello
func say_hello() int32 {
input := pdk.Input()
output := "👋 Hello " + string(input)
printMessage("from say_hello")
mem := pdk.AllocateString(output)
pdk.OutputMemory(mem)
return 0
}
//export say_hey
func say_hey() int32 {
input := pdk.Input()
output := "🫱 Hey " + string(input)
displayMessage("from say_hey")
mem := pdk.AllocateString(output)
pdk.OutputMemory(mem)
return 0
}
func main() {} When I run the host application, I get the following: 🟣: from say_hello
🙂 👋 Hello John Doe
🟣: from say_hey
🙂 🫱 Hey Jane Doe instead of: 🟢: from say_hello
🙂 👋 Hello John Doe
🟣: from say_hey
🙂 🫱 Hey Jane Doe If I reverse the order of the host functions: hostFunctions := []extism.HostFunction{
display_message,
print_message,
} I get the following: 🟢: from say_hello
🙂 👋 Hello John Doe
🟢: from say_hey
🙂 🫱 Hey Jane Doe |
@mhmd-azeez I confirm 🥰🎉 I can resume my other tests 😉: |
Great, looks like a fun game! is that for your talk? |
It was just for fun, but perhaps I will use it for the talk (to explain the host functions) |
Haven't run it yet, but finally got around to refactoring my application using the new SDK!! Super smooth, and loved it!! Also, I opened this PR to add convenience methods for setting/getting variables to/from a plugin 😎 |
I think we've gotten through this period. If anyone has any more issues let's move the to specific github issues. Thank you to everyone who participated getting this SDK off the ground! |
Adding a new issue here to take feedback on the SDK while it's being actively developed.
The text was updated successfully, but these errors were encountered: