Files
KKRainbow 3d0c9c3ca5 chore(go): use lowercase module import path (#2560)
* chore(go): use lowercase module import path
* ci: scope checks for Go module changes
2026-09-10 15:13:55 +08:00

229 lines
5.0 KiB
Go

package engine
import (
"context"
"fmt"
"net"
"github.com/easytier/easytier/easytier-go/internal/coreabi"
)
type rpcCore interface {
SubmitRPC(
context.Context,
[]byte,
) (coreabi.RPCOperationID, error)
TakeRPCResponse(
context.Context,
coreabi.RPCOperationID,
) ([]byte, bool, error)
FreeRPCOperation(context.Context, coreabi.RPCOperationID) error
}
type rpcCommandKind uint8
const (
rpcSubmit rpcCommandKind = iota + 1
rpcFree
)
type rpcCommand struct {
kind rpcCommandKind
request []byte
operation coreabi.RPCOperationID
response chan rpcCommandResponse
}
type rpcCommandResponse struct {
ticket rpcTicket
outcome rpcOutcome
completed bool
err error
}
type rpcTicket struct {
id coreabi.RPCOperationID
result <-chan rpcOutcome
}
type rpcOutcome struct {
response []byte
err error
}
type pendingRPC struct {
result chan rpcOutcome
}
func (instance *Instance) RPC(
ctx context.Context,
encodedRequest []byte,
) ([]byte, error) {
if ctx == nil {
return nil, fmt.Errorf("submit EasyTier RPC with nil context")
}
response := make(chan rpcCommandResponse, 1)
request := rpcCommand{
kind: rpcSubmit,
request: encodedRequest,
response: response,
}
select {
case instance.rpcCommands <- request:
case <-instance.done:
return nil, net.ErrClosed
case <-instance.closeRequested:
return nil, net.ErrClosed
case <-ctx.Done():
return nil, ctx.Err()
}
var ticket rpcTicket
select {
case submitted := <-response:
if submitted.err != nil {
return nil, submitted.err
}
if submitted.completed {
return submitted.outcome.response, submitted.outcome.err
}
ticket = submitted.ticket
case <-instance.done:
return nil, net.ErrClosed
case <-ctx.Done():
// The driver has accepted the request and may already have submitted
// it. Wait for its operation ID so cancellation cannot leak it.
select {
case submitted := <-response:
if submitted.err != nil {
return nil, submitted.err
}
if submitted.completed {
return submitted.outcome.response, submitted.outcome.err
}
ticket = submitted.ticket
case <-instance.done:
return nil, net.ErrClosed
}
return instance.freeRPCAndWait(ctx, ticket)
}
select {
case outcome := <-ticket.result:
return outcome.response, outcome.err
case <-ctx.Done():
return instance.freeRPCAndWait(ctx, ticket)
case <-instance.done:
return nil, net.ErrClosed
case <-instance.closeRequested:
return nil, net.ErrClosed
}
}
func (instance *Instance) freeRPCAndWait(
cancelled context.Context,
ticket rpcTicket,
) ([]byte, error) {
response := make(chan rpcCommandResponse, 1)
request := rpcCommand{
kind: rpcFree,
operation: ticket.id,
response: response,
}
select {
case instance.rpcCommands <- request:
case outcome := <-ticket.result:
return outcome.response, outcome.err
case <-instance.done:
return nil, net.ErrClosed
case <-instance.closeRequested:
return nil, net.ErrClosed
}
select {
case result := <-response:
if result.err != nil {
return nil, result.err
}
case outcome := <-ticket.result:
return outcome.response, outcome.err
case <-instance.done:
return nil, net.ErrClosed
case <-instance.closeRequested:
return nil, net.ErrClosed
}
select {
case outcome := <-ticket.result:
return outcome.response, outcome.err
default:
return nil, cancelled.Err()
}
}
func (instance *Instance) handleRPCCommand(
request rpcCommand,
) rpcCommandResponse {
instance.host.guestMu.Lock()
defer instance.host.guestMu.Unlock()
switch request.kind {
case rpcSubmit:
operation, err := instance.rpc.SubmitRPC(
instance.ctx,
request.request,
)
if err != nil {
return rpcCommandResponse{err: err}
}
result := make(chan rpcOutcome, 1)
instance.pendingRPCs[operation] = &pendingRPC{result: result}
return rpcCommandResponse{ticket: rpcTicket{
id: operation,
result: result,
}}
case rpcFree:
if _, exists := instance.pendingRPCs[request.operation]; !exists {
return rpcCommandResponse{}
}
if err := instance.rpc.FreeRPCOperation(
instance.ctx,
request.operation,
); err != nil {
return rpcCommandResponse{err: err}
}
delete(instance.pendingRPCs, request.operation)
return rpcCommandResponse{}
default:
return rpcCommandResponse{
err: fmt.Errorf("unknown RPC command %d", request.kind),
}
}
}
// takeRPCResponses runs with host.guestMu held.
func (instance *Instance) takeRPCResponses() error {
if instance.rpc == nil || len(instance.pendingRPCs) == 0 {
return nil
}
for operation, pending := range instance.pendingRPCs {
response, ready, err := instance.rpc.TakeRPCResponse(
instance.ctx,
operation,
)
if err != nil {
return err
}
if !ready {
continue
}
delete(instance.pendingRPCs, operation)
pending.result <- rpcOutcome{response: response}
}
return nil
}
func (instance *Instance) failPendingRPCs(err error) {
for operation, pending := range instance.pendingRPCs {
pending.result <- rpcOutcome{err: err}
delete(instance.pendingRPCs, operation)
}
}