This repository has been archived by the owner on May 12, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
agent: fix the issue of missing close process terminal
Kata assumes once a process exits, it's IO will also close automatically, thus the client will wait on the process's IO closed and then do some cleanup for this process. But the issue is that if the process forked some background children processes running as daemon process, thus those children process will intherit its parent terminal, and even its parent exited, its terminal will not close as the children running. So this commit will try to fix this issue by introducing a pipe, and let the IO read epolling on one end of this pipe and the process's pty master. Once the process exits, it will close the other end of the pipe to notify the IO read that the process has exited and and there's no need to wait on its IO. Fixes: #370 Signed-off-by: fupan <[email protected]>
- Loading branch information
Showing
6 changed files
with
275 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Copyright (c) 2018 HyperHQ Inc. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
package main | ||
|
||
import ( | ||
"os" | ||
|
||
"golang.org/x/sys/unix" | ||
"google.golang.org/grpc/codes" | ||
grpcStatus "google.golang.org/grpc/status" | ||
) | ||
|
||
const maxEvents = 2 | ||
|
||
type epoller struct { | ||
fd int | ||
// sockR and sockW are a pipe's files two ends, this pipe is | ||
// used to sync between the readStdio and the process exits. | ||
// once the process exits, it will close one end to notify | ||
// the readStdio that the process has exited and it should not | ||
// wait on the process's terminal which has been inherited | ||
// by it's children and hasn't exited. | ||
sockR *os.File | ||
sockW *os.File | ||
sockMap map[int32]*os.File | ||
} | ||
|
||
func newEpoller() (*epoller, error) { | ||
epollerFd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
rSock, wSock, err := os.Pipe() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
ep := &epoller{ | ||
fd: epollerFd, | ||
sockW: wSock, | ||
sockR: rSock, | ||
sockMap: make(map[int32]*os.File), | ||
} | ||
|
||
if err = ep.add(rSock); err != nil { | ||
return nil, err | ||
} | ||
|
||
return ep, nil | ||
} | ||
|
||
func (ep *epoller) add(f *os.File) error { | ||
// add creates an epoll which is used to monitor the process's pty's master and | ||
// one end of its exit notify pipe. Those files will be registered with level-triggered | ||
// notification. | ||
|
||
event := unix.EpollEvent{ | ||
Fd: int32(f.Fd()), | ||
Events: unix.EPOLLHUP | unix.EPOLLIN | unix.EPOLLERR | unix.EPOLLRDHUP, | ||
} | ||
ep.sockMap[int32(f.Fd())] = f | ||
return unix.EpollCtl(ep.fd, unix.EPOLL_CTL_ADD, int(f.Fd()), &event) | ||
} | ||
|
||
// There will be three cases on the epoller once it run: | ||
// a: only pty's master get an event; | ||
// b: only the pipe get an event; | ||
// c: both of pty and pipe have event occur; | ||
// for case a, it means there is output in process's terminal and what needed to do is | ||
// just read the terminal and send them out; for case b, it means the process has exited | ||
// and there is no data in the terminal, thus just return the "EOF" to end the io; | ||
// for case c, it means the process has exited but there is some data in the terminal which | ||
// hasn't been send out, thus it should send those data out first and then send "EOF" last to | ||
// end the io. | ||
func (ep *epoller) run() (*os.File, error) { | ||
fd := int32(ep.sockR.Fd()) | ||
events := make([]unix.EpollEvent, maxEvents) | ||
for { | ||
n, err := unix.EpollWait(ep.fd, events, -1) | ||
if err != nil { | ||
// EINTR: The call was interrupted by a signal handler before either | ||
// any of the requested events occurred or the timeout expired | ||
if err == unix.EINTR { | ||
continue | ||
} | ||
return nil, err | ||
} | ||
|
||
for i := 0; i < n; i++ { | ||
ev := &events[i] | ||
// fd has been assigned with one end of process's exited pipe by default, and | ||
// here to check is there any event occur on process's terminal, if "yes", it | ||
// should be dealt first, otherwise, it means the process has exited and there | ||
// is nothing left in the process's terminal needed to be read. | ||
if ev.Fd != fd { | ||
fd = ev.Fd | ||
break | ||
} | ||
} | ||
break | ||
} | ||
|
||
mf, exist := ep.sockMap[fd] | ||
if !exist { | ||
return nil, grpcStatus.Errorf(codes.NotFound, "File %d not found", fd) | ||
} | ||
|
||
return mf, nil | ||
} |
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,79 @@ | ||
// Copyright (c) 2018 HyperHQ Inc. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
package main | ||
|
||
import ( | ||
"os" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"golang.org/x/sys/unix" | ||
) | ||
|
||
func TestNewEpoller(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
epoller, err := newEpoller() | ||
assert.NoError(err) | ||
|
||
closeEpoller(epoller) | ||
|
||
} | ||
|
||
func closeEpoller(ep *epoller) { | ||
ep.sockW.Close() | ||
ep.sockR.Close() | ||
unix.Close(ep.fd) | ||
} | ||
|
||
func TestAddEpoller(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
epoller, _ := newEpoller() | ||
assert.NotNil(epoller) | ||
defer closeEpoller(epoller) | ||
|
||
rSock, wSock, err := os.Pipe() | ||
assert.NoError(err) | ||
defer rSock.Close() | ||
defer wSock.Close() | ||
|
||
err = epoller.add(rSock) | ||
|
||
assert.NoError(err) | ||
} | ||
|
||
func TestRunEpoller(t *testing.T) { | ||
assert := assert.New(t) | ||
wg := sync.WaitGroup{} | ||
|
||
epoller, _ := newEpoller() | ||
assert.NotNil(epoller) | ||
defer closeEpoller(epoller) | ||
|
||
content := []byte("temporary file's content") | ||
rSock, wSock, err := os.Pipe() | ||
assert.NoError(err) | ||
defer rSock.Close() | ||
defer wSock.Close() | ||
|
||
err = epoller.add(rSock) | ||
assert.NoError(err) | ||
|
||
wg.Add(1) | ||
go func() { | ||
wg.Done() | ||
wSock.Write(content) | ||
}() | ||
|
||
wg.Wait() | ||
f, err := epoller.run() | ||
assert.NoError(err) | ||
|
||
assert.Equal(f.Fd(), rSock.Fd()) | ||
closeEpoller(epoller) | ||
} |
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