Skip to content

Apparently simple program fails to behave like native #262

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

Closed
FiloSottile opened this issue Jul 21, 2015 · 5 comments
Closed

Apparently simple program fails to behave like native #262

FiloSottile opened this issue Jul 21, 2015 · 5 comments

Comments

@FiloSottile
Copy link

Here's a small program

package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/cloudflare/dns"
    "github.com/gopherjs/gopherjs/js"
)

func main() {
    js.Global.Set("go", map[string]interface{}{
        "ToDS": ToDS,
    })
}

func ToDS(zone string) {
    go func() {
        zone := "filippo.io.\t3600\tIN\tDNSKEY\t257 3 13 DGpDkudNu/XQT1KmQkXFtKCfZPxHGV07qSTIcDXS33/WtT8UUG7LyxAgKznsRSFEhiQVR53E69/E57IFm8b6Zw=="

        r := strings.NewReader(zone)
        for {
            rr, err := dns.ReadRR(r, "")
            if err != nil {
                log.Println(err)
                continue
            }
            if rr == nil {
                break
            }

            dnskey, ok := rr.(*dns.DNSKEY)
            if !ok {
                log.Println("Not a DNSKEY:", rr)
                continue
            }

            if dnskey.Flags&dns.SEP == 0 {
                // ZSK
                continue
            }

            ds1 := dnskey.ToDS(dns.SHA1)
            ds2 := dnskey.ToDS(dns.SHA256)

            js.Global.Get("document").Call("write", fmt.Sprintf("%s\n%s\n", ds1, ds2))
        }
    }()
}

I compile it with default options and load it in a empty page, and then from the console:

> go.ToDS("")
< undefined
syscall.go:44 2015/07/21 01:32:52 dns: not a TTL: "DGpDkudNu/XQT1KmQkXFtKCfZPxHGV07qSTIcDXS33/WtT8UUG7LyxAgKznsRSFEhiQVR53E69/E57IFm8b6Zw==" at line: 1:124

The parser fails in some weird way. Different inputs fail in different ways.

Here's a equivalent program, that I run with go run

package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/cloudflare/dns"
)

func main() {
    zone := "filippo.io.\t3600\tIN\tDNSKEY\t257 3 13 DGpDkudNu/XQT1KmQkXFtKCfZPxHGV07qSTIcDXS33/WtT8UUG7LyxAgKznsRSFEhiQVR53E69/E57IFm8b6Zw=="

    r := strings.NewReader(zone)
    for {
        rr, err := dns.ReadRR(r, "")
        if err != nil {
            log.Println(err)
            continue
        }
        if rr == nil {
            break
        }

        dnskey, ok := rr.(*dns.DNSKEY)
        if !ok {
            log.Println("Not a DNSKEY:", rr)
            continue
        }

        if dnskey.Flags&dns.SEP == 0 {
            // ZSK
            continue
        }

        ds1 := dnskey.ToDS(dns.SHA1)
        ds2 := dnskey.ToDS(dns.SHA256)

        println(fmt.Sprintf("%s\n%s\n", ds1, ds2))
    }
}
$ go run src/dnskey-to-ds/test.go
filippo.io. 3600    IN  DS  42 13 1 6D68D297C0AF610513996154BDDDEDA300831957
filippo.io. 3600    IN  DS  42 13 2 2DDD9653A96320688619B6F0EBACEC1A2F4DEBA8D563A6D37C64FB3197C108A7

First time gopherjs user, so I might be missing something, but it looks like a bug in the channel usage of dns.ReadRR (source)

@dmitshur
Copy link
Member

Hi @FiloSottile (I know you from Twitter and I'm a big fan of your work and use of Go!),

At first I was going to say this is likely because that github.com/cloudflare/dns package imports syscall and likely does some low level stuff that cannot run directly in the browser (like os.Open a file on local disk).

However, I don't think that's the problem. I think you're right and there may be sort of bug with GopherJS channel support.

I've traced the program both via go and gopherjs, and you can clearly see they start running identically, but soon there is an odd deviation in behavior:

image

Which causes the two executions to return different results:

image

The full log files are here: go.txt and gopherjs.txt.

Edit: Actually, there are goroutines involved so exact order of execution may vary (it's a flattened log) and therefore the actual difference in behavior is smaller than the text diff may suggest. But there are still some differences, like the first lex value printed has different fields, and IsDomainName("filippo.io.") is never called in GopherJS version.

This is definitely worth looking into and fixing, and the good news is that I think it's likely that GopherJS will soon be able to run this code successfully. Thanks for the bug report!

@dmitshur
Copy link
Member

I've made a minimal reproducible test case for the issue:

package main

import "fmt"

type T struct{ string }

func main() {
    c := make(chan T)
    go foo(c)
    fmt.Println(<-c)
}

func foo(c chan T) {
    var t T
    t.string = "Original"
    c <- t
    t.string = "Modified After Sending"
}

Expected outcome (and actual output for gc compiler) is:

{Original}

Whereas GopherJS output is:

{Modified After Sending}

http://www.gopherjs.org/playground/#/glD7xks6KD

I have a guess the issue might be very trivial, when sending on a channel, the value should be copied, rather than original being used.

@neelance
Copy link
Member

Yes, we have to explicitly copy the values in JS. Sorry for the trouble and thanks for reporting!

@FiloSottile
Copy link
Author

Awesome! Thanks for the super-fast investigation and fix @shurcooL and @neelance

@neelance
Copy link
Member

@shurcooL btw: Thanks for investigating and the test case. Because of that, I immediately knew what was wrong and how to fix it. Much appreciated.

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