Improve FUSE async/noreply call logic

This change adds bookkeeping variables for the
FUSE request. With them, old insecure confusing
code we used to process async requests is replaced
by new clear compiling ones. Future code can take
advantage of them to have better control of each
requests.
This commit is contained in:
Jinmou Li
2020-09-16 12:19:30 -07:00
committed by Andrei Vagin
parent f1219ec5f1
commit 826a685a95
3 changed files with 55 additions and 64 deletions
+29 -37
View File
@@ -15,7 +15,6 @@
package fuse
import (
"errors"
"sync"
"gvisor.dev/gvisor/pkg/abi/linux"
@@ -201,42 +200,40 @@ func newFUSEConnection(_ context.Context, fd *vfs.FileDescription, opts *filesys
}, nil
}
// Call makes a request to the server and blocks the invoking task until a
// server responds with a response. Task should never be nil.
// Requests will not be sent before the connection is initialized.
// For async tasks, use CallAsync().
// CallAsync makes an async (aka background) request.
// It's a simple wrapper around Call().
func (conn *connection) CallAsync(t *kernel.Task, r *Request) error {
r.async = true
_, err := conn.Call(t, r)
return err
}
// Call makes a request to the server.
// Block before the connection is initialized.
// When the Request is FUSE_INIT, it will not be blocked before initialization.
// Task should never be nil.
//
// For a sync request, it blocks the invoking task until
// a server responds with a response.
//
// For an async request (that do not expect a response immediately),
// it returns directly unless being blocked either before initialization
// or when there are too many async requests ongoing.
//
// Example for async request:
// init, readahead, write, async read/write, fuse_notify_reply,
// non-sync release, interrupt, forget.
//
// The forget request does not have a reply,
// as documented in include/uapi/linux/fuse.h:FUSE_FORGET.
func (conn *connection) Call(t *kernel.Task, r *Request) (*Response, error) {
// Block requests sent before connection is initalized.
if !conn.Initialized() {
if !conn.Initialized() && r.hdr.Opcode != linux.FUSE_INIT {
if err := t.Block(conn.initializedChan); err != nil {
return nil, err
}
}
return conn.call(t, r)
}
// CallAsync makes an async (aka background) request.
// Those requests either do not expect a response (e.g. release) or
// the response should be handled by others (e.g. init).
// Return immediately unless the connection is blocked (before initialization).
// Async call example: init, release, forget, aio, interrupt.
// When the Request is FUSE_INIT, it will not be blocked before initialization.
func (conn *connection) CallAsync(t *kernel.Task, r *Request) error {
// Block requests sent before connection is initalized.
if !conn.Initialized() && r.hdr.Opcode != linux.FUSE_INIT {
if err := t.Block(conn.initializedChan); err != nil {
return err
}
}
// This should be the only place that invokes call() with a nil task.
_, err := conn.call(nil, r)
return err
}
// call makes a call without blocking checks.
func (conn *connection) call(t *kernel.Task, r *Request) (*Response, error) {
if !conn.connected {
return nil, syserror.ENOTCONN
}
@@ -271,11 +268,6 @@ func (conn *connection) callFuture(t *kernel.Task, r *Request) (*futureResponse,
// if there are always too many ongoing requests all the time. The
// supported maxActiveRequests setting should be really high to avoid this.
for conn.fd.numActiveRequests == conn.fd.fs.opts.maxActiveRequests {
if t == nil {
// Since there is no task that is waiting. We must error out.
return nil, errors.New("FUSE request queue full")
}
log.Infof("Blocking request %v from being queued. Too many active requests: %v",
r.id, conn.fd.numActiveRequests)
conn.fd.mu.Unlock()
@@ -292,8 +284,8 @@ func (conn *connection) callFuture(t *kernel.Task, r *Request) (*futureResponse,
// callFutureLocked makes a request to the server and returns a future response.
func (conn *connection) callFutureLocked(t *kernel.Task, r *Request) (*futureResponse, error) {
conn.fd.queue.PushBack(r)
conn.fd.numActiveRequests += 1
fut := newFutureResponse(r.hdr.Opcode)
conn.fd.numActiveRequests++
fut := newFutureResponse(r)
conn.fd.completions[r.id] = fut
// Signal the readers that there is something to read.
+12 -19
View File
@@ -367,22 +367,20 @@ func (fd *DeviceFD) Seek(ctx context.Context, offset int64, whence int32) (int64
// sendResponse sends a response to the waiting task (if any).
func (fd *DeviceFD) sendResponse(ctx context.Context, fut *futureResponse) error {
// See if the running task need to perform some action before returning.
// Since we just finished writing the future, we can be sure that
// getResponse generates a populated response.
if err := fd.noReceiverAction(ctx, fut.getResponse()); err != nil {
return err
}
// Signal the task waiting on a response if any.
defer close(fut.ch)
// Signal that the queue is no longer full.
select {
case fd.fullQueueCh <- struct{}{}:
default:
}
fd.numActiveRequests -= 1
fd.numActiveRequests--
if fut.async {
return fd.asyncCallBack(ctx, fut.getResponse())
}
// Signal the task waiting on a response.
close(fut.ch)
return nil
}
@@ -404,23 +402,18 @@ func (fd *DeviceFD) sendError(ctx context.Context, errno int32, req *Request) er
delete(fd.completions, respHdr.Unique)
fut.hdr = &respHdr
if err := fd.sendResponse(ctx, fut); err != nil {
return err
}
return nil
return fd.sendResponse(ctx, fut)
}
// noReceiverAction has the calling kernel.Task do some action if its known that no
// receiver is going to be waiting on the future channel. This is to be used by:
// FUSE_INIT.
func (fd *DeviceFD) noReceiverAction(ctx context.Context, r *Response) error {
// asyncCallBack executes pre-defined callback function for async requests.
// Currently used by: FUSE_INIT.
func (fd *DeviceFD) asyncCallBack(ctx context.Context, r *Response) error {
switch r.opcode {
case linux.FUSE_INIT:
creds := auth.CredentialsFromContext(ctx)
rootUserNs := kernel.KernelFromContext(ctx).RootUserNamespace()
return fd.fs.conn.InitRecv(r, creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, rootUserNs))
// TODO(gvisor.dev/issue/3247): support async read: correctly process the response using information from r.options.
// TODO(gvisor.dev/issue/3247): support async read: correctly process the response.
}
return nil
+14 -8
View File
@@ -19,7 +19,6 @@ import (
"syscall"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/usermem"
@@ -96,6 +95,12 @@ type Request struct {
// payload for this request: extra bytes to write after
// the data slice. Used by FUSE_WRITE.
payload []byte
// If this request is async.
async bool
// If we don't care its response.
// Manually set by the caller.
noReply bool
}
// NewRequest creates a new request that can be sent to the FUSE server.
@@ -138,24 +143,25 @@ type futureResponse struct {
ch chan struct{}
hdr *linux.FUSEHeaderOut
data []byte
// If this request is async.
async bool
}
// newFutureResponse creates a future response to a FUSE request.
func newFutureResponse(opcode linux.FUSEOpcode) *futureResponse {
func newFutureResponse(req *Request) *futureResponse {
return &futureResponse{
opcode: opcode,
opcode: req.hdr.Opcode,
ch: make(chan struct{}),
async: req.async,
}
}
// resolve blocks the task until the server responds to its corresponding request,
// then returns a resolved response.
func (f *futureResponse) resolve(t *kernel.Task) (*Response, error) {
// If there is no Task associated with this request - then we don't try to resolve
// the response. Instead, the task writing the response (proxy to the server) will
// process the response on our behalf.
if t == nil {
log.Infof("fuse.Response.resolve: Not waiting on a response from server.")
// Return directly for async requests.
if f.async {
return nil, nil
}