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

connect() Socket API undefined #100

Open
elithrar opened this issue Apr 16, 2024 · 11 comments
Open

connect() Socket API undefined #100

elithrar opened this issue Apr 16, 2024 · 11 comments

Comments

@elithrar
Copy link
Contributor

Raising this as I debug in parallel.

Summary: It appears that the attempt to get a handler on the connect method fails and returns undefined: https://github.com/syumai/workers/blob/main/cloudflare/sockets/connect.go#L33 -> https://github.com/syumai/workers/blob/main/internal/runtimecontext/context.go#L34

  • I originally ran into this when using a custom Dialer implementation to have lib/pq talk to Hyperdrive, and pq would return the below and fail to actually establish a TCP connection:
  • From looking at the source of runtimecontext I suspected I needed to plumb through a context.Context, but from the example below that doesn't seem to change anything here.
  • What am I missing?
# This is from https://github.com/syumai/workers/blob/main/internal/runtimecontext/context.go#L34
  (log) 2024/04/15 16:43:11 query failed: &errors.errorString{s:"pq: unexpected error: \"runtime object was not found\""}

Distilling this down into the minimal repro (below) I see this:

✘ [ERROR]   TypeError: Cannot read properties of undefined (reading 'exports')
package main

import (
	"net/http"
	"github.com/syumai/workers"
	"github.com/syumai/workers/cloudflare/sockets"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		msg := "Hello!"
		w.Write([]byte(msg))
	})
	// We pass the request context into `sockets.Connect` here
	http.HandleFunc("/dial", func(w http.ResponseWriter, r *http.Request) {
		sockets.Connect(r.Context(), "blog.questionable.services:80", &sockets.SocketOptions{SecureTransport: sockets.SecureTransportOff})
	})

	workers.Serve(nil) // use http.DefaultServeMux
}
syumai added a commit to syumai/workers-playground that referenced this issue Apr 16, 2024
@syumai
Copy link
Owner

syumai commented Apr 16, 2024

@elithrar Thank you for raising this issue!

The minimal reproduction seems to be more simplified.
Here is my reproduction code: https://github.com/syumai/workers-playground/tree/main/syumai-workers-repro-issue-100
It seems that not writing a response body in HandlerFunc causes a weird error.

So it seems not related to the original Issue. I'll separate a new Issue for that.


Perhaps the original problem is caused by custom Dialer implementation.
It is possible that the Context used by Dialer is different from the one created by syumai/workers.
I'm not familiar with the implementation of Dialer itself, so I apologize for the fact that this is just a guess.
If I could see the implementation, I might be able to do some analysis.

On the other hand, this problem is likely to be resolved by another issue (#90) that removes the dependency on Context.
I will try to work on this issue as soon as possible.

@syumai
Copy link
Owner

syumai commented Apr 16, 2024

I don't know if it will be helpful, but here is an example of how I used go-sql-driver/mysql and connect().
I confirmed that this worked.
https://github.com/syumai/workers/blob/main/_examples/mysql-blog-server/app/handler.go#L29-L40

@elithrar
Copy link
Contributor Author

Thanks for investigating!

The full code (since it's a bit more verbose...) — as a work-in-progress / still very hacky — is below. With *http.Request.Context plumbed through into the call to socket.Connect I get the below - when we try to write/read from the socket in db.Ping it fails:

  (log) 2024/04/16 13:21:41 failed to ping DB: js.Error{Value:js.Value{_:[0]func(){}, ref:0x7ff8000100000072, gcPtr:(*js.ref)(0x140cd60)}}
✘ [ERROR]   TypeError: Cannot read properties of undefined (reading 'exports')

Code:

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/url"
	"time"

	"github.com/lib/pq"

	"github.com/syumai/workers"
	"github.com/syumai/workers/cloudflare"
	"github.com/syumai/workers/cloudflare/sockets"
)

type HyperdriveDialer struct {
	bindingName      string
	connectionString string
	context          context.Context
	timeout          time.Duration
}

func (hd *HyperdriveDialer) Dial(network, addr string) (net.Conn, error) {
	return sockets.Connect(hd.context, hd.connectionString, &sockets.SocketOptions{SecureTransport: sockets.SecureTransportOff})
}

func (hd *HyperdriveDialer) DialTimeout(network string, addr string, timeout time.Duration) (net.Conn, error) {
	// TODO(matt): Plumb through the timeout
	return hd.Dial(network, addr)
}

func (hd *HyperdriveDialer) Address() string {
	uri, err := url.Parse(hd.connectionString)
	if err != nil {
		return ""
	}
	return uri.Hostname()
}

func (hd *HyperdriveDialer) ConnectionString() string {
	return hd.connectionString
}

func NewHyperdriveDialer(ctx context.Context, bindingName string) *HyperdriveDialer {
	return &HyperdriveDialer{
		connectionString: cloudflare.GetBinding(ctx, bindingName).Get("connectionString").String(),
		context:          ctx,
	}
}

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		msg := "Hello!"
		w.Write([]byte(msg))
	})

	http.HandleFunc("/query", queryDB)

	workers.Serve(nil) // use http.DefaultServeMux
}

func queryDB(w http.ResponseWriter, r *http.Request) {
	hd := NewHyperdriveDialer(r.Context(), "HYPERDRIVE")
	connector, err := pq.NewConnector(hd.ConnectionString())
	if err != nil {
		log.Printf("#%v\n", err)
		fmt.Fprintf(w, "bad")
		return
	}

	log.Printf("connectionString: %#v\n", hd.ConnectionString())

	connector.Dialer(hd)
	db := sql.OpenDB(connector)
	if err != nil {
		log.Printf("opening db failed: %#v\n", err)
		fmt.Fprintf(w, "bad")
		return
	}

	if err := db.PingContext(r.Context()); err != nil {
		log.Printf("failed to ping DB: %#v\n", err)
		fmt.Fprintf(w, "failed to ping DB %#v\n", err)
		return
	}

	rows, err := db.Query("SELECT * FROM pg_tables LIMIT 1")
	if err != nil {
		log.Printf("query failed: %#v\n", err)
		fmt.Fprintf(w, "bad")
		return
	}

	names := make([]string, 0)
	for rows.Next() {
		var name string
		if err = rows.Scan(&name); err != nil {
			break
		}

		names = append(names, name)
	}

	if err := rows.Close(); err != nil {
		log.Printf("closing rows failed; %#v\n", err)
		fmt.Fprintf(w, "bad")
		return
	}

	fmt.Fprintf(w, "#%v\n", names)
}

@syumai
Copy link
Owner

syumai commented Apr 16, 2024

Dial(network, addr string) (net.Conn, error) {

looks using correct Context, I wonder why this code is receiving runtime object was not found error 🤔

By the way, #102 is merged and now sockets.Connect() in main branch has no dependency on special Context (it can also be plain context.Background()).
I wish it will help your investigation.

@elithrar
Copy link
Contributor Author

Thank you @syumai!

I've pulled the latest version of this package down — some progress but still running into an issue when attempting to use the socket. This could be outside of this lib:

➜  go get github.com/syumai/workers@01783f2
go: downloading github.com/syumai/workers v0.23.4-0.20240416160017-01783f2ba18c
go: upgraded github.com/syumai/workers v0.23.3 => v0.23.4-0.20240416160017-01783f2ba18c
  (log) 2024/04/16 16:23:34 failed to ping DB: js.Error{Value:js.Value{_:[0]func(){}, ref:0x7ff8000100000072, gcPtr:(*js.ref)(0x140cd60)}}
  (log) fatal error: all goroutines are asleep - deadlock!
  (log)
  (log) goroutine 1 [chan receive]:
  (log) github.com/syumai/workers.Serve(...)
  (log)         /Users/matt/repos/go/pkg/mod/github.com/syumai/[email protected]/handler.go:94
  (log) main.main()
  (log)         /Users/matt/repos/go-on-workers/main.go:78 +0xa
  (log)
  (log) goroutine 6 [waiting]:
  (log) syscall/js.Value.New({{}, 0x7ff800040000000f, 0x140c068}, {0x1440e28, 0x1, 0x1})
  (log)         /opt/homebrew/Cellar/go/1.22.2/libexec/src/syscall/js/js.go:431 +0x3
  (log) github.com/syumai/workers/internal/jsutil.NewPromise(...)
  (log)         /Users/matt/repos/go/pkg/mod/github.com/syumai/[email protected]/internal/jsutil/jsutil.go:34
  (log) github.com/syumai/workers.init.0.func1({{}, 0x7ff800010000000e, 0x140c460}, {0x140e230, 0x1, 0x1})
  (log)         /Users/matt/repos/go/pkg/mod/github.com/syumai/[email protected]/handler.go:40 +0xf
  (log) syscall/js.handleEvent()
  (log)         /opt/homebrew/Cellar/go/1.22.2/libexec/src/syscall/js/func.go:100 +0x23
  (log)
  (log) goroutine 9 [select]:
  (log) database/sql.(*DB).connectionOpener(0x144ca90, {0xd8c90, 0x14380f0})
  (log)         /opt/homebrew/Cellar/go/1.22.2/libexec/src/database/sql/sql.go:1246 +0xa
  (log) created by database/sql.OpenDB in goroutine 8
  (log)         /opt/homebrew/Cellar/go/1.22.2/libexec/src/database/sql/sql.go:824 +0xc
  (warn) exit code: 2
✘ [ERROR]   TypeError: Cannot read properties of undefined (reading 'exports')

(still investigating)

@ndisidore
Copy link

ndisidore commented Apr 17, 2024

I was also able to reproduce this issue, both deployed and locally. For context, this is connecting to posgres on Neon DB.
Interestingly, if I bypass Hyperdrive and attempt to connect to the database directly, we appear to make progress (it errors, but not with deadlock error above). Will also keep poking at this

Edit: I may have been overly ambitious with this reply - when I bypassed hyperdrive I used the default postgres driver which means we weren't making use of the custom dialer - dialing over sockets could still be an issue here

@syumai
Copy link
Owner

syumai commented Apr 18, 2024

I'll investigate this after #103 is resolved.

@syumai
Copy link
Owner

syumai commented Apr 19, 2024

This issue probably has the same problem as #103.
io.Pipe response streaming seems to have a blocking issue.
#103 (comment)

@syumai
Copy link
Owner

syumai commented Apr 20, 2024

@elithrar A stream conversion bug is fixed in #110 and released as v0.25.0.
Could you please try using the updated version?

@elithrar
Copy link
Contributor Author

elithrar commented Apr 20, 2024

Just updated to v0.25.0 - still seeing the same issue:

  (log) 2024/04/20 20:30:22 query failed: js.Error{Value:js.Value{_:[0]func(){}, ref:0x7ff8000100000075, gcPtr:(*js.ref)(0x140cd88)}}
  (log) fatal error: all goroutines are asleep - deadlock!
  (log)
  (log) goroutine 1 [chan receive]:
  (log) github.com/syumai/workers.Serve(...)
  (log)         /Users/matt/repos/go/pkg/mod/github.com/syumai/[email protected]/handler.go:96
  (log) main.main()
  (log)         /Users/matt/repos/go-on-workers/main.go:69 +0xa
  (log)
  (log) goroutine 6 [waiting]:
  (log) syscall/js.Value.New({{}, 0x7ff800040000000f, 0x140c068}, {0x1440e38, 0x1, 0x1})
  (log)         /opt/homebrew/Cellar/go/1.22.2/libexec/src/syscall/js/js.go:431 +0x3
  (log) github.com/syumai/workers/internal/jsutil.NewPromise(...)
  (log)         /Users/matt/repos/go/pkg/mod/github.com/syumai/[email protected]/internal/jsutil/jsutil.go:34
  (log) github.com/syumai/workers.init.0.func1({{}, 0x7ff800010000000e, 0x140c460}, {0x140e230, 0x1, 0x1})
  (log)         /Users/matt/repos/go/pkg/mod/github.com/syumai/[email protected]/handler.go:43 +0xe
  (log) syscall/js.handleEvent()
  (log)         /opt/homebrew/Cellar/go/1.22.2/libexec/src/syscall/js/func.go:100 +0x23
  (log)
  (log) goroutine 9 [select]:
  (log) database/sql.(*DB).connectionOpener(0x144aa90, {0xd9010, 0x1438140})
  (log)         /opt/homebrew/Cellar/go/1.22.2/libexec/src/database/sql/sql.go:1246 +0xa
  (log) created by database/sql.OpenDB in goroutine 8
  (log)         /opt/homebrew/Cellar/go/1.22.2/libexec/src/database/sql/sql.go:824 +0xc
  (warn) exit code: 2
✘ [ERROR]   TypeError: Cannot read properties of undefined (reading 'exports')

Bypassing Hyperdrive and just going direct to the origin database fails with the same issue (just to remove Hyperdrive's proxy from the equation).

A simple sockets.Connect and conn.Write appears to successfully write bytes to the socket:

https://go-on-workers.silverlock.workers.dev/dial
# Returns "14" after writing "GET HTTP/1.1 /" to the socket

@syumai
Copy link
Owner

syumai commented Apr 21, 2024

OK, thank you for detail information! I'll continue investigation.

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

3 participants