Skip to content

Commit cc10fd4

Browse files
authored
Merge pull request #392 from walles/johan/fix-ci-2026
Fix CI
2 parents de4f89a + 2ba7646 commit cc10fd4

File tree

5 files changed

+126
-8
lines changed

5 files changed

+126
-8
lines changed

.github/workflows/linux-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
# commandline below:
2323
# https://github.com/golangci/golangci-lint/releases/latest
2424
- name: Install golangci-lint
25-
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)"/bin v2.1.2
25+
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)"/bin v2.11.2
2626

2727
- run: go build ./...
2828
- run: ./test.sh

twin/interruptablereader.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ type interruptableReader struct {
1919
pauseOrRead semaphore.Weighted
2020
}
2121

22-
const interruptableReaderPollInterval = 100 * time.Millisecond
22+
// Basically how long we wait between interrupt checks
23+
const interruptableReaderMaxWait = 100 * time.Millisecond
2324

2425
func newInterruptableReader(base *os.File) interruptableReader {
2526
return interruptableReader{
@@ -55,7 +56,7 @@ func (r *interruptableReader) Read(p []byte) (n int, err error) {
5556
return 0, io.EOF
5657
}
5758

58-
ready, waitErr := r.waitForReadReady()
59+
ready, waitErr := r.waitForReadReady(interruptableReaderMaxWait)
5960
if waitErr != nil {
6061
return 0, waitErr
6162
}

twin/screen-setup-windows.go

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,68 @@ import (
88
"runtime/debug"
99
"syscall"
1010
"time"
11+
"unsafe"
1112

1213
"golang.org/x/sys/windows"
1314
"golang.org/x/term"
1415
)
1516

16-
func (r *interruptableReader) waitForReadReady() (ready bool, err error) {
17-
timeoutMillis := uint32(interruptableReaderPollInterval.Milliseconds())
17+
var peekNamedPipe = windows.NewLazySystemDLL("kernel32.dll").NewProc("PeekNamedPipe")
18+
19+
func waitForPipeReadReady(handle windows.Handle) (ready bool, err error) {
20+
var bytesAvailable uint32
21+
result, _, callErr := peekNamedPipe.Call(
22+
uintptr(handle),
23+
0,
24+
0,
25+
0,
26+
uintptr(unsafe.Pointer(&bytesAvailable)),
27+
0,
28+
)
29+
if result != 0 {
30+
return bytesAvailable > 0, nil
31+
}
32+
33+
if callErr == windows.ERROR_BROKEN_PIPE {
34+
// Writer closed: let a real Read() return EOF.
35+
return true, nil
36+
}
37+
38+
if callErr == windows.ERROR_NO_DATA {
39+
// Pipe has no data right now.
40+
return false, nil
41+
}
42+
43+
if callErr == windows.ERROR_HANDLE_EOF {
44+
return true, nil
45+
}
46+
47+
return false, fmt.Errorf("PeekNamedPipe failed: %w", callErr)
48+
}
49+
50+
func (r *interruptableReader) waitForReadReady(timeout time.Duration) (ready bool, err error) {
51+
fileType, err := windows.GetFileType(windows.Handle(r.base.Fd()))
52+
if err != nil {
53+
return false, err
54+
}
55+
56+
if fileType == windows.FILE_TYPE_PIPE {
57+
ready, err = waitForPipeReadReady(windows.Handle(r.base.Fd()))
58+
if ready || err != nil {
59+
return
60+
}
61+
62+
time.Sleep(timeout / 2)
63+
ready, err = waitForPipeReadReady(windows.Handle(r.base.Fd()))
64+
if ready || err != nil {
65+
return
66+
}
67+
68+
time.Sleep(timeout / 2)
69+
return
70+
}
71+
72+
timeoutMillis := uint32(timeout.Milliseconds())
1873
if timeoutMillis == 0 {
1974
timeoutMillis = 1
2075
}

twin/screen-setup.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import (
88
"os/signal"
99
"runtime/debug"
1010
"syscall"
11+
"time"
1112

1213
"golang.org/x/sys/unix"
1314
"golang.org/x/term"
1415
)
1516

16-
func (r *interruptableReader) waitForReadReady() (ready bool, err error) {
17+
func (r *interruptableReader) waitForReadReady(timeout time.Duration) (ready bool, err error) {
1718
// "This argument should be set to the highest-numbered file descriptor in
1819
// any of the three sets, plus 1. The indicated file descriptors in each set
1920
// are checked, up to this limit"
@@ -22,9 +23,9 @@ func (r *interruptableReader) waitForReadReady() (ready bool, err error) {
2223
nfds := r.base.Fd()
2324
readFds := unix.FdSet{}
2425
readFds.Set(int(r.base.Fd()))
25-
timeout := unix.NsecToTimeval(interruptableReaderPollInterval.Nanoseconds())
26+
selectTimeout := unix.NsecToTimeval(timeout.Nanoseconds())
2627

27-
_, err = unix.Select(int(nfds)+1, &readFds, nil, nil, &timeout)
28+
_, err = unix.Select(int(nfds)+1, &readFds, nil, nil, &selectTimeout)
2829
if err == syscall.EINTR {
2930
// Not really a problem, we can get this on window resizes for example
3031
return false, nil

twin/screen_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,64 @@ func TestInterruptableReader_justRead(t *testing.T) {
378378
assert.Equal(t, buffer[0], byte(42))
379379
assert.Equal(t, len(buffer), 7)
380380
}
381+
382+
func TestInterruptableReader_waitForReadReadyPipe(t *testing.T) {
383+
// Make a pipe to read from and write to
384+
pipeReader, pipeWriter, err := os.Pipe()
385+
assert.NilError(t, err)
386+
387+
t.Cleanup(func() {
388+
_ = pipeReader.Close()
389+
_ = pipeWriter.Close()
390+
})
391+
392+
// Make an interruptable reader
393+
testMe := newInterruptableReader(pipeReader)
394+
395+
// With no data available we should wait a bit, then report not ready.
396+
t0 := time.Now()
397+
ready, err := testMe.waitForReadReady(time.Millisecond * 100)
398+
duration := time.Since(t0)
399+
assert.NilError(t, err)
400+
assert.Equal(t, ready, false)
401+
assert.Assert(t, duration > time.Millisecond*100)
402+
403+
// After writing, the pipe should become ready.
404+
n, err := pipeWriter.Write([]byte{42})
405+
assert.NilError(t, err)
406+
assert.Equal(t, n, 1)
407+
408+
// With data available we should report ready immediately
409+
ready, err = testMe.waitForReadReady(time.Hour)
410+
assert.NilError(t, err)
411+
assert.Equal(t, ready, true)
412+
}
413+
414+
// On Unix, files are always ready (to return EOF if nothing else), but on
415+
// Windows they are non-ready if they have no data. So we just verify the
416+
// have-data case here, and let the no-data case be whatever.
417+
func TestInterruptableReader_waitForReadReadyFile(t *testing.T) {
418+
tempFile, err := os.CreateTemp("", "moor-wait-for-read-ready-*.txt")
419+
assert.NilError(t, err)
420+
421+
t.Cleanup(func() {
422+
_ = tempFile.Close()
423+
_ = os.Remove(tempFile.Name())
424+
})
425+
426+
// Put something in the file
427+
n, err := tempFile.Write([]byte("x"))
428+
assert.NilError(t, err)
429+
assert.Equal(t, n, 1)
430+
431+
// Rewind so we can see the "x"
432+
seek, err := tempFile.Seek(0, 0)
433+
assert.NilError(t, err)
434+
assert.Equal(t, seek, int64(0))
435+
436+
// Expect read-ready immediately
437+
testMe := newInterruptableReader(tempFile)
438+
ready, err := testMe.waitForReadReady(time.Hour)
439+
assert.NilError(t, err)
440+
assert.Equal(t, ready, true)
441+
}

0 commit comments

Comments
 (0)