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 completed
goroutine 20 [running]:
testing.(*common).Fail(0xc0004956c0)
/u/dvfjs/.local/usr/go/src/testing/testing.go:952 +0x134
testing.(*common).Errorf(0xc0004956c0, {0x10850424, 0x19}, {0xc0004e2fc8, 0x1, 0x1})
/u/dvfjs/.local/usr/go/src/testing/testing.go:1075 +0x9e
github.com/creack/pty/v2.TestReadClose.func1()
/u/dvfjs/diffm/pty/io_test.go:72 +0xf0
created by github.com/creack/pty/v2.TestReadClose in goroutine 17
/u/dvfjs/diffm/pty/io_test.go:69 +0x192
FAIL github.com/creack/pty/v2 0.829s
FAIL
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
------------------------------