mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-07-28 07:19:56 +00:00
TUN-528: Move cloudflared into a separate repo
This commit is contained in:
442
vendor/zombiezen.com/go/capnproto2/rpc/question.go
generated
vendored
Normal file
442
vendor/zombiezen.com/go/capnproto2/rpc/question.go
generated
vendored
Normal file
@@ -0,0 +1,442 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/internal/fulfiller"
|
||||
"zombiezen.com/go/capnproto2/internal/queue"
|
||||
rpccapnp "zombiezen.com/go/capnproto2/std/capnp/rpc"
|
||||
)
|
||||
|
||||
// newQuestion creates a new question with an unassigned ID.
|
||||
func (c *Conn) newQuestion(ctx context.Context, method *capnp.Method) *question {
|
||||
id := questionID(c.questionID.next())
|
||||
q := &question{
|
||||
ctx: ctx,
|
||||
conn: c,
|
||||
method: method,
|
||||
resolved: make(chan struct{}),
|
||||
id: id,
|
||||
}
|
||||
// TODO(light): populate paramCaps
|
||||
if int(id) == len(c.questions) {
|
||||
c.questions = append(c.questions, q)
|
||||
} else {
|
||||
c.questions[id] = q
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (c *Conn) findQuestion(id questionID) *question {
|
||||
if int(id) >= len(c.questions) {
|
||||
return nil
|
||||
}
|
||||
return c.questions[id]
|
||||
}
|
||||
|
||||
func (c *Conn) popQuestion(id questionID) *question {
|
||||
q := c.findQuestion(id)
|
||||
if q == nil {
|
||||
return nil
|
||||
}
|
||||
c.questions[id] = nil
|
||||
c.questionID.remove(uint32(id))
|
||||
return q
|
||||
}
|
||||
|
||||
type question struct {
|
||||
id questionID
|
||||
ctx context.Context
|
||||
conn *Conn
|
||||
method *capnp.Method // nil if this is bootstrap
|
||||
paramCaps []exportID
|
||||
resolved chan struct{}
|
||||
|
||||
// Protected by conn.mu
|
||||
derived [][]capnp.PipelineOp
|
||||
|
||||
// Fields below are protected by mu.
|
||||
mu sync.RWMutex
|
||||
obj capnp.Ptr
|
||||
err error
|
||||
state questionState
|
||||
}
|
||||
|
||||
type questionState uint8
|
||||
|
||||
// Question states
|
||||
const (
|
||||
questionInProgress questionState = iota
|
||||
questionResolved
|
||||
questionCanceled
|
||||
)
|
||||
|
||||
// start signals that the question has been sent.
|
||||
func (q *question) start() {
|
||||
go func() {
|
||||
select {
|
||||
case <-q.resolved:
|
||||
// Resolved naturally, nothing to do.
|
||||
case <-q.conn.bg.Done():
|
||||
case <-q.ctx.Done():
|
||||
select {
|
||||
case <-q.resolved:
|
||||
case <-q.conn.bg.Done():
|
||||
case <-q.conn.mu:
|
||||
if err := q.conn.startWork(); err != nil {
|
||||
// teardown calls cancel.
|
||||
q.conn.mu.Unlock()
|
||||
return
|
||||
}
|
||||
if q.cancel(q.ctx.Err()) {
|
||||
q.conn.sendMessage(newFinishMessage(nil, q.id, true /* release */))
|
||||
}
|
||||
q.conn.workers.Done()
|
||||
q.conn.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// fulfill is called to resolve a question successfully.
|
||||
// The caller must be holding onto q.conn.mu.
|
||||
func (q *question) fulfill(obj capnp.Ptr) {
|
||||
var ctab []capnp.Client
|
||||
if obj.IsValid() {
|
||||
ctab = obj.Segment().Message().CapTable
|
||||
}
|
||||
visited := make([]bool, len(ctab))
|
||||
for _, d := range q.derived {
|
||||
tgt, err := capnp.TransformPtr(obj, d)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
in := tgt.Interface()
|
||||
if !in.IsValid() {
|
||||
continue
|
||||
}
|
||||
if ic := isImport(in.Client()); ic != nil && ic.conn == q.conn {
|
||||
// Imported from remote vat. Don't need to disembargo.
|
||||
continue
|
||||
}
|
||||
cn := in.Capability()
|
||||
if visited[cn] {
|
||||
continue
|
||||
}
|
||||
visited[cn] = true
|
||||
id, e := q.conn.newEmbargo()
|
||||
ctab[cn] = newEmbargoClient(ctab[cn], e, q.conn.bg.Done())
|
||||
m := newDisembargoMessage(nil, rpccapnp.Disembargo_context_Which_senderLoopback, id)
|
||||
dis, _ := m.Disembargo()
|
||||
mt, _ := dis.NewTarget()
|
||||
pa, _ := mt.NewPromisedAnswer()
|
||||
pa.SetQuestionId(uint32(q.id))
|
||||
transformToPromisedAnswer(m.Segment(), pa, d)
|
||||
mt.SetPromisedAnswer(pa)
|
||||
|
||||
select {
|
||||
case q.conn.out <- m:
|
||||
case <-q.conn.bg.Done():
|
||||
// TODO(soon): perhaps just drop all embargoes in this case?
|
||||
}
|
||||
}
|
||||
|
||||
q.mu.Lock()
|
||||
if q.state != questionInProgress {
|
||||
panic("question.fulfill called more than once")
|
||||
}
|
||||
q.obj, q.state = obj, questionResolved
|
||||
close(q.resolved)
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
// reject is called to resolve a question with failure.
|
||||
// The caller must be holding onto q.conn.mu.
|
||||
func (q *question) reject(err error) {
|
||||
if err == nil {
|
||||
panic("question.reject called with nil")
|
||||
}
|
||||
q.mu.Lock()
|
||||
if q.state != questionInProgress {
|
||||
panic("question.reject called more than once")
|
||||
}
|
||||
q.err = err
|
||||
q.state = questionResolved
|
||||
close(q.resolved)
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
// cancel is called to resolve a question with cancellation.
|
||||
// The caller must be holding onto q.conn.mu.
|
||||
func (q *question) cancel(err error) bool {
|
||||
if err == nil {
|
||||
panic("question.cancel called with nil")
|
||||
}
|
||||
q.mu.Lock()
|
||||
canceled := q.state == questionInProgress
|
||||
if canceled {
|
||||
q.err = err
|
||||
q.state = questionCanceled
|
||||
close(q.resolved)
|
||||
}
|
||||
q.mu.Unlock()
|
||||
return canceled
|
||||
}
|
||||
|
||||
// addPromise records a returned capability as being used for a call.
|
||||
// This is needed for determining embargoes upon resolution. The
|
||||
// caller must be holding onto q.conn.mu.
|
||||
func (q *question) addPromise(transform []capnp.PipelineOp) {
|
||||
for _, d := range q.derived {
|
||||
if transformsEqual(transform, d) {
|
||||
return
|
||||
}
|
||||
}
|
||||
q.derived = append(q.derived, transform)
|
||||
}
|
||||
|
||||
func transformsEqual(t, u []capnp.PipelineOp) bool {
|
||||
if len(t) != len(u) {
|
||||
return false
|
||||
}
|
||||
for i := range t {
|
||||
if t[i].Field != u[i].Field {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (q *question) Struct() (capnp.Struct, error) {
|
||||
select {
|
||||
case <-q.resolved:
|
||||
case <-q.conn.bg.Done():
|
||||
return capnp.Struct{}, ErrConnClosed
|
||||
}
|
||||
q.mu.RLock()
|
||||
s, err := q.obj.Struct(), q.err
|
||||
q.mu.RUnlock()
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (q *question) PipelineCall(transform []capnp.PipelineOp, ccall *capnp.Call) capnp.Answer {
|
||||
select {
|
||||
case <-q.conn.mu:
|
||||
if err := q.conn.startWork(); err != nil {
|
||||
q.conn.mu.Unlock()
|
||||
return capnp.ErrorAnswer(err)
|
||||
}
|
||||
case <-ccall.Ctx.Done():
|
||||
return capnp.ErrorAnswer(ccall.Ctx.Err())
|
||||
}
|
||||
ans := q.lockedPipelineCall(transform, ccall)
|
||||
q.conn.workers.Done()
|
||||
q.conn.mu.Unlock()
|
||||
return ans
|
||||
}
|
||||
|
||||
// lockedPipelineCall is equivalent to PipelineCall but assumes that the
|
||||
// caller is already holding onto q.conn.mu.
|
||||
func (q *question) lockedPipelineCall(transform []capnp.PipelineOp, ccall *capnp.Call) capnp.Answer {
|
||||
if q.conn.findQuestion(q.id) != q {
|
||||
// Question has been finished. The call should happen as if it is
|
||||
// back in application code.
|
||||
q.mu.RLock()
|
||||
obj, err, state := q.obj, q.err, q.state
|
||||
q.mu.RUnlock()
|
||||
if state == questionInProgress {
|
||||
panic("question popped but not done")
|
||||
}
|
||||
client := clientFromResolution(transform, obj, err)
|
||||
return q.conn.lockedCall(client, ccall)
|
||||
}
|
||||
|
||||
pipeq := q.conn.newQuestion(ccall.Ctx, &ccall.Method)
|
||||
msg := newMessage(nil)
|
||||
msgCall, _ := msg.NewCall()
|
||||
msgCall.SetQuestionId(uint32(pipeq.id))
|
||||
msgCall.SetInterfaceId(ccall.Method.InterfaceID)
|
||||
msgCall.SetMethodId(ccall.Method.MethodID)
|
||||
target, _ := msgCall.NewTarget()
|
||||
a, _ := target.NewPromisedAnswer()
|
||||
a.SetQuestionId(uint32(q.id))
|
||||
err := transformToPromisedAnswer(a.Segment(), a, transform)
|
||||
if err != nil {
|
||||
q.conn.popQuestion(pipeq.id)
|
||||
return capnp.ErrorAnswer(err)
|
||||
}
|
||||
payload, _ := msgCall.NewParams()
|
||||
if err := q.conn.fillParams(payload, ccall); err != nil {
|
||||
q.conn.popQuestion(q.id)
|
||||
return capnp.ErrorAnswer(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case q.conn.out <- msg:
|
||||
case <-ccall.Ctx.Done():
|
||||
q.conn.popQuestion(pipeq.id)
|
||||
return capnp.ErrorAnswer(ccall.Ctx.Err())
|
||||
case <-q.conn.bg.Done():
|
||||
q.conn.popQuestion(pipeq.id)
|
||||
return capnp.ErrorAnswer(ErrConnClosed)
|
||||
}
|
||||
q.addPromise(transform)
|
||||
pipeq.start()
|
||||
return pipeq
|
||||
}
|
||||
|
||||
func (q *question) PipelineClose(transform []capnp.PipelineOp) error {
|
||||
<-q.resolved
|
||||
q.mu.RLock()
|
||||
obj, err := q.obj, q.err
|
||||
q.mu.RUnlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
x, err := capnp.TransformPtr(obj, transform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := x.Interface().Client()
|
||||
if c == nil {
|
||||
return capnp.ErrNullClient
|
||||
}
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
// embargoClient is a client that waits until an embargo signal is
|
||||
// received to deliver calls.
|
||||
type embargoClient struct {
|
||||
cancel <-chan struct{}
|
||||
client capnp.Client
|
||||
embargo embargo
|
||||
|
||||
mu sync.RWMutex
|
||||
q queue.Queue
|
||||
calls ecallList
|
||||
}
|
||||
|
||||
func newEmbargoClient(client capnp.Client, e embargo, cancel <-chan struct{}) *embargoClient {
|
||||
ec := &embargoClient{
|
||||
client: client,
|
||||
embargo: e,
|
||||
cancel: cancel,
|
||||
calls: make(ecallList, callQueueSize),
|
||||
}
|
||||
ec.q.Init(ec.calls, 0)
|
||||
go ec.flushQueue()
|
||||
return ec
|
||||
}
|
||||
|
||||
func (ec *embargoClient) push(cl *capnp.Call) capnp.Answer {
|
||||
f := new(fulfiller.Fulfiller)
|
||||
cl, err := cl.Copy(nil)
|
||||
if err != nil {
|
||||
return capnp.ErrorAnswer(err)
|
||||
}
|
||||
i := ec.q.Push()
|
||||
if i == -1 {
|
||||
return capnp.ErrorAnswer(errQueueFull)
|
||||
}
|
||||
ec.calls[i] = ecall{cl, f}
|
||||
return f
|
||||
}
|
||||
|
||||
func (ec *embargoClient) Call(cl *capnp.Call) capnp.Answer {
|
||||
// Fast path: queue is flushed.
|
||||
ec.mu.RLock()
|
||||
ok := ec.isPassthrough()
|
||||
ec.mu.RUnlock()
|
||||
if ok {
|
||||
return ec.client.Call(cl)
|
||||
}
|
||||
|
||||
ec.mu.Lock()
|
||||
if ec.isPassthrough() {
|
||||
ec.mu.Unlock()
|
||||
return ec.client.Call(cl)
|
||||
}
|
||||
ans := ec.push(cl)
|
||||
ec.mu.Unlock()
|
||||
return ans
|
||||
}
|
||||
|
||||
func (ec *embargoClient) tryQueue(cl *capnp.Call) capnp.Answer {
|
||||
ec.mu.Lock()
|
||||
if ec.isPassthrough() {
|
||||
ec.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
ans := ec.push(cl)
|
||||
ec.mu.Unlock()
|
||||
return ans
|
||||
}
|
||||
|
||||
func (ec *embargoClient) isPassthrough() bool {
|
||||
select {
|
||||
case <-ec.embargo:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return ec.q.Len() == 0
|
||||
}
|
||||
|
||||
func (ec *embargoClient) Close() error {
|
||||
ec.mu.Lock()
|
||||
for ; ec.q.Len() > 0; ec.q.Pop() {
|
||||
c := ec.calls[ec.q.Front()]
|
||||
c.f.Reject(errQueueCallCancel)
|
||||
}
|
||||
ec.mu.Unlock()
|
||||
return ec.client.Close()
|
||||
}
|
||||
|
||||
// flushQueue is run in its own goroutine.
|
||||
func (ec *embargoClient) flushQueue() {
|
||||
select {
|
||||
case <-ec.embargo:
|
||||
case <-ec.cancel:
|
||||
ec.mu.Lock()
|
||||
for ec.q.Len() > 0 {
|
||||
ec.q.Pop()
|
||||
}
|
||||
ec.mu.Unlock()
|
||||
return
|
||||
}
|
||||
var c ecall
|
||||
ec.mu.RLock()
|
||||
if i := ec.q.Front(); i != -1 {
|
||||
c = ec.calls[i]
|
||||
}
|
||||
ec.mu.RUnlock()
|
||||
for c.call != nil {
|
||||
ans := ec.client.Call(c.call)
|
||||
go joinFulfiller(c.f, ans)
|
||||
|
||||
ec.mu.Lock()
|
||||
ec.q.Pop()
|
||||
if i := ec.q.Front(); i != -1 {
|
||||
c = ec.calls[i]
|
||||
} else {
|
||||
c = ecall{}
|
||||
}
|
||||
ec.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
type ecall struct {
|
||||
call *capnp.Call
|
||||
f *fulfiller.Fulfiller
|
||||
}
|
||||
|
||||
type ecallList []ecall
|
||||
|
||||
func (el ecallList) Len() int {
|
||||
return len(el)
|
||||
}
|
||||
|
||||
func (el ecallList) Clear(i int) {
|
||||
el[i] = ecall{}
|
||||
}
|
Reference in New Issue
Block a user