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  func TestWSASocketConflict(t *testing.T) {
    24  	t.Parallel()
    25  	s, err := windows.WSASocket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP, nil, 0, windows.WSA_FLAG_OVERLAPPED)
    26  	if err != nil {
    27  		t.Fatal(err)
    28  	}
    29  	fd := poll.FD{Sysfd: s, IsStream: true, ZeroReadIsEOF: true}
    30  	if err = fd.Init("tcp", true); err != nil {
    31  		syscall.CloseHandle(s)
    32  		t.Fatal(err)
    33  	}
    34  	defer fd.Close()
    35  
    36  	const SIO_TCP_INFO = syscall.IOC_INOUT | syscall.IOC_VENDOR | 39
    37  	inbuf := uint32(0)
    38  	var outbuf _TCP_INFO_v0
    39  	cbbr := uint32(0)
    40  
    41  	var ov syscall.Overlapped
    42  	// Create an event so that we can efficiently wait for completion
    43  	// of a requested overlapped I/O operation.
    44  	ov.HEvent, _ = windows.CreateEvent(nil, 0, 0, nil)
    45  	if ov.HEvent == 0 {
    46  		t.Fatalf("could not create the event!")
    47  	}
    48  	defer syscall.CloseHandle(ov.HEvent)
    49  
    50  	if err = fd.WSAIoctl(
    51  		SIO_TCP_INFO,
    52  		(*byte)(unsafe.Pointer(&inbuf)),
    53  		uint32(unsafe.Sizeof(inbuf)),
    54  		(*byte)(unsafe.Pointer(&outbuf)),
    55  		uint32(unsafe.Sizeof(outbuf)),
    56  		&cbbr,
    57  		&ov,
    58  		0,
    59  	); err != nil && !errors.Is(err, syscall.ERROR_IO_PENDING) {
    60  		t.Fatalf("could not perform the WSAIoctl: %v", err)
    61  	}
    62  
    63  	if err != nil && errors.Is(err, syscall.ERROR_IO_PENDING) {
    64  		// It is possible that the overlapped I/O operation completed
    65  		// immediately so there is no need to wait for it to complete.
    66  		if res, err := syscall.WaitForSingleObject(ov.HEvent, syscall.INFINITE); res != 0 {
    67  			t.Fatalf("waiting for the completion of the overlapped IO failed: %v", err)
    68  		}
    69  	}
    70  }
    71  
    72  type _TCP_INFO_v0 struct {
    73  	State             uint32
    74  	Mss               uint32
    75  	ConnectionTimeMs  uint64
    76  	TimestampsEnabled bool
    77  	RttUs             uint32
    78  	MinRttUs          uint32
    79  	BytesInFlight     uint32
    80  	Cwnd              uint32
    81  	SndWnd            uint32
    82  	RcvWnd            uint32
    83  	RcvBuf            uint32
    84  	BytesOut          uint64
    85  	BytesIn           uint64
    86  	BytesReordered    uint32
    87  	BytesRetrans      uint32
    88  	FastRetrans       uint32
    89  	DupAcksIn         uint32
    90  	TimeoutEpisodes   uint32
    91  	SynRetrans        uint8
    92  }
    93  
    94  func newFD(t testing.TB, h syscall.Handle, kind string, overlapped bool) *poll.FD {
    95  	fd := poll.FD{
    96  		Sysfd:         h,
    97  		IsStream:      true,
    98  		ZeroReadIsEOF: true,
    99  	}
   100  	err := fd.Init(kind, overlapped)
   101  	if overlapped && err != nil {
   102  		// Overlapped file handles should not error.
   103  		fd.Close()
   104  		t.Fatal(err)
   105  	}
   106  	t.Cleanup(func() {
   107  		fd.Close()
   108  	})
   109  	return &fd
   110  }
   111  
   112  func newFile(t testing.TB, name string, overlapped bool) *poll.FD {
   113  	namep, err := syscall.UTF16PtrFromString(name)
   114  	if err != nil {
   115  		t.Fatal(err)
   116  	}
   117  	flags := syscall.FILE_ATTRIBUTE_NORMAL
   118  	if overlapped {
   119  		flags |= syscall.FILE_FLAG_OVERLAPPED
   120  	}
   121  	h, err := syscall.CreateFile(namep,
   122  		syscall.GENERIC_READ|syscall.GENERIC_WRITE,
   123  		syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_READ,
   124  		nil, syscall.OPEN_ALWAYS, uint32(flags), 0)
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	typ, err := syscall.GetFileType(h)
   129  	if err != nil {
   130  		syscall.CloseHandle(h)
   131  		t.Fatal(err)
   132  	}
   133  	kind := "file"
   134  	if typ == syscall.FILE_TYPE_PIPE {
   135  		kind = "pipe"
   136  	}
   137  	return newFD(t, h, kind, overlapped)
   138  }
   139  
   140  func TestFileSkipsCompletionPortOnSuccess(t *testing.T) {
   141  	t.Parallel()
   142  	fd := newFile(t, filepath.Join(t.TempDir(), "foo"), true)
   143  	if !poll.SkipsCompletionPortOnSuccess(fd) {
   144  		t.Fatal("expected file handles to skip completion port on success")
   145  	}
   146  }
   147  
   148  func TestSocketSkipsCompletionPortOnSuccess(t *testing.T) {
   149  	// Assume that all Windows we test on only have IFS handles for TCP sockets.
   150  	t.Parallel()
   151  	s, err := windows.WSASocket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP, nil, 0, windows.WSA_FLAG_OVERLAPPED)
   152  	if err != nil {
   153  		t.Fatal(err)
   154  	}
   155  	fd := newFD(t, s, "tcp", true)
   156  	if !poll.SkipsCompletionPortOnSuccess(fd) {
   157  		t.Fatal("expected socket handles to skip completion port on success")
   158  	}
   159  }
   160  
   161  func BenchmarkReadOverlapped(b *testing.B) {
   162  	benchmarkRead(b, true)
   163  }
   164  
   165  func BenchmarkReadSync(b *testing.B) {
   166  	benchmarkRead(b, false)
   167  }
   168  
   169  func benchmarkRead(b *testing.B, overlapped bool) {
   170  	name := filepath.Join(b.TempDir(), "foo")
   171  	const content = "hello world"
   172  	err := os.WriteFile(name, []byte(content), 0644)
   173  	if err != nil {
   174  		b.Fatal(err)
   175  	}
   176  	file := newFile(b, name, overlapped)
   177  	var buf [len(content)]byte
   178  	for b.Loop() {
   179  		_, err := io.ReadFull(file, buf[:])
   180  		if err != nil {
   181  			b.Fatal(err)
   182  		}
   183  		if _, err := file.Seek(0, io.SeekStart); err != nil {
   184  			b.Fatal(err)
   185  		}
   186  	}
   187  }
   188  

View as plain text