@@ -17,6 +17,7 @@ import (
17
17
"context"
18
18
"io"
19
19
"strings"
20
+ "sync"
20
21
"time"
21
22
22
23
"github.com/firecracker-microvm/firecracker-containerd/internal"
@@ -36,6 +37,12 @@ type IOProxy interface {
36
37
// process. It returns two channels, one to indicate io initialization
37
38
// is completed and one to indicate io copying is completed.
38
39
start (proc * vmProc ) (ioInitDone <- chan error , ioCopyDone <- chan error )
40
+
41
+ // Close the proxy.
42
+ Close ()
43
+
44
+ // IsOpen returns true if the proxy hasn't been closed.
45
+ IsOpen () bool
39
46
}
40
47
41
48
// IOConnector is function that begins initializing an IO connection (i.e.
@@ -71,6 +78,7 @@ func (connectorPair *IOConnectorPair) proxy(
71
78
logger * logrus.Entry ,
72
79
timeoutAfterExit time.Duration ,
73
80
) (ioInitDone <- chan error , ioCopyDone <- chan error ) {
81
+ // initDone might not have to be buffered. We only send ioInitErr once.
74
82
initDone := make (chan error , 2 )
75
83
copyDone := make (chan error )
76
84
@@ -152,6 +160,10 @@ type ioConnectorSet struct {
152
160
stdin * IOConnectorPair
153
161
stdout * IOConnectorPair
154
162
stderr * IOConnectorPair
163
+
164
+ // closeMu is needed since Close() will be called from different goroutines.
165
+ closeMu sync.Mutex
166
+ closed bool
155
167
}
156
168
157
169
// NewIOConnectorProxy implements the IOProxy interface for a set of
@@ -163,12 +175,33 @@ func NewIOConnectorProxy(stdin, stdout, stderr *IOConnectorPair) IOProxy {
163
175
stdin : stdin ,
164
176
stdout : stdout ,
165
177
stderr : stderr ,
178
+ closed : false ,
166
179
}
167
180
}
168
181
182
+ func (ioConnectorSet * ioConnectorSet ) Close () {
183
+ ioConnectorSet .closeMu .Lock ()
184
+ defer ioConnectorSet .closeMu .Unlock ()
185
+
186
+ ioConnectorSet .closed = true
187
+ }
188
+
189
+ func (ioConnectorSet * ioConnectorSet ) IsOpen () bool {
190
+ ioConnectorSet .closeMu .Lock ()
191
+ defer ioConnectorSet .closeMu .Unlock ()
192
+
193
+ return ! ioConnectorSet .closed
194
+ }
195
+
196
+ // start starts goroutines to copy stdio and returns two channels.
197
+ // The first channel returns its initialization error. The second channel returns errors from copying.
169
198
func (ioConnectorSet * ioConnectorSet ) start (proc * vmProc ) (ioInitDone <- chan error , ioCopyDone <- chan error ) {
170
199
var initErrG errgroup.Group
171
- var copyErrG errgroup.Group
200
+
201
+ // When one of the goroutines returns an error, we will cancel
202
+ // the rest of goroutines through the ctx below.
203
+ copyErrG , ctx := errgroup .WithContext (proc .ctx )
204
+
172
205
waitErrs := func (initErrCh , copyErrCh <- chan error ) {
173
206
initErrG .Go (func () error { return <- initErrCh })
174
207
copyErrG .Go (func () error { return <- copyErrCh })
@@ -177,24 +210,25 @@ func (ioConnectorSet *ioConnectorSet) start(proc *vmProc) (ioInitDone <-chan err
177
210
if ioConnectorSet .stdin != nil {
178
211
// For Stdin only, provide 0 as the timeout to wait after the proc exits before closing IO streams.
179
212
// There's no reason to send stdin data to a proc that's already dead.
180
- waitErrs (ioConnectorSet .stdin .proxy (proc .ctx , proc .logger .WithField ("stream" , "stdin" ), 0 ))
181
-
213
+ waitErrs (ioConnectorSet .stdin .proxy (ctx , proc .logger .WithField ("stream" , "stdin" ), 0 ))
182
214
} else {
183
215
proc .logger .Debug ("skipping proxy io for unset stdin" )
184
216
}
185
217
186
218
if ioConnectorSet .stdout != nil {
187
- waitErrs (ioConnectorSet .stdout .proxy (proc . ctx , proc .logger .WithField ("stream" , "stdout" ), defaultIOFlushTimeout ))
219
+ waitErrs (ioConnectorSet .stdout .proxy (ctx , proc .logger .WithField ("stream" , "stdout" ), defaultIOFlushTimeout ))
188
220
} else {
189
221
proc .logger .Debug ("skipping proxy io for unset stdout" )
190
222
}
191
223
192
224
if ioConnectorSet .stderr != nil {
193
- waitErrs (ioConnectorSet .stderr .proxy (proc . ctx , proc .logger .WithField ("stream" , "stderr" ), defaultIOFlushTimeout ))
225
+ waitErrs (ioConnectorSet .stderr .proxy (ctx , proc .logger .WithField ("stream" , "stderr" ), defaultIOFlushTimeout ))
194
226
} else {
195
227
proc .logger .Debug ("skipping proxy io for unset stderr" )
196
228
}
197
229
230
+ // These channels are not buffered, since we will close them right after having one error.
231
+ // Callers must read the channels.
198
232
initDone := make (chan error )
199
233
go func () {
200
234
defer close (initDone )
@@ -227,3 +261,29 @@ func logClose(logger *logrus.Entry, streams ...io.Closer) {
227
261
logger .WithError (closeErr ).Error ("error closing io stream" )
228
262
}
229
263
}
264
+
265
+ // InputPair returns an IOConnectorPair from the given vsock port to
266
+ // the FIFO file.
267
+ func InputPair (src uint32 , dest string ) * IOConnectorPair {
268
+ if dest == "" {
269
+ return nil
270
+ }
271
+
272
+ return & IOConnectorPair {
273
+ ReadConnector : VSockAcceptConnector (src ),
274
+ WriteConnector : WriteFIFOConnector (dest ),
275
+ }
276
+ }
277
+
278
+ // OutputPair returns an IOConnectorPair from the given FIFO to
279
+ // the vsock port.
280
+ func OutputPair (src string , dest uint32 ) * IOConnectorPair {
281
+ if src == "" {
282
+ return nil
283
+ }
284
+
285
+ return & IOConnectorPair {
286
+ ReadConnector : ReadFIFOConnector (src ),
287
+ WriteConnector : VSockAcceptConnector (dest ),
288
+ }
289
+ }
0 commit comments