Yes ,this is the right forum and I understand you are already in contact with someone on the Go team on this topic.
Original Message:
Sent: Thu June 06, 2024 07:39 PM
From: Frank Swarbrick
Subject: Possible Go z/OS runtime issue.
Is there a better place to report possible issues?
------------------------------
Frank Swarbrick
Original Message:
Sent: Sun June 02, 2024 03:19 PM
From: Frank Swarbrick
Subject: Possible Go z/OS runtime issue.
//go:build zos// +build zospackage ptyimport ( "os" "syscall" "golang.org/x/sys/unix")func open() (pty, tty *os.File, err error) { ptmxfd, err := unix.Posix_openpt(os.O_RDWR | syscall.O_NOCTTY) if err != nil { return nil, nil, err } // Needed for z/OS so that the characters are not garbled if ptyp* is untagged cvtreq := unix.F_cnvrt{Cvtcmd: unix.SETCVTON, Pccsid: 0, Fccsid: 1047} if _, err = unix.FcntlFcnvrt(uintptr(ptmxfd), unix.F_CONTROL_CVT, &cvtreq); err != nil { return nil, nil, err } p := os.NewFile(uintptr(ptmxfd), "/dev/ptmx") if p == nil { return nil, nil, err } // In case of error after this point, make sure we close the ptmx fd. defer func() { if err != nil { _ = p.Close() // Best effort. } }() sname, err := unix.Ptsname(ptmxfd) if err != nil { return nil, nil, err } _, err = unix.Grantpt(ptmxfd) if err != nil { return nil, nil, err } if _, err = unix.Unlockpt(ptmxfd); err != nil { return nil, nil, err } ptsfd, err := syscall.Open(sname, os.O_RDWR|syscall.O_NOCTTY, 0) if err != nil { return nil, nil, err } if _, err = unix.FcntlFcnvrt(uintptr(ptsfd), unix.F_CONTROL_CVT, &cvtreq); err != nil { return nil, nil, err } t := os.NewFile(uintptr(ptsfd), sname) if err != nil { return nil, nil, err } return p, t, nil}
------------------------------
Frank Swarbrick
Original Message:
Sent: Sun June 02, 2024 03:16 PM
From: Frank Swarbrick
Subject: Possible Go z/OS runtime issue.
So I am adding z/OS support to package github.com/creack/pty. I've got it working pretty well (see attached pty_zos.go), but it is not passing one of the tests:
--- FAIL: TestReadClose (0.00s) io_test.go:80: Unexpected read error: read /dev/ptmx: EDC5112I Resource temporarily unavailable..panic: Fail in goroutine after TestReadClose has completedgoroutine 20 [running]:testing.(*common).Fail(0xc0004956c0) /u/dvfjs/.local/usr/go/src/testing/testing.go:952 +0x134testing.(*common).Errorf(0xc0004956c0, {0x10850424, 0x19}, {0xc0004e2fc8, 0x1, 0x1}) /u/dvfjs/.local/usr/go/src/testing/testing.go:1075 +0x9egithub.com/creack/pty/v2.TestReadClose.func1() /u/dvfjs/diffm/pty/io_test.go:72 +0xf0created by github.com/creack/pty/v2.TestReadClose in goroutine 17 /u/dvfjs/diffm/pty/io_test.go:69 +0x192FAIL github.com/creack/pty/v2 0.829sFAIL
The code in question is this:
func TestReadClose(t *testing.T) { ptmx, success := prepare(t) if err := syscall.SetNonblock(int(ptmx.Fd()), true); err != nil { t.Fatalf("Error: set non block: %s", err) } go func() { time.Sleep(timeout / 10) if err := ptmx.Close(); err != nil { t.Errorf("Failed to close ptmx: %s.", err) } }() buf := make([]byte, 1) n, err := ptmx.Read(buf) success() if err != nil && !errors.Is(err, os.ErrClosed) { t.Fatalf("Unexpected read error: %s.", err) } if n != 0 && buf[0] != errMarker { t.Errorf("Received unexpected data from pmtx (%d bytes): 0x%X; err=%v.", n, buf, err) }}
Line 80 is the t.Fatal with "Unexpected read error". The read should be returning os.ErrClosed, because the earlier go routine has closed the psuedo-terminal asynchronously. I've stepped through the code in Linux and z/OS and have found a difference in behavior.
In go/src/internal/poll/fd_mutex.go we have method rwlock:
func (mu *fdMutex) rwlock(read bool) bool { var mutexBit, mutexWait, mutexMask uint64 var mutexSema *uint32 if read { mutexBit = mutexRLock mutexWait = mutexRWait mutexMask = mutexRMask mutexSema = &mu.rsema } else { mutexBit = mutexWLock mutexWait = mutexWWait mutexMask = mutexWMask mutexSema = &mu.wsema } for { old := atomic.LoadUint64(&mu.state) if old&mutexClosed != 0 { return false } [...]
On Linux, when the method is entered, mu.state == 0x0, but by the time it gets to the first line of rwlock, the state has been updated to 0x1 (asynchronously, I assume). So when it checks old&mutextClosed, that returns 0x1 and causes the method to return false. The causes readLock (below) to return errClosing(fd.isFile).
func (fd *FD) readLock() error { if !fd.fdmu.rwlock(true) { return errClosing(fd.isFile) } return nil }
Then of course this error gets passed back through the read.
With z/OS the state is not changing from 0x0 to 0x1, so the errClosing return never executes.
Thoughts?
(Edit: My attachment didn't upload. I will include it in a reply to this post.)
------------------------------
Frank Swarbrick
------------------------------