| // Copyright 2019 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| package component | |
| import ( | |
| "fmt" | |
| "sync" | |
| "syscall/zx" | |
| "syscall/zx/fidl" | |
| "syscall/zx/zxwait" | |
| ) | |
| var bytesPool = sync.Pool{ | |
| New: func() interface{} { | |
| return make([]byte, zx.ChannelMaxMessageBytes) | |
| }, | |
| } | |
| var handleInfosPool = sync.Pool{ | |
| New: func() interface{} { | |
| return make([]zx.HandleInfo, zx.ChannelMaxMessageHandles) | |
| }, | |
| } | |
| var handleDispositionsPool = sync.Pool{ | |
| New: func() interface{} { | |
| return make([]zx.HandleDisposition, zx.ChannelMaxMessageHandles) | |
| }, | |
| } | |
| func serveOne(ctx fidl.Context, stub fidl.Stub, req zx.Channel, onError func(error)) error { | |
| b := bytesPool.Get().([]byte) | |
| defer bytesPool.Put(b) | |
| bOrig := b | |
| hi := handleInfosPool.Get().([]zx.HandleInfo) | |
| defer handleInfosPool.Put(hi) | |
| // Arrange for unconsumed handles to close on return. | |
| hi = hi[:0] | |
| defer func() { | |
| for _, hi := range hi { | |
| if err := hi.Handle.Close(); err != nil { | |
| onError(fmt.Errorf("failed to close unconsumed inbound handle: %w", err)) | |
| } | |
| } | |
| }() | |
| var nb, nhi uint32 | |
| if err := zxwait.WithRetry(func() error { | |
| var err error | |
| nb, nhi, err = req.ReadEtc(b, hi[:cap(hi)], 0) | |
| return err | |
| }, *req.Handle(), zx.SignalChannelReadable, zx.SignalChannelPeerClosed); err != nil { | |
| return err | |
| } | |
| b = b[:nb] | |
| hi = hi[:nhi] | |
| var reqHeader fidl.MessageHeader | |
| hnb, hnh, err := fidl.Unmarshal(b, hi, &reqHeader) | |
| if err != nil { | |
| return err | |
| } | |
| if !reqHeader.IsSupportedVersion() { | |
| return fidl.ErrUnknownMagic | |
| } | |
| b = b[hnb:] | |
| hi = hi[hnh:] | |
| marshalerCtx := reqHeader.NewCtx() | |
| p, shouldRespond, err := stub.Dispatch(fidl.DispatchArgs{ | |
| Ctx: fidl.WithMarshalerContext(ctx, marshalerCtx), | |
| Ordinal: reqHeader.Ordinal, | |
| Bytes: b, | |
| HandleInfos: hi, | |
| }) | |
| if err != nil { | |
| return err | |
| } | |
| // Consumed, prevent cleanup. | |
| hi = hi[:0] | |
| if shouldRespond { | |
| hd := handleDispositionsPool.Get().([]zx.HandleDisposition) | |
| defer handleDispositionsPool.Put(hd) | |
| // Arrange for unconsumed handles to close on return. | |
| hd = hd[:0] | |
| defer func() { | |
| for _, hd := range hd { | |
| if err := hd.Handle.Close(); err != nil { | |
| onError(fmt.Errorf("failed to close unconsumed outbound handle: %w", err)) | |
| } | |
| } | |
| }() | |
| b = bOrig | |
| respHeader := marshalerCtx.NewHeader() | |
| respHeader.Ordinal = reqHeader.Ordinal | |
| respHeader.Txid = reqHeader.Txid | |
| cnb, cnh, err := fidl.MarshalHeaderThenMessage(&respHeader, p, b, hd[:cap(hd)]) | |
| if err != nil { | |
| return err | |
| } | |
| b = b[:cnb] | |
| hd = hd[:cnh] | |
| if err := req.WriteEtc(b, hd, 0); err != nil { | |
| return err | |
| } | |
| // Consumed, prevent cleanup. | |
| hd = hd[:0] | |
| } | |
| return nil | |
| } | |
| func serve(ctx fidl.Context, stub fidl.Stub, req zx.Channel, onError func(error)) error { | |
| for { | |
| if err := ctx.Err(); err != nil { | |
| return err | |
| } | |
| // Wait for an incoming message before calling serveOne, which acquires | |
| // pooled memory to read into. This technique avoids O(requests) memory | |
| // usage, which yields substantial savings when the number of idle requests | |
| // is high. | |
| if _, err := zxwait.Wait(*req.Handle(), zx.SignalChannelReadable|zx.SignalChannelPeerClosed, zx.TimensecInfinite); err != nil { | |
| return err | |
| } | |
| if err := serveOne(ctx, stub, req, onError); err != nil { | |
| return err | |
| } | |
| } | |
| } | |
| // ServeExclusive assumes ownership of req and serially serves requests on it | |
| // via stub until ctx is called or req's peer is closed. ServeExclusive closes | |
| // req before returning. | |
| func ServeExclusive(ctx fidl.Context, stub fidl.Stub, req zx.Channel, onError func(error)) { | |
| defer func() { | |
| if err := req.Close(); err != nil { | |
| onError(fmt.Errorf("failed to close request channel: %w", err)) | |
| } | |
| }() | |
| if err := serve(ctx, stub, req, onError); err != nil { | |
| if err == ctx.Err() { | |
| return | |
| } | |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrPeerClosed { | |
| return | |
| } | |
| onError(fmt.Errorf("serving terminated: %w", err)) | |
| } | |
| } |