Skip to content
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

Calls to plugin hang when passing large amounts of data back and forth #14

Open
cchamplin opened this issue Dec 9, 2022 · 6 comments
Open

Comments

@cchamplin
Copy link

When calling into plugins the behavior is not consistent depending on how much data is being passed. For small amounts of data everything seems fine. For larger amounts of data each successive call gets slower. For even larger amounts of data only one or two calls is possible before the callee hangs indefinitely. When the hang occurs it seems like we never make it into the plugin function, so it appears to be an issue with memory allocation or garbage collection within in the wasm boundary.

Version: f7d9444
Reproducible Test: https://github.com/cchamplin/plugin-bug

=== RUN   TestPlugin_MemorySmall
2022/12/09 15:14:13 Size of data being passed 1000 (139890 bytes)
2022/12/09 15:14:13 calling plugin 0
2022/12/09 15:14:13 call execution time: 61.440708ms
2022/12/09 15:14:13 calling plugin 1
2022/12/09 15:14:13 call execution time: 61.888625ms
...
2022/12/09 15:14:13 calling plugin 18
2022/12/09 15:14:14 call execution time: 252.296958ms
2022/12/09 15:14:14 calling plugin 19
2022/12/09 15:14:14 call execution time: 18.755334ms
--- PASS: TestPlugin_MemorySmall (1.17s)

=== RUN   TestPlugin_MemoryMedium
2022/12/09 15:14:14 Size of data being passed 5000 (703890 bytes)
2022/12/09 15:14:14 calling plugin 0
2022/12/09 15:14:16 call execution time: 2.158440416s
2022/12/09 15:14:16 calling plugin 1
2022/12/09 15:14:18 call execution time: 1.677055583s
2022/12/09 15:14:18 calling plugin 2
2022/12/09 15:14:20 call execution time: 2.145829459s
2022/12/09 15:14:20 calling plugin 3
2022/12/09 15:14:20 call execution time: 100.430083ms
...
022/12/09 15:15:10 call execution time: 11.31319075s
2022/12/09 15:15:10 calling plugin 18
2022/12/09 15:15:10 call execution time: 122.91525ms
2022/12/09 15:15:10 calling plugin 19
2022/12/09 15:15:10 call execution time: 132.877958ms
--- PASS: TestPlugin_MemoryMedium (56.58s)
=== RUN   TestPlugin_MemoryBig
2022/12/09 15:15:10 Size of data being passed 10000 (1408890 bytes)
2022/12/09 15:15:10 calling plugin 0
...
Never completes
@mathetake
Copy link
Contributor

mathetake commented Dec 22, 2022

ack there's something memory leak might be happening in host generated code, as I confirmed the following test code can succeed fast with tinygo test -target wasi ./....

var p TestPlugin

func Test_In_TinyGo(t *testing.T) {
	ctx := context.Background()
	const entrySize = 10000
	// Initialize.
	response, err := p.PassData(ctx, host.PassDataRequest{
		Entries:  entrySize,
		Sequence: 0,
	})
	if err != nil {
		t.Fatal(err)
	}
	if len(response.Data) != entrySize {
		t.Fatalf("data size mismatch: %d != %d", len(response.Data), entrySize)
	}

	log.Printf("Size of data being passed %d (%d bytes)", len(response.Data), response.SizeVT())

	for i := uint64(0); i < 20; i++ {
		log.Printf("calling plugin %d", i)
		start := time.Now()
		response, err = p.PassData(context.Background(), host.PassDataRequest{
			Sequence: i,
			Entries:  entrySize,
			Data:     response.Data,
		})
		log.Printf("call execution time: %s", time.Since(start))
		if err != nil {
			t.Error(err)
			return
		}

		if len(response.Data) != int(entrySize) {
			t.Errorf("expected %d entries, got %d", entrySize, len(response.Data))
			return
		}
	}
}

@mathetake
Copy link
Contributor

well, the test case is not enough to simulate the behavior -- I have to copy the entire PassDataRequest in each iteration. WIll fix it

@mathetake
Copy link
Contributor

Updated, though it's till finishing in a decent time vs go test --- PASS: TestPlugin_MemoryMedium (80.04s):

$ tinygo test -target wasi ./...
--snip--
ok      github.com/cchamplin/plugin-bug/guest   9.114s
package main

import (
	"context"
	host "github.com/cchamplin/plugin-bug/plugin"
	"log"
	"strings"
	"testing"
	"time"
)

var p TestPlugin

func Test_In_TinyGo(t *testing.T) {
	ctx := context.Background()
	const entrySize = 10000
	// Initialize.
	response, err := p.PassData(ctx, host.PassDataRequest{
		Entries:  entrySize,
		Sequence: 0,
	})
	if err != nil {
		t.Fatal(err)
	}
	if len(response.Data) != entrySize {
		t.Fatalf("data size mismatch: %d != %d", len(response.Data), entrySize)
	}

	log.Printf("Size of data being passed %d (%d bytes)", len(response.Data), response.SizeVT())

	for i := uint64(0); i < 20; i++ {
		log.Printf("calling plugin %d", i)
		start := time.Now()
		req := &host.PassDataRequest{
			Sequence: i,
			Entries:  entrySize,
			Data:     make(map[string]*host.State, entrySize),
		}
		for k, v := range response.Data {
			state := &host.State{
				FieldA: strings.Clone(v.FieldA),
				FieldB: strings.Clone(v.FieldB),
				FieldC: strings.Clone(v.FieldC),
				FieldD: strings.Clone(v.FieldD),
				FieldE: strings.Clone(v.FieldE),
				FieldF: strings.Clone(v.FieldF),
				FieldG: strings.Clone(v.FieldG),
				FieldH: strings.Clone(v.FieldH),
				FieldI: strings.Clone(v.FieldI),
				FieldJ: strings.Clone(v.FieldJ),
				FieldK: strings.Clone(v.FieldK),
				FieldL: strings.Clone(v.FieldL),
				FieldM: strings.Clone(v.FieldM),
				FieldN: strings.Clone(v.FieldN),
			}
			req.Data[k] = state
		}

		response, err = p.PassData(context.Background(), *req)
		log.Printf("call execution time: %s", time.Since(start))
		if err != nil {
			t.Error(err)
			return
		}

		if len(response.Data) != int(entrySize) {
			t.Errorf("expected %d entries, got %d", entrySize, len(response.Data))
			return
		}
	}
}

@mathetake
Copy link
Contributor

I am pretty confident that this is an TinyGo issue. Could you try https://github.com/wasilibs/nottinygc to see problems will be gone?

ref: tetratelabs/proxy-wasm-go-sdk#349 (comment)

@brennanjl
Copy link

Just stumbled across this, was wondering if anyone ever got this figured out @mathetake @cchamplin ?

Looking to use this library with Wazero in a data intensive application, with plugins written in TinyGo, but want to see if this got resolved.

@codefromthecrypt
Copy link
Collaborator

@brennanjl because large amounts of data have to be re-serialized into protobuf each time, it will be difficult to support with this model I think.

First, really consider what @mathetake said because I have seen one project drop 1/2 its overhead by using https://github.com/wasilibs/nottinygc

After that, if you still have issues, I think the choices are..

  1. use this project, but make your own special host functions for the bulk data. (just the bulk is hard)
  2. use a raw ABI approach instead of this project. (everything is hard)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants