Source file src/internal/poll/fd_windows_test.go

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package poll_test
     6  
     7  import (
     8  	"errors"
     9  	"internal/poll"
    10  	"internal/syscall/windows"
    11  	"io"
    12  	"os"
    13  	"path/filepath"
    14  	"syscall"
    15  	"testing"
    16  	"unsafe"
    17  )
    18  
    19  func init() {
    20  	poll.InitWSA()
    21  }
    22  
    23  // checkFileIsNotPartOfNetpoll verifies that f is not managed by netpoll.
    24  func checkFileIsNotPartOfNetpoll(t *testing.T, f *os.File) {
    25  	t.Helper()
    26  	sc, err := f.SyscallConn()
    27  	if err != nil {
    28  		t.Fatal(err)
    29  	}
    30  	if err := sc.Control(func(fd uintptr) {
    31  		// Only try to associate the file with an IOCP if the handle is opened for overlapped I/O,
    32  		// else the association will always fail.
    33  		overlapped, err := windows.IsNonblock(syscall.Handle(fd))
    34  		if err != nil {
    35  			t.Fatalf("%v fd=%v: %v", f.Name(), fd, err)
    36  		}
    37  		if overlapped {
    38  			// If the file is part of netpoll, then associating it with another IOCP should fail.
    39  			if _, err := windows.CreateIoCompletionPort(syscall.Handle(fd), 0, 0, 1); err != nil {
    40  				t.Fatalf("%v fd=%v: is part of netpoll, but should not be: %v", f.Name(), fd, err)
    41  			}
    42  		}
    43  	}); err != nil {
    44  		t.Fatalf("%v fd=%v: is not initialized", f.Name(), f.Fd())
    45  	}
    46  }
    47  
    48  func TestFileFdsAreInitialised(t *testing.T) {
    49  	t.Parallel()
    50  	exe, err := os.Executable()
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	f, err := os.Open(exe)
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	defer f.Close()
    59  
    60  	checkFileIsNotPartOfNetpoll(t, f)
    61  }
    62  
    63  func TestSerialFdsAreInitialised(t *testing.T) {
    64  	t.Parallel()
    65  	for _, name := range []string{"COM1", "COM2", "COM3", "COM4"} {
    66  		t.Run(name, func(t *testing.T) {
    67  			t.Parallel()
    68  			h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name),
    69  				syscall.GENERIC_READ|syscall.GENERIC_WRITE,
    70  				0,
    71  				nil,
    72  				syscall.OPEN_EXISTING,
    73  				syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED,
    74  				0)
    75  			if err != nil {
    76  				if errno, ok := err.(syscall.Errno); ok {
    77  					switch errno {
    78  					case syscall.ERROR_FILE_NOT_FOUND,
    79  						syscall.ERROR_ACCESS_DENIED:
    80  						t.Log("Skipping: ", err)
    81  						return
    82  					}
    83  				}
    84  				t.Fatal(err)
    85  			}
    86  			f := os.NewFile(uintptr(h), name)
    87  			defer f.Close()
    88  
    89  			checkFileIsNotPartOfNetpoll(t, f)
    90  		})
    91  	}
    92  }
    93  
    94  func TestWSASocketConflict(t *testing.T) {
    95  	t.Parallel()
    96  	s, err := windows.WSASocket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP, nil, 0, windows.WSA_FLAG_OVERLAPPED)
    97  	if err != nil {
    98  		t.Fatal(err)
    99  	}
   100  	fd := poll.FD{Sysfd: s, IsStream: true, ZeroReadIsEOF: true}
   101  	if err = fd.Init("tcp", true); err != nil {
   102  		syscall.CloseHandle(s)
   103  		t.Fatal(err)
   104  	}
   105  	defer fd.Close()
   106  
   107  	const SIO_TCP_INFO = syscall.IOC_INOUT | syscall.IOC_VENDOR | 39
   108  	inbuf := uint32(0)
   109  	var outbuf _TCP_INFO_v0
   110  	cbbr := uint32(0)
   111  
   112  	var ov syscall.Overlapped
   113  	// Create an event so that we can efficiently wait for completion
   114  	// of a requested overlapped I/O operation.
   115  	ov.HEvent, _ = windows.CreateEvent(nil, 0, 0, nil)
   116  	if ov.HEvent == 0 {
   117  		t.Fatalf("could not create the event!")
   118  	}
   119  	defer syscall.CloseHandle(ov.HEvent)
   120  
   121  	if err = fd.WSAIoctl(
   122  		SIO_TCP_INFO,
   123  		(*byte)(unsafe.Pointer(&inbuf)),
   124  		uint32(unsafe.Sizeof(inbuf)),
   125  		(*byte)(unsafe.Pointer(&outbuf)),
   126  		uint32(unsafe.Sizeof(outbuf)),
   127  		&cbbr,
   128  		&ov,
   129  		0,
   130  	); err != nil && !errors.Is(err, syscall.ERROR_IO_PENDING) {
   131  		t.Fatalf("could not perform the WSAIoctl: %v", err)
   132  	}
   133  
   134  	if err != nil && errors.Is(err, syscall.ERROR_IO_PENDING) {
   135  		// It is possible that the overlapped I/O operation completed
   136  		// immediately so there is no need to wait for it to complete.
   137  		if res, err := syscall.WaitForSingleObject(ov.HEvent, syscall.INFINITE); res != 0 {
   138  			t.Fatalf("waiting for the completion of the overlapped IO failed: %v", err)
   139  		}
   140  	}
   141  }
   142  
   143  type _TCP_INFO_v0 struct {
   144  	State             uint32
   145  	Mss               uint32
   146  	ConnectionTimeMs  uint64
   147  	TimestampsEnabled bool
   148  	RttUs             uint32
   149  	MinRttUs          uint32
   150  	BytesInFlight     uint32
   151  	Cwnd              uint32
   152  	SndWnd            uint32
   153  	RcvWnd            uint32
   154  	RcvBuf            uint32
   155  	BytesOut          uint64
   156  	BytesIn           uint64
   157  	BytesReordered    uint32
   158  	BytesRetrans      uint32
   159  	FastRetrans       uint32
   160  	DupAcksIn         uint32
   161  	TimeoutEpisodes   uint32
   162  	SynRetrans        uint8
   163  }
   164  
   165  func newFD(t testing.TB, h syscall.Handle, kind string, overlapped bool) *poll.FD {
   166  	fd := poll.FD{
   167  		Sysfd:         h,
   168  		IsStream:      true,
   169  		ZeroReadIsEOF: true,
   170  	}
   171  	err := fd.Init(kind, overlapped)
   172  	if overlapped && err != nil {
   173  		// Overlapped file handles should not error.
   174  		fd.Close()
   175  		t.Fatal(err)
   176  	}
   177  	t.Cleanup(func() {
   178  		fd.Close()
   179  	})
   180  	return &fd
   181  }
   182  
   183  func newFile(t testing.TB, name string, overlapped bool) *poll.FD {
   184  	namep, err := syscall.UTF16PtrFromString(name)
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  	flags := syscall.FILE_ATTRIBUTE_NORMAL
   189  	if overlapped {
   190  		flags |= syscall.FILE_FLAG_OVERLAPPED
   191  	}
   192  	h, err := syscall.CreateFile(namep,
   193  		syscall.GENERIC_READ|syscall.GENERIC_WRITE,
   194  		syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_READ,
   195  		nil, syscall.OPEN_ALWAYS, uint32(flags), 0)
   196  	if err != nil {
   197  		t.Fatal(err)
   198  	}
   199  	typ, err := syscall.GetFileType(h)
   200  	if err != nil {
   201  		syscall.CloseHandle(h)
   202  		t.Fatal(err)
   203  	}
   204  	kind := "file"
   205  	if typ == syscall.FILE_TYPE_PIPE {
   206  		kind = "pipe"
   207  	}
   208  	return newFD(t, h, kind, overlapped)
   209  }
   210  
   211  func BenchmarkReadOverlapped(b *testing.B) {
   212  	benchmarkRead(b, true)
   213  }
   214  
   215  func BenchmarkReadSync(b *testing.B) {
   216  	benchmarkRead(b, false)
   217  }
   218  
   219  func benchmarkRead(b *testing.B, overlapped bool) {
   220  	name := filepath.Join(b.TempDir(), "foo")
   221  	const content = "hello world"
   222  	err := os.WriteFile(name, []byte(content), 0644)
   223  	if err != nil {
   224  		b.Fatal(err)
   225  	}
   226  	file := newFile(b, name, overlapped)
   227  	var buf [len(content)]byte
   228  	for b.Loop() {
   229  		_, err := io.ReadFull(file, buf[:])
   230  		if err != nil {
   231  			b.Fatal(err)
   232  		}
   233  		if _, err := file.Seek(0, io.SeekStart); err != nil {
   234  			b.Fatal(err)
   235  		}
   236  	}
   237  }
   238  

View as plain text