mirror of
https://github.com/moby/buildkit.git
synced 2026-08-04 06:40:22 +00:00
This updates the pipe library to use generics for the request payload and the status value. This allows the solver to put in explicit types rather than rely on type casting from interfaces which helps with type safety and understandability. The status value used by the solver uses the `any` type instead of an explicit type because the `unpark` method takes a generic list of pipes and the different pipes have different result types. We can likely change this in the future or create a discriminated union for the types that can be used in this package. That is left for future work because at least the request payload is typed now. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
194 lines
3.7 KiB
Go
194 lines
3.7 KiB
Go
package pipe
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type channel[V any] struct {
|
|
OnSendCompletion func()
|
|
value atomic.Pointer[V]
|
|
lastValue *V
|
|
}
|
|
|
|
func (c *channel[V]) Send(v V) {
|
|
c.value.Store(&v)
|
|
if c.OnSendCompletion != nil {
|
|
c.OnSendCompletion()
|
|
}
|
|
}
|
|
|
|
func (c *channel[V]) Receive() (V, bool) {
|
|
v := c.value.Load()
|
|
if v == nil || v == c.lastValue {
|
|
return *new(V), false
|
|
}
|
|
c.lastValue = v
|
|
return *v, true
|
|
}
|
|
|
|
type Pipe[Payload, Value any] struct {
|
|
Sender Sender[Payload, Value]
|
|
Receiver Receiver[Payload, Value]
|
|
OnReceiveCompletion func()
|
|
OnSendCompletion func()
|
|
}
|
|
|
|
type Request[Payload any] struct {
|
|
Payload Payload
|
|
Canceled bool
|
|
}
|
|
|
|
type Sender[Payload, Value any] interface {
|
|
Request() Request[Payload]
|
|
Update(v Value)
|
|
Finalize(v Value, err error)
|
|
Status() Status[Value]
|
|
}
|
|
|
|
type Receiver[Payload, Value any] interface {
|
|
Receive() bool
|
|
Cancel()
|
|
Status() Status[Value]
|
|
Request() Payload
|
|
}
|
|
|
|
type Status[Value any] struct {
|
|
Canceled bool
|
|
Completed bool
|
|
Err error
|
|
Value Value
|
|
}
|
|
|
|
func NewWithFunction[Payload, Value any](f func(context.Context) (Value, error)) (*Pipe[Payload, Value], func()) {
|
|
p := New[Payload, Value](Request[Payload]{})
|
|
|
|
ctx, cancel := context.WithCancelCause(context.TODO())
|
|
|
|
p.OnReceiveCompletion = func() {
|
|
if req := p.Sender.Request(); req.Canceled {
|
|
cancel(errors.WithStack(context.Canceled))
|
|
}
|
|
}
|
|
|
|
return p, func() {
|
|
res, err := f(ctx)
|
|
if err != nil {
|
|
p.Sender.Finalize(*new(Value), err)
|
|
return
|
|
}
|
|
p.Sender.Finalize(res, nil)
|
|
}
|
|
}
|
|
|
|
func New[Payload, Value any](req Request[Payload]) *Pipe[Payload, Value] {
|
|
cancelCh := &channel[Request[Payload]]{}
|
|
roundTripCh := &channel[Status[Value]]{}
|
|
pw := &sender[Payload, Value]{
|
|
req: req,
|
|
sendChannel: roundTripCh,
|
|
}
|
|
pr := &receiver[Payload, Value]{
|
|
req: req,
|
|
recvChannel: roundTripCh,
|
|
sendChannel: cancelCh,
|
|
}
|
|
|
|
p := &Pipe[Payload, Value]{
|
|
Sender: pw,
|
|
Receiver: pr,
|
|
}
|
|
|
|
cancelCh.OnSendCompletion = func() {
|
|
v, ok := cancelCh.Receive()
|
|
if ok {
|
|
pw.setRequest(v)
|
|
}
|
|
if p.OnReceiveCompletion != nil {
|
|
p.OnReceiveCompletion()
|
|
}
|
|
}
|
|
|
|
roundTripCh.OnSendCompletion = func() {
|
|
if p.OnSendCompletion != nil {
|
|
p.OnSendCompletion()
|
|
}
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
type sender[Payload, Value any] struct {
|
|
status Status[Value]
|
|
req Request[Payload]
|
|
sendChannel *channel[Status[Value]]
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func (pw *sender[Payload, Value]) Status() Status[Value] {
|
|
return pw.status
|
|
}
|
|
|
|
func (pw *sender[Payload, Value]) Request() Request[Payload] {
|
|
pw.mu.Lock()
|
|
defer pw.mu.Unlock()
|
|
return pw.req
|
|
}
|
|
|
|
func (pw *sender[Payload, Value]) setRequest(req Request[Payload]) {
|
|
pw.mu.Lock()
|
|
defer pw.mu.Unlock()
|
|
pw.req = req
|
|
}
|
|
|
|
func (pw *sender[Payload, Value]) Update(v Value) {
|
|
pw.status.Value = v
|
|
pw.sendChannel.Send(pw.status)
|
|
}
|
|
|
|
func (pw *sender[Payload, Value]) Finalize(v Value, err error) {
|
|
pw.status.Value = v
|
|
pw.status.Err = err
|
|
pw.status.Completed = true
|
|
if errors.Is(err, context.Canceled) && pw.req.Canceled {
|
|
pw.status.Canceled = true
|
|
}
|
|
pw.sendChannel.Send(pw.status)
|
|
}
|
|
|
|
type receiver[Payload, Value any] struct {
|
|
status Status[Value]
|
|
req Request[Payload]
|
|
recvChannel *channel[Status[Value]]
|
|
sendChannel *channel[Request[Payload]]
|
|
}
|
|
|
|
func (pr *receiver[Payload, Value]) Request() Payload {
|
|
return pr.req.Payload
|
|
}
|
|
|
|
func (pr *receiver[Payload, Value]) Receive() bool {
|
|
v, ok := pr.recvChannel.Receive()
|
|
if !ok {
|
|
return false
|
|
}
|
|
pr.status = v
|
|
return true
|
|
}
|
|
|
|
func (pr *receiver[Payload, Value]) Cancel() {
|
|
req := pr.req
|
|
if req.Canceled {
|
|
return
|
|
}
|
|
req.Canceled = true
|
|
pr.sendChannel.Send(req)
|
|
}
|
|
|
|
func (pr *receiver[Payload, Value]) Status() Status[Value] {
|
|
return pr.status
|
|
}
|