Ensure win32PipeListener.Close() correctly aborts Listen() calls

Signed-off-by: John Starks <jostarks@microsoft.com>
This commit is contained in:
John Starks
2016-01-31 13:46:55 -08:00
parent f001f808a9
commit b0d265e6c2
3 changed files with 87 additions and 37 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
} }
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) { func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
return makeWin32File(h) return makeWin32File(h)
} }
// closeHandle closes the resources associated with a Win32 handle // closeHandle closes the resources associated with a Win32 handle
+44 -33
View File
@@ -114,8 +114,9 @@ type win32PipeListener struct {
firstHandle syscall.Handle firstHandle syscall.Handle
path string path string
securityDescriptor string securityDescriptor string
closeCh chan (chan int)
acceptCh chan (chan acceptResponse) acceptCh chan (chan acceptResponse)
closeCh chan int
doneCh chan int
} }
func makeServerPipeHandle(path, securityDescriptor string, first bool) (syscall.Handle, error) { func makeServerPipeHandle(path, securityDescriptor string, first bool) (syscall.Handle, error) {
@@ -157,40 +158,43 @@ func (l *win32PipeListener) makeServerPipe() (*win32Pipe, error) {
} }
func (l *win32PipeListener) listenerRoutine() { func (l *win32PipeListener) listenerRoutine() {
var closeResponseCh (chan int) closed := false
Loop: for !closed {
for {
var responseCh (chan acceptResponse)
select { select {
case closeResponseCh = <-l.closeCh: case <-l.closeCh:
break Loop closed = true
case responseCh = <-l.acceptCh: case responseCh := <-l.acceptCh:
} p, err := l.makeServerPipe()
p, err := l.makeServerPipe() if err == nil {
if err != nil { // Wait for the client to connect.
responseCh <- acceptResponse{nil, err} ch := make(chan error)
} else { go func() {
ch := make(chan error) ch <- connectPipe(p)
go func() { }()
ch <- connectPipe(p) select {
}() case err = <-ch:
select { if err != nil {
case closeResponseCh = <-l.closeCh: p.Close()
p.Close() p = nil
<-ch }
break Loop case <-l.closeCh:
case err = <-ch: // Abort the connect request by closing the handle.
if err != nil {
p.Close() p.Close()
p = nil p = nil
err = <-ch
if err == nil {
err = FileClosed
}
closed = true
} }
responseCh <- acceptResponse{p, err}
} }
responseCh <- acceptResponse{p, err}
} }
} }
syscall.CloseHandle(l.firstHandle) syscall.CloseHandle(l.firstHandle)
l.firstHandle = syscall.Handle(0) l.firstHandle = syscall.Handle(0)
closeResponseCh <- 1 // Notify Close() and Accept() callers that the handle has been closed.
close(l.doneCh)
} }
func ListenPipe(path, securityDescriptor string) (net.Listener, error) { func ListenPipe(path, securityDescriptor string) (net.Listener, error) {
@@ -210,8 +214,9 @@ func ListenPipe(path, securityDescriptor string) (net.Listener, error) {
firstHandle: h, firstHandle: h,
path: path, path: path,
securityDescriptor: securityDescriptor, securityDescriptor: securityDescriptor,
closeCh: make(chan (chan int)),
acceptCh: make(chan (chan acceptResponse)), acceptCh: make(chan (chan acceptResponse)),
closeCh: make(chan int),
doneCh: make(chan int),
} }
go l.listenerRoutine() go l.listenerRoutine()
return l, nil return l, nil
@@ -232,15 +237,21 @@ func connectPipe(p *win32Pipe) error {
func (l *win32PipeListener) Accept() (net.Conn, error) { func (l *win32PipeListener) Accept() (net.Conn, error) {
ch := make(chan acceptResponse) ch := make(chan acceptResponse)
l.acceptCh <- ch select {
response := <-ch case l.acceptCh <- ch:
return response.p, response.err response := <-ch
return response.p, response.err
case <-l.doneCh:
return nil, FileClosed
}
} }
func (l *win32PipeListener) Close() error { func (l *win32PipeListener) Close() error {
ch := make(chan int) select {
l.closeCh <- ch case l.closeCh <- 1:
<-ch <-l.doneCh
case <-l.doneCh:
}
return nil return nil
} }
+42 -3
View File
@@ -93,12 +93,11 @@ func TestReadTimeout(t *testing.T) {
} }
} }
func server(l net.Listener) { func server(l net.Listener, ch chan int) {
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer c.Close()
rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c))
s, err := rw.ReadString('\n') s, err := rw.ReadString('\n')
if err != nil { if err != nil {
@@ -112,6 +111,8 @@ func server(l net.Listener) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
c.Close()
ch <- 1
} }
func TestFullListenDialReadWrite(t *testing.T) { func TestFullListenDialReadWrite(t *testing.T) {
@@ -119,13 +120,16 @@ func TestFullListenDialReadWrite(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer l.Close()
go server(l) ch := make(chan int)
go server(l, ch)
c, err := DialPipe(testPipeName, nil) c, err := DialPipe(testPipeName, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer c.Close()
rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c))
_, err = rw.WriteString("hello world\n") _, err = rw.WriteString("hello world\n")
@@ -145,4 +149,39 @@ func TestFullListenDialReadWrite(t *testing.T) {
if s != ms { if s != ms {
t.Errorf("expected '%s', got '%s'", ms, s) t.Errorf("expected '%s', got '%s'", ms, s)
} }
<-ch
}
func TestCloseAbortsListen(t *testing.T) {
l, err := ListenPipe(testPipeName, "")
if err != nil {
t.Fatal(err)
}
ch := make(chan int)
go func() {
_, err := l.Accept()
if err != syscall.ERROR_OPERATION_ABORTED {
t.Fatalf("expected ERROR_OPERATION_ABORTED, got %v", err)
}
ch <- 1
}()
time.Sleep(30 * time.Millisecond)
l.Close()
<-ch
}
func TestAcceptAfterCloseFails(t *testing.T) {
l, err := ListenPipe(testPipeName, "")
if err != nil {
t.Fatal(err)
}
l.Close()
_, err = l.Accept()
if err != FileClosed {
t.Fatalf("expected FileClosed, got %v", err)
}
} }