-
Notifications
You must be signed in to change notification settings - Fork 259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wasi: nonblocking I/O for sockets and pipes on Windows #1579
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
0f82d12
wasi: nonblocking I/O for sockets and pipes on Windows
evacchi ffa0af6
formatting
evacchi e6bbb48
peek sockets instead of selecting
evacchi d6795b4
wip
evacchi 4b50041
wip
evacchi 301d324
wip
evacchi 0bbeb68
test
evacchi 7bbba14
more tests
evacchi 465d214
coverage
evacchi 7936d97
docs
evacchi 38218db
fix nil
evacchi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
//go:build !windows | ||
|
||
package platform | ||
|
||
// Set adds the given fd to the set. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
//go:build !darwin && !linux | ||
//go:build !darwin && !linux && !windows | ||
|
||
package platform | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
package platform | ||
|
||
import ( | ||
"syscall" | ||
"unsafe" | ||
) | ||
|
||
var procGetNamedPipeInfo = kernel32.NewProc("GetNamedPipeInfo") | ||
|
||
// Maximum number of fds in a WinSockFdSet. | ||
const _FD_SETSIZE = 64 | ||
|
||
// WinSockFdSet implements the FdSet representation that is used internally by WinSock. | ||
// | ||
// Note: this representation is quite different from the one used in most POSIX implementations | ||
// where a bitfield is usually implemented; instead on Windows we have a simpler array+count pair. | ||
// Notice that because it keeps a count of the inserted handles, the first argument of select | ||
// in WinSock is actually ignored. | ||
// | ||
// The implementation of the Set, Clear, IsSet, Zero, methods follows exactly | ||
// the real implementation found in WinSock2.h, e.g. see: | ||
// https://github.com/microsoft/win32metadata/blob/ef7725c75c6b39adfdc13ba26fb1d89ac954449a/generation/WinSDK/RecompiledIdlHeaders/um/WinSock2.h#L124-L175 | ||
type WinSockFdSet struct { | ||
// count is the number of used slots used in the handles slice. | ||
count uint64 | ||
// handles is the array of handles. This is called "array" in the WinSock implementation | ||
// and it has a fixed length of _FD_SETSIZE. | ||
handles [_FD_SETSIZE]syscall.Handle | ||
} | ||
|
||
// FdSet implements the same methods provided on other plaforms. | ||
// | ||
// Note: the implementation is very different from POSIX; Windows provides | ||
// POSIX select only for sockets. We emulate a select for other APIs in the sysfs | ||
// package, but we still want to use the "real" select in the case of sockets. | ||
// So, we keep separate FdSets for sockets, pipes and regular files, so that we can | ||
// handle them separately. For instance sockets can be used directly in winsock select. | ||
type FdSet struct { | ||
sockets WinSockFdSet | ||
pipes WinSockFdSet | ||
regular WinSockFdSet | ||
} | ||
|
||
// SetAll overwrites all the fields in f with the fields in g. | ||
func (f *FdSet) SetAll(g *FdSet) { | ||
if f == nil { | ||
return | ||
} | ||
f.sockets = g.sockets | ||
f.pipes = g.pipes | ||
f.regular = g.regular | ||
} | ||
|
||
// Sockets returns a WinSockFdSet containing the handles in this FdSet that are sockets. | ||
func (f *FdSet) Sockets() *WinSockFdSet { | ||
if f == nil { | ||
return nil | ||
} | ||
return &f.sockets | ||
} | ||
|
||
// Regular returns a WinSockFdSet containing the handles in this FdSet that are regular files. | ||
func (f *FdSet) Regular() *WinSockFdSet { | ||
if f == nil { | ||
return nil | ||
} | ||
return &f.regular | ||
} | ||
|
||
// Pipes returns a WinSockFdSet containing the handles in this FdSet that are pipes. | ||
func (f *FdSet) Pipes() *WinSockFdSet { | ||
if f == nil { | ||
return nil | ||
} | ||
return &f.pipes | ||
} | ||
|
||
// getFdSetFor returns a pointer to the right fd set for the given fd. | ||
// It checks the type for fd and returns the field for pipes, regular or sockets | ||
// to simplify code. | ||
// | ||
// For instance, if fd is a socket and it must be set if f.pipes, instead | ||
// of writing: | ||
// | ||
// if isSocket(fd) { | ||
// f.sockets.Set(fd) | ||
// } | ||
// | ||
// It is possible to write: | ||
// | ||
// f.getFdSetFor(fd).Set(fd) | ||
func (f *FdSet) getFdSetFor(fd int) *WinSockFdSet { | ||
h := syscall.Handle(fd) | ||
t, err := syscall.GetFileType(h) | ||
if err != nil { | ||
return nil | ||
} | ||
switch t { | ||
case syscall.FILE_TYPE_CHAR, syscall.FILE_TYPE_DISK: | ||
return &f.regular | ||
case syscall.FILE_TYPE_PIPE: | ||
if isSocket(h) { | ||
return &f.sockets | ||
} else { | ||
return &f.pipes | ||
} | ||
default: | ||
return nil | ||
} | ||
} | ||
|
||
// Set adds the given fd to the set. | ||
func (f *FdSet) Set(fd int) { | ||
if s := f.getFdSetFor(fd); s != nil { | ||
s.Set(fd) | ||
} | ||
} | ||
|
||
// Clear removes the given fd from the set. | ||
func (f *FdSet) Clear(fd int) { | ||
if s := f.getFdSetFor(fd); s != nil { | ||
s.Clear(fd) | ||
} | ||
} | ||
|
||
// IsSet returns true when fd is in the set. | ||
func (f *FdSet) IsSet(fd int) bool { | ||
if s := f.getFdSetFor(fd); s != nil { | ||
return s.IsSet(fd) | ||
} | ||
return false | ||
} | ||
|
||
// Copy returns a copy of this FdSet. It returns nil, if the FdSet is nil. | ||
func (f *FdSet) Copy() *FdSet { | ||
if f == nil { | ||
return nil | ||
} | ||
return &FdSet{ | ||
sockets: f.sockets, | ||
pipes: f.pipes, | ||
regular: f.regular, | ||
} | ||
} | ||
|
||
// Zero clears the set. It returns 0 if the FdSet is nil. | ||
func (f *FdSet) Count() int { | ||
if f == nil { | ||
return 0 | ||
} | ||
return f.sockets.Count() + f.regular.Count() + f.pipes.Count() | ||
} | ||
|
||
// Zero clears the set. | ||
func (f *FdSet) Zero() { | ||
if f == nil { | ||
return | ||
} | ||
f.sockets.Zero() | ||
f.regular.Zero() | ||
f.pipes.Zero() | ||
} | ||
|
||
// Set adds the given fd to the set. | ||
func (f *WinSockFdSet) Set(fd int) { | ||
if f.count < _FD_SETSIZE { | ||
f.handles[f.count] = syscall.Handle(fd) | ||
f.count++ | ||
} | ||
} | ||
|
||
// Clear removes the given fd from the set. | ||
func (f *WinSockFdSet) Clear(fd int) { | ||
h := syscall.Handle(fd) | ||
for i := uint64(0); i < f.count; i++ { | ||
if f.handles[i] == h { | ||
for ; i < f.count-1; i++ { | ||
f.handles[i] = f.handles[i+1] | ||
} | ||
f.count-- | ||
break | ||
} | ||
} | ||
} | ||
|
||
// IsSet returns true when fd is in the set. | ||
func (f *WinSockFdSet) IsSet(fd int) bool { | ||
h := syscall.Handle(fd) | ||
for i := uint64(0); i < f.count; i++ { | ||
if f.handles[i] == h { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// Zero clears the set. | ||
func (f *WinSockFdSet) Zero() { | ||
if f == nil { | ||
return | ||
} | ||
f.handles = [64]syscall.Handle{} | ||
f.count = 0 | ||
} | ||
|
||
// Count returns the number of values that are set in the fd set. | ||
func (f *WinSockFdSet) Count() int { | ||
if f == nil { | ||
return 0 | ||
} | ||
return int(f.count) | ||
} | ||
|
||
// Copy returns a copy of the fd set or nil if it is nil. | ||
func (f *WinSockFdSet) Copy() *WinSockFdSet { | ||
if f == nil { | ||
return nil | ||
} | ||
copy := *f | ||
return © | ||
} | ||
|
||
// Get returns the handle at the given index. | ||
func (f *WinSockFdSet) Get(index int) syscall.Handle { | ||
return f.handles[index] | ||
} | ||
|
||
// isSocket returns true if the given file handle | ||
// is a pipe. | ||
func isSocket(fd syscall.Handle) bool { | ||
r, _, errno := syscall.SyscallN( | ||
procGetNamedPipeInfo.Addr(), | ||
uintptr(fd), | ||
uintptr(unsafe.Pointer(nil)), | ||
uintptr(unsafe.Pointer(nil)), | ||
uintptr(unsafe.Pointer(nil)), | ||
uintptr(unsafe.Pointer(nil))) | ||
return r == 0 || errno != 0 | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unless we use things as nil (later re-assign the pointer) I would leave out the receiver == nil thing for internal types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the receiver can be nil in many places because select accepts fdreads, fdwrites, fdexcepts to be nil. By allowing the receiver to be nil, some error checks can be avoided e.g.
f.Count()
returning 0 when f == nil, or in this case, zeroing out a nil receiver, which is also useful inselect
. To be fair, maybeselect
can be rewritten in a different way so that the nil checks are centralized at the top of the function (maybe replacing nils with empty structs)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what made me look was not all functions check nil ;) Anyway it is cool to know at least some of it is helpful!