From a24e05f16aa4226a04d01392bf9efdcd1fd653a0 Mon Sep 17 00:00:00 2001 From: Edoardo Vacchi Date: Tue, 1 Aug 2023 22:30:36 +0200 Subject: [PATCH] Add new test cases, fix sock impl Signed-off-by: Edoardo Vacchi --- imports/wasi_snapshot_preview1/poll.go | 61 ++-- imports/wasi_snapshot_preview1/poll_test.go | 296 ++++++++++++++++++ .../testdata/zig-cc/wasi.c | 22 +- .../testdata/zig-cc/wasi.wasm | Bin 62102 -> 63172 bytes .../wasi_stdlib_test.go | 52 +++ internal/sysfs/sock_unix.go | 20 +- internal/sysfs/sock_windows.go | 7 +- 7 files changed, 422 insertions(+), 36 deletions(-) diff --git a/imports/wasi_snapshot_preview1/poll.go b/imports/wasi_snapshot_preview1/poll.go index fa8a58f737..ddd4818945 100644 --- a/imports/wasi_snapshot_preview1/poll.go +++ b/imports/wasi_snapshot_preview1/poll.go @@ -95,10 +95,8 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno var blockingSubs []*filePollEvent // The timeout is initialized at max Duration, the loop will find the minimum. var timeout time.Duration = 1<<63 - 1 - // Count of all the clock subscribers that have been already written back to outBuf. - clockEvents := uint32(0) - // Count of all the non-clock subscribers that have been already written back to outBuf. - readySubs := uint32(0) + // Count of all the subscriptions that have been already written back to outBuf. + nevents := uint32(0) // Layout is subscription_u: Union // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#subscription_u @@ -112,15 +110,14 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno userData := inBuf[inOffset : inOffset+8] evt := &pollEvent{ + outOffset: outOffset, eventType: eventType, userData: userData, errno: wasip1.ErrnoSuccess, - outOffset: outOffset, } switch eventType { case wasip1.EventTypeClock: // handle later - clockEvents++ newTimeout, err := processClockEvent(argBuf) if err != 0 { return err @@ -131,6 +128,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno } // Ack the clock event to the outBuf. writeEvent(outBuf, evt) + nevents++ case wasip1.EventTypeFdRead: fd := int32(le.Uint32(argBuf)) if fd < 0 { @@ -139,7 +137,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno if file, ok := fsc.LookupFile(fd); !ok { evt.errno = wasip1.ErrnoBadf writeEvent(outBuf, evt) - readySubs++ + nevents++ } else if !file.File.IsNonblock() { // If the fd is blocking, do not ack yet, // append to a slice for delayed evaluation. @@ -147,7 +145,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno blockingSubs = append(blockingSubs, fe) } else { writeEvent(outBuf, evt) - readySubs++ + nevents++ } case wasip1.EventTypeFdWrite: fd := int32(le.Uint32(argBuf)) @@ -159,7 +157,7 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno } else { evt.errno = wasip1.ErrnoBadf } - readySubs++ + nevents++ writeEvent(outBuf, evt) default: return sys.EINVAL @@ -167,33 +165,42 @@ func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) sys.Errno } sysCtx := mod.(*wasm.ModuleInstance).Sys + if nevents == nsubscriptions { + // We already wrote back all the results. We already wrote this number + // earlier to offset `resultNevents`. + // We only need to observe the timeout (nonzero if there are clock subscriptions) + // and return. + if timeout > 0 { + sysCtx.Nanosleep(int64(timeout)) + } + return 0 + } - // There are no blocking subscribers, we just wait for the given timeout. - if len(blockingSubs) == 0 { - sysCtx.Nanosleep(int64(timeout)) - } else { - // If there are blocking subscribers, check the fds using poll. - n, errno := pollFileEventsOnce(blockingSubs, outBuf) + // If nevents != nsubscriptions, then there are blocking subscribers. + // We check these fds once using poll. + n, errno := pollFileEventsOnce(blockingSubs, outBuf) + if errno != 0 { + return errno + } + nevents += n + + // If the previous poll returned n == 0 (no data) but the timeout is nonzero + // (i.e. there are clock subscriptions), we poll until either the timeout expires + // or any File.Poll() returns true ("ready"); otherwise we are done. + if n == 0 && timeout > 0 { + n, errno = pollFileEventsUntil(sysCtx, timeout, blockingSubs, outBuf) if errno != 0 { return errno } - readySubs += n - - // If there are any subscribers ready, including those we checked earlier, - // we don't need to poll further; otherwise, poll until the given timeout. - if readySubs == 0 { - readySubs, errno = pollFileEventsUntil(sysCtx, timeout, blockingSubs, outBuf) - if errno != 0 { - return errno - } - } + nevents += n } - if readySubs != nsubscriptions { - if !mod.Memory().WriteUint32Le(resultNevents, readySubs+clockEvents) { + if nevents != nsubscriptions { + if !mod.Memory().WriteUint32Le(resultNevents, nevents) { return sys.EFAULT } } + return 0 } diff --git a/imports/wasi_snapshot_preview1/poll_test.go b/imports/wasi_snapshot_preview1/poll_test.go index 6db7d6926d..233229e1ed 100644 --- a/imports/wasi_snapshot_preview1/poll_test.go +++ b/imports/wasi_snapshot_preview1/poll_test.go @@ -2,12 +2,15 @@ package wasi_snapshot_preview1_test import ( "io/fs" + "net" + "os" "strings" "testing" "time" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental/sock" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/testing/require" @@ -401,6 +404,295 @@ func setStdin(t *testing.T, mod api.Module, stdin experimentalsys.File) { f.File = stdin } +func Test_pollOneoff_Mixed(t *testing.T) { + // Test stdin (pipes) mixed with sockets. + + const listenFd = 3 + const acceptFd = 4 + + type addr interface { + Addr() *net.TCPAddr + } + + tests := []struct { + name string + skip bool + in, out, nsubscriptions, resultNevents uint32 + connected, nonblocking bool + mem []byte // at offset in + files []experimentalsys.File + expectedErrno wasip1.Errno + expectedMem []byte // at offset out + expectedLog string + expectedNevents uint32 + }{ + { + name: "Read from sock (not connected)", + nsubscriptions: 1, + expectedNevents: 0, + mem: fdReadSubFd(listenFd), // assume sock at fd 3 + expectedErrno: wasip1.ErrnoSuccess, + out: 128, // past in + resultNevents: 512, // past out + expectedMem: []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + '?', // stopped after encoding + }, + expectedLog: ` +==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=1) +<== (nevents=0,errno=ESUCCESS) +`, + }, + { + name: "Read from sock (connected)", + connected: true, + nsubscriptions: 2, + expectedNevents: 1, + mem: append(fdReadSubFd(listenFd), fdReadSubFd(acceptFd)...), // assume sock at fd 3 + expectedErrno: wasip1.ErrnoSuccess, + out: 128, // past in + resultNevents: 512, // past out + expectedMem: []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + '?', // stopped after encoding + }, + expectedLog: ` +==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=2) +<== (nevents=1,errno=ESUCCESS) +`, + }, + + { + name: "Read from sock (connected+nonblocking)", + connected: true, + nonblocking: true, + nsubscriptions: 2, + expectedNevents: 2, + mem: append(fdReadSubFd(listenFd), fdReadSubFd(acceptFd)...), // assume sock at fd 3 + expectedErrno: wasip1.ErrnoSuccess, + out: 128, // past in + resultNevents: 512, // past out + expectedMem: []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + '?', // stopped after encoding + }, + expectedLog: ` +==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=2) +<== (nevents=2,errno=ESUCCESS) +`, + }, + + { + name: "Read from sock (not connected) and stdin", + nsubscriptions: 2, + expectedNevents: 1, + mem: append(fdReadSubFd(listenFd), fdReadSub...), // assume sock at fd 3 + expectedErrno: wasip1.ErrnoSuccess, + out: 128, // past in + resultNevents: 512, // past out + expectedMem: []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + '?', // stopped after encoding + }, + expectedLog: ` +==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=2) +<== (nevents=1,errno=ESUCCESS) +`, + }, + + { + name: "Read from sock (connected) and stdin (ready)", + connected: true, + nsubscriptions: 3, + expectedNevents: 2, + mem: append(append(fdReadSubFd(listenFd), fdReadSubFd(acceptFd)...), fdReadSub...), // assume sock at fd 3 + expectedErrno: wasip1.ErrnoSuccess, + out: 128, // past in + resultNevents: 512, // past out + expectedMem: []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + '?', // stopped after encoding + }, + expectedLog: ` +==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=3) +<== (nevents=2,errno=ESUCCESS) +`, + }, + { + name: "Read from sock (connected+nonblocking) and stdin (ready)", + connected: true, + nonblocking: true, + nsubscriptions: 3, + expectedNevents: 3, + mem: append(append(fdReadSubFd(listenFd), fdReadSubFd(acceptFd)...), fdReadSub...), // assume sock at fd 3 + expectedErrno: wasip1.ErrnoSuccess, + out: 128, // past in + resultNevents: 512, // past out + expectedMem: []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata + byte(wasip1.ErrnoSuccess), 0x0, // errno is 16 bit + wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, // 4 bytes for type enum + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + '?', // stopped after encoding + }, + expectedLog: ` +==> wasi_snapshot_preview1.poll_oneoff(in=0,out=128,nsubscriptions=3) +<== (nevents=3,errno=ESUCCESS) +`, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + if tc.skip { + t.Skip() + } + ctx := sock.WithConfig(testCtx, + sock.NewConfig().WithTCPListener("127.0.0.1", 0)) + + stdinReader, stdinWriter, err := os.Pipe() + require.NoError(t, err) + defer stdinReader.Close() + defer stdinWriter.Close() + + mod, r, log := requireProxyModuleWithContext(ctx, t, wazero.NewModuleConfig().WithStdin(stdinReader)) + _, _ = stdinWriter.Write([]byte("wazero")) + + defer r.Close(ctx) + defer log.Reset() + + maskMemory(t, mod, 1024) + if tc.mem != nil { + mod.Memory().Write(tc.in, tc.mem) + } + + if tc.connected { + fsc := mod.(*wasm.ModuleInstance).Sys.FS() + ch := make(chan struct{}, 1) + file, _ := fsc.LookupFile(listenFd) + if tc.nonblocking { + _ = file.File.SetNonblock(true) + } + + go func() { + for { + _, errno := fsc.SockAccept(listenFd, false) + if errno == experimentalsys.EAGAIN { + continue + } + require.EqualErrno(t, 0, errno) + close(ch) + return + } + }() + + // Wait for the socket to accept. + sleepALittle() + + addr := file.File.(addr) + c, err := net.DialTCP("tcp", nil, addr.Addr()) + + <-ch + + require.NoError(t, err) + _, _ = c.Write([]byte("wazero")) + } + + requireErrnoResult(t, tc.expectedErrno, mod, wasip1.PollOneoffName, uint64(tc.in), uint64(tc.out), + uint64(tc.nsubscriptions), uint64(tc.resultNevents)) + require.Equal(t, tc.expectedLog, "\n"+log.String()) + + out, ok := mod.Memory().Read(tc.out, uint32(len(tc.expectedMem))) + require.True(t, ok) + require.Equal(t, tc.expectedMem, out) + + // Events should be written on success regardless of nested failure. + if tc.expectedErrno == wasip1.ErrnoSuccess { + nevents, ok := mod.Memory().ReadUint32Le(tc.resultNevents) + require.True(t, ok) + require.Equal(t, tc.expectedNevents, nevents) + _ = nevents + } + }) + } +} + func Test_pollOneoff_Zero(t *testing.T) { poller := &pollStdinFile{StdinFile: sys.StdinFile{Reader: strings.NewReader("test")}, ready: true} @@ -522,6 +814,10 @@ func fdReadSubFd(fd byte) []byte { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // userdata wasip1.EventTypeFdRead, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, fd, 0x0, 0x0, 0x0, // valid readable FD + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, } } diff --git a/imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c b/imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c index 818b8c330a..f8afbc743c 100644 --- a/imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c +++ b/imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.c @@ -54,7 +54,7 @@ void main_poll(int timeout, int millis) { tv.tv_usec = millis*1000; ret = select(1, &rfds, NULL, NULL, &tv); if ((ret > 0) && FD_ISSET(0, &rfds)) { - printf("STDIN\n"); + printf("STDIN\n", ret); } else { printf("NOINPUT\n"); } @@ -121,7 +121,7 @@ void main_open_wronly() { unlink(path); } -void main_sock() { +void main_sock_mixed(bool checkStdin) { // Get a listener from the pre-opened file descriptor. // The listener is the first pre-open, with a file-descriptor of 3. int listener_fd = 3; @@ -148,7 +148,13 @@ void main_sock() { struct timeval tv = {1, 0}; fd_set set; FD_ZERO(&set); - FD_SET(nfd, &set); + if (checkStdin) { + FD_SET(0, &set); + FD_SET(nfd, &set); + FD_SET(listener_fd, &set); + } else { + FD_SET(nfd, &set); + } int ret = select(nfd+1, &set, NULL, NULL, &tv); // If some data is available, read it. @@ -162,6 +168,14 @@ void main_sock() { } } +void main_sock() { + main_sock_mixed(false); +} + +void main_mixed() { + main_sock_mixed(true); +} + void main_nonblock(char* fpath) { struct timespec tim, tim2; tim.tv_sec = 0; @@ -212,6 +226,8 @@ int main(int argc, char** argv) { main_open_wronly(); } else if (strcmp(argv[1],"sock")==0) { main_sock(); + } else if (strcmp(argv[1],"mixed")==0) { + main_mixed(); } else if (strcmp(argv[1],"nonblock")==0) { main_nonblock(argv[2]); } else { diff --git a/imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.wasm b/imports/wasi_snapshot_preview1/testdata/zig-cc/wasi.wasm index 255a198f097f84776b119254c961b1b5fe43aadf..e75963d132102f353f28aca0c4619a4469499a5e 100755 GIT binary patch delta 8508 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 Bp=1C6 diff --git a/imports/wasi_snapshot_preview1/wasi_stdlib_test.go b/imports/wasi_snapshot_preview1/wasi_stdlib_test.go index c4f7979523..6b17706504 100644 --- a/imports/wasi_snapshot_preview1/wasi_stdlib_test.go +++ b/imports/wasi_snapshot_preview1/wasi_stdlib_test.go @@ -635,3 +635,55 @@ func testLargeStdout(t *testing.T, tname string, bin []byte) { require.NoError(t, err, string(output)) } } + +func Test_Mixed(t *testing.T) { + toolchains := map[string][]byte{ + // TODO: "cargo-wasi": wasmCargoWasi, + "zig-cc": wasmZigCc, + } + + for toolchain, bin := range toolchains { + toolchain := toolchain + bin := bin + t.Run(toolchain, func(t *testing.T) { + testMixed(t, bin) + }) + } +} + +func testMixed(t *testing.T, bin []byte) { + // This is identical to testSock, except we also hook a pipe to stdin + // We expect poll_oneoff to be invoked successfully. + sockCfg := experimentalsock.NewConfig().WithTCPListener("127.0.0.1", 0) + ctx := experimentalsock.WithConfig(testCtx, sockCfg) + r, w, err := os.Pipe() + require.NoError(t, err) + defer r.Close() + defer w.Close() + _, err = w.Write([]byte("wazero")) + require.NoError(t, err) + moduleConfig := wazero.NewModuleConfig().WithArgs("wasi", "mixed").WithSysNanosleep().WithStdin(r) + tcpAddrCh := make(chan *net.TCPAddr, 1) + ch := make(chan string, 1) + go func() { + ch <- compileAndRunWithPreStart(t, ctx, moduleConfig, bin, func(t *testing.T, mod api.Module) { + tcpAddrCh <- requireTCPListenerAddr(t, mod) + }) + }() + tcpAddr := <-tcpAddrCh + + // Give a little time for _start to complete + sleepALittle() + + // Now dial to the initial address, which should be now held by wazero. + conn, err := net.Dial("tcp", tcpAddr.String()) + require.NoError(t, err) + defer conn.Close() + + n, err := conn.Write([]byte("wazero")) + console := <-ch + require.NotEqual(t, 0, n) + require.NoError(t, err) + // Nonblocking connections may contain error logging, we ignore those. + require.Equal(t, "wazero\n", console[len(console)-7:]) +} diff --git a/internal/sysfs/sock_unix.go b/internal/sysfs/sock_unix.go index 29b9416352..1bc2803f1c 100644 --- a/internal/sysfs/sock_unix.go +++ b/internal/sysfs/sock_unix.go @@ -41,8 +41,9 @@ var _ socketapi.TCPSock = (*tcpListenerFile)(nil) type tcpListenerFile struct { baseSockFile - fd uintptr - addr *net.TCPAddr + fd uintptr + addr *net.TCPAddr + nonblock bool } // Accept implements the same method as documented on socketapi.TCPSock @@ -62,9 +63,15 @@ func (f *tcpListenerFile) Poll(flag sys.Pflag, timeoutMillis int32) (ready bool, // SetNonblock implements the same method as documented on sys.File func (f *tcpListenerFile) SetNonblock(enabled bool) sys.Errno { + f.nonblock = enabled return sys.UnwrapOSError(setNonblock(f.fd, enabled)) } +// IsNonblock implements the same method as documented on sys.File +func (f *tcpListenerFile) IsNonblock() bool { + return f.nonblock +} + // Close implements the same method as documented on sys.File func (f *tcpListenerFile) Close() sys.Errno { return sys.UnwrapOSError(syscall.Close(int(f.fd))) @@ -80,7 +87,8 @@ var _ socketapi.TCPConn = (*tcpConnFile)(nil) type tcpConnFile struct { baseSockFile - fd uintptr + fd uintptr + nonblock bool // closed is true when closed was called. This ensures proper sys.EBADF closed bool @@ -96,9 +104,15 @@ func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { // SetNonblock implements the same method as documented on sys.File func (f *tcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) { + f.nonblock = enabled return sys.UnwrapOSError(setNonblock(f.fd, enabled)) } +// IsNonblock implements the same method as documented on sys.File +func (f *tcpConnFile) IsNonblock() bool { + return f.nonblock +} + // Poll implements the same method as documented on sys.File func (f *tcpConnFile) Poll(flag sys.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) { return poll(f.fd, flag, timeoutMillis) diff --git a/internal/sysfs/sock_windows.go b/internal/sysfs/sock_windows.go index 716fe4b737..3c5a1b9339 100644 --- a/internal/sysfs/sock_windows.go +++ b/internal/sysfs/sock_windows.go @@ -100,7 +100,6 @@ func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, sys.Errno)) // because they are sensibly different from Unix's. func newTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { w := &winTcpListenerFile{tl: tl} - _ = w.SetNonblock(true) return w } @@ -117,8 +116,10 @@ type winTcpListenerFile struct { // Accept implements the same method as documented on socketapi.TCPSock func (f *winTcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) { // Ensure we have an incoming connection using winsock_select, otherwise return immediately. - if ready, errno := f.Poll(sys.POLLIN, 0); !ready || errno != 0 { - return nil, sys.EAGAIN + if f.nonblock { + if ready, errno := f.Poll(sys.POLLIN, 0); !ready || errno != 0 { + return nil, sys.EAGAIN + } } // Accept normally blocks goroutines, but we