golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124k stars 17.67k forks source link

x/exp/shiny/driver/x11driver: runtime error on centos-7 #15100

Open sbinet opened 8 years ago

sbinet commented 8 years ago

Please answer these questions before submitting your issue. Thanks!

  1. What version of Go are you using (go version)?
$> go version
go version go1.6 linux/amd64
  1. What operating system and processor architecture are you using (go env)?
$> go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/go"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GO15VENDOREXPERIMENT="1"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"
CXX="g++"
CGO_ENABLED="1"
  1. What did you do?
$> go get -t -u golang.org/x/exp/shiny/...
$> cd $GOPATH/src/golang/x/exp/shiny/example/basic
$> go build main.go
$> ./main
  1. What did you expect to see? a window with 3 rounded squares.
  2. What did you see instead?

an empty window (with whatever was displayed behind that window, captured into that window but not refreshed) and the following error:

$> ./main
2016/04/04 13:47:06 x11driver: xproto.WaitForEvent: BadAccess {NiceName: Access, Sequence: 17, BadValue: 92274692, MinorOpcode: 1, MajorOpcode: 130}
2016/04/04 13:47:06 x11driver: xproto.WaitForEvent: BadBadSeg {NiceName: BadSeg, Sequence: 21, BadValue: 92274691, MinorOpcode: 3, MajorOpcode: 130}

The "interesting" thing is that the pointer-painting example from xgbutil works correctly. So it presumably is some configuration issue on the x11driver end. (I tried replacing xgb.NewConn() with xgbutil.NewConn() and adapt a bit, to no avail)

sbinet commented 8 years ago

FYI, it's also present when using a docker container (centos:7).

$> xhost +
$> docker run -ti --rm \
      -e DISPLAY=$DISPLAY \
      -v /tmp/.X11-unix:/tmp/.X11-unix \
      centos:7 bash

docker> yum install -y git gcc && \
 curl -O -L https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz && \
 tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz

docker> export PATH=/usr/local/go/bin:$PATH
docker> export GOPATH=/go
docker> go get -t -u golang.org/x/exp/shiny/driver/x11driver
docker> cd $GOPATH/src/golang.org/x/exp/shiny/example/basic
docker> go run main.go
XGB: conn.go:47: Could not get authority info: open /root/.Xauthority: no such file or directory
XGB: conn.go:48: Trying connection without authority info...
2016/04/07 12:33:07 x11driver: xproto.WaitForEvent: BadAccess {NiceName: Access, Sequence: 17, BadValue: 83886084, MinorOpcode: 1, MajorOpcode: 130}
2016/04/07 12:33:07 x11driver: xproto.WaitForEvent: BadBadSeg {NiceName: BadSeg, Sequence: 21, BadValue: 83886083, MinorOpcode: 3, MajorOpcode: 130}

@nigeltao any idea?

nigeltao commented 8 years ago

What is $HOME and $XAUTHORITY for both cases? For docker, it looks like $HOME is "/home", which looks weird. Are you running as root? (Sorry, I'm not very familiar with docker).

On centos, could you strace both the xgb- and xgbutil-based programs and see what files they open, whose names look vaguely like "Xauthority"?

nigeltao commented 8 years ago

Ah, never mind about the strace. I just read the first post again, and if you can get a window up, then xgb should have auth'd correctly, even if the window isn't painting properly. Hmm...

sbinet commented 8 years ago

just a small update. the following program works on centos-7:

package main

import (
    "fmt"
    "image"
    "image/color"
    "image/draw"
    "time"

    "github.com/BurntSushi/xgbutil"
    "github.com/BurntSushi/xgbutil/xevent"
    "github.com/BurntSushi/xgbutil/xgraphics"
)

func main() {
    const (
        w = 150
        h = 150
    )
    img := image.NewRGBA(image.Rect(0, 0, int(w), int(h)))

    X, err := xgbutil.NewConn()
    fatal(err)

    ximg := xgraphics.New(X, img.Bounds())

    wid := ximg.XShowExtra("foo", true)
    go func() {
        xevent.Main(X)
    }()

    ximg.XDraw()
    ximg.XPaint(wid.Id)
    fmt.Printf("window id: %v\n", wid.Id)

    time.Sleep(2 * time.Second)
    fmt.Printf("drawing white...\n")
    draw.Draw(ximg, ximg.Bounds(), image.NewUniform(color.White), image.Point{0, 0}, draw.Src)
    ximg.XDraw()
    ximg.XPaint(wid.Id)

    time.Sleep(5 * time.Second)
}

func fatal(err error) {
    if err != nil {
        panic(err)
    }
}
nigeltao commented 8 years ago

I'd still like to know what $HOME and $XAUTHORITY are, and whether you're running as root.

sbinet commented 8 years ago

inside the docker container, I get these:

[binet@32e78b70bb07 basic]$ whoami
binet
[binet@32e78b70bb07 basic]$ echo $HOME
/home/binet
[binet@32e78b70bb07 basic]$ echo "auth=[$XAUTHORITY]"
auth=[]

inside a proper centos-7 VM, I am indeed running as root:

[root@themachine ~]# whoami
root
[root@themachine ~]# echo $HOME
/root
[root@themachine ~]# echo "auth=[$XAUTHORITY]"
auth=[]
sbinet commented 8 years ago

and, on another Centos-7 machine I have access to, as a regular user:

13:37 binet@lxplus0099 shiny/example/basic% ./main 
2016/04/15 13:37:40 x11driver: xproto.WaitForEvent: BadAccess {NiceName: Access, Sequence: 17, BadValue: 106954756, MinorOpcode: 1, MajorOpcode: 130}
2016/04/15 13:37:40 x11driver: xproto.WaitForEvent: BadBadSeg {NiceName: BadSeg, Sequence: 21, BadValue: 106954755, MinorOpcode: 3, MajorOpcode: 130}
^C
zsh: interrupt  ./main
13:37 binet@lxplus0099 shiny/example/basic% echo $HOME
/afs/cern.ch/user/b/binet
13:37 binet@lxplus0099 shiny/example/basic% whoami
binet
13:37 binet@lxplus0099 shiny/example/basic% echo $XAUTHORITY

13:38 binet@lxplus0099 shiny/example/basic% cat /etc/redhat-release 
CentOS Linux release 7.2.1511 (Core) 
sbinet commented 8 years ago

did a bit of print-foo debugging. here are the print-outs:

$ ./main
-- render.Init --
--- case 1: err=<nil> seq=1
----> cookies... (seq=1 err=<nil>)
-- shm.Init --
--- case 1: err=<nil> seq=2
----> cookies... (seq=2 err=<nil>)
-- newScreenImpl --
xsi...
initAtoms...
--- case 1: err=<nil> seq=3
----> cookies... (seq=3 err=<nil>)
--- case 1: err=<nil> seq=4
----> cookies... (seq=4 err=<nil>)
--- case 1: err=<nil> seq=5
----> cookies... (seq=5 err=<nil>)
initKeyboardMapping...
--- case 1: err=<nil> seq=6
----> cookies... (seq=6 err=<nil>)
initPictformats...
--- case 1: err=<nil> seq=7
----> cookies... (seq=7 err=<nil>)
initWindow32...
--- case 1: err=<nil> seq=9
----> cookies... (seq=9 err=<nil>)
s.xsi.Root-Depth = 24 (24)
--- case 1: err=<nil> seq=11
----> cookies... (seq=11 err=<nil>)
--- case 1: err=<nil> seq=13
----> cookies... (seq=13 err=<nil>)
go run()...
... wait for event...
--- case 1: err=<nil> seq=15
----> cookies... (seq=15 err=<nil>)
--- case 1: err=<nil> seq=17
----> cookies... (seq=17 err=<nil>)
--- case 1: err=<nil> seq=19
----> cookies... (seq=19 err=<nil>)
--- case 1: err=<nil> seq=21
----> cookies... (seq=21 err=<nil>)
--- case 1: err=<nil> seq=23
----> cookies... (seq=23 err=<nil>)
--- case default: err=<nil> evtnum=21
--- case 0: err=BadAccess {NiceName: Access, Sequence: 24, BadValue: 111149060, MinorOpcode: 1, MajorOpcode: 130} seq=24
buf=[0 10 24 0 4 0 160 6 1 0 130 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
----> cookies... (seq=24 err=BadAccess {NiceName: Access, Sequence: 24, BadValue: 111149060, MinorOpcode: 1, MajorOpcode: 130})
****cookie err: BadAccess {NiceName: Access, Sequence: 24, BadValue: 111149060, MinorOpcode: 1, MajorOpcode: 130}
... wait for event...
2016/04/15 12:33:55 x11driver: xproto.WaitForEvent: BadAccess {NiceName: Access, Sequence: 24, BadValue: 111149060, MinorOpcode: 1, MajorOpcode: 130}
... wait for event...
--- case default: err=<nil> evtnum=22
... wait for event...
--- case 1: err=<nil> seq=26
----> cookies... (seq=26 err=<nil>)
--- case 1: err=<nil> seq=28
----> cookies... (seq=28 err=<nil>)
--- case default: err=<nil> evtnum=22
... wait for event...
--- case 1: err=<nil> seq=30
----> cookies... (seq=30 err=<nil>)
--- case 0: err=BadBadSeg {NiceName: BadSeg, Sequence: 31, BadValue: 111149059, MinorOpcode: 3, MajorOpcode: 130} seq=31
buf=[0 128 31 0 3 0 160 6 3 0 130 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
----> cookies... (seq=31 err=BadBadSeg {NiceName: BadSeg, Sequence: 31, BadValue: 111149059, MinorOpcode: 3, MajorOpcode: 130})
****cookie err: BadBadSeg {NiceName: BadSeg, Sequence: 31, BadValue: 111149059, MinorOpcode: 3, MajorOpcode: 130}
2016/04/15 12:33:55 x11driver: xproto.WaitForEvent: BadBadSeg {NiceName: BadSeg, Sequence: 31, BadValue: 111149059, MinorOpcode: 3, MajorOpcode: 130}

which correspond to: https://github.com/golang/exp/blob/master/shiny/vendor/github.com/BurntSushi/xgb/xgb.go#L400

        buf := make([]byte, 32)
        err, seq = nil, 0
        if _, err := io.ReadFull(c.conn, buf); err != nil {
            Logger.Printf("A read error is unrecoverable: %s", err)
            fmt.Printf("**** read error: %v\n", err)
            c.eventChan <- err
            c.Close()
            continue
        }
        switch buf[0] {
        case 0: // This is an error
            // Use the constructor function for this error (that is auto
            // generated) by looking it up by the error number.
            newErrFun, ok := NewErrorFuncs[int(buf[1])]
            if !ok {
                Logger.Printf("BUG: Could not find error constructor function "+
                    "for error with number %d.", buf[1])
                continue
            }
            err = newErrFun(buf)
            seq = err.SequenceId()
            fmt.Printf("--- case 0: err=%v seq=%v\nbuf=%v\n", err, seq, buf)
            // This error is either sent to the event channel or a specific
            // cookie's error channel below.
        case 1: // This is a reply
            seq = Get16(buf[2:])

            // check to see if this reply has more bytes to be read
            size := Get32(buf[4:])
            if size > 0 {
                byteCount := 32 + size*4
                biggerBuf := make([]byte, byteCount)
                copy(biggerBuf[:32], buf)
                if _, err := io.ReadFull(c.conn, biggerBuf[32:]); err != nil {
                    Logger.Printf("A read error is unrecoverable: %s", err)
                    fmt.Printf("**** read-error: %v\n", err)
                    c.eventChan <- err
                    c.Close()
                    continue
                }
                replyBytes = biggerBuf
            } else {
                replyBytes = buf
            }
            fmt.Printf("--- case 1: err=%v seq=%v\n", err, seq)

            // This reply is sent to its corresponding cookie below.
        default: // This is an event
            // Use the constructor function for this event (like for errors,
            // and is also auto generated) by looking it up by the event number.
            // Note that we AND the event number with 127 so that we ignore
            // the most significant bit (which is set when it was sent from
            // a SendEvent request).
            evNum := int(buf[0] & 127)
            newEventFun, ok := NewEventFuncs[evNum]
            if !ok {
                Logger.Printf("BUG: Could not find event construct function "+
                    "for event with number %d.", evNum)
                fmt.Printf("*** could not find event construct[%d]\n", evNum)
                continue
            }
            fmt.Printf("--- case default: err=%v evtnum=%v\n", err, evNum)
            c.eventChan <- newEventFun(buf)
            continue
        }

        fmt.Printf("----> cookies... (seq=%v err=%v)\n", seq, err)
        // At this point, we have a sequence number and we're either
        // processing an error or a reply, which are both responses to
        // requests. So all we have to do is find the cookie corresponding
        // to this error/reply, and send the appropriate data to it.
        // In doing so, we make sure that any cookies that came before it
        // are marked as successful if they are void and checked.
        // If there's a cookie that requires a reply that is before this
        // reply, then something is wrong.
        for cookie := range c.cookieChan {
            // This is the cookie we're looking for. Process and break.
            if cookie.Sequence == seq {
                if err != nil { // this is an error to a request
                    // synchronous processing
                    if cookie.errorChan != nil {
                        fmt.Printf("**** sync-cookie error: %v\n", err)
                        cookie.errorChan <- err
                    } else { // asynchronous processing
                        fmt.Printf("****cookie err: %v\n", err)
                        c.eventChan <- err
                        // if this is an unchecked reply, ping the cookie too
                        if cookie.pingChan != nil {
                            cookie.pingChan <- true
                        }
                    }
                } else { // this is a reply
                    if cookie.replyChan == nil {
                        Logger.Printf("Reply with sequence id %d does not "+
                            "have a cookie with a valid reply channel.", seq)
                        continue
                    } else {
                        cookie.replyChan <- replyBytes
                    }
                }
                break
            }

hth, -s

aarzilli commented 8 years ago

@sbinet: are you trying to use shiny with a remote display server? The 'afs' in your home path makes me think you are.

sbinet commented 8 years ago

@aarzilli dunno. with X11, remote, server and client have (to me) unclear semantics. in the specific case of the afs test:

hth

sbinet commented 8 years ago

for all the variations of (centos-7) machines to which I ssh, the BurntSushi/xgb examples are working. the shiny ones do not.

aarzilli commented 8 years ago

with X11, server and client have (to me) unclear semantics.

I can sympathize with this.

So, the display server (ie the thing that puts stuff on the screen) you are trying to use is running on your local archlinux64b machine, the shiny program is running on a CentOS-7 machine you are ssh'ing into. This makes the display server "remote" wrt the shiny program.

AFAIK this won't work, x11driver uses MIT-SHM to upload the image and MIT-SHM doesn't work without, you know, actual "shared memory" between the client and the display server.

xgbutil and xgb examples work because they are using server side pixmaps (or whatever they are called, I'm not an expert). I guess it wouldn't be too much work to add PutImage as a fallback for uploading but remember that we're all getting waylanded soon and once we are in wayland-land there's no way remote display servers work.

gopherbot commented 4 years ago

Change https://golang.org/cl/213199 mentions this issue: shiny/driver/x11driver: failback to regular pixmaps if SHM is unavaiable