debug: finish server when all session finished
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>pull/2010/head
parent
86ae8ea854
commit
caa83a6d84
@ -0,0 +1,217 @@
|
|||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/docker/buildx/controller/processes"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type session struct {
|
||||||
|
manager *sessionManager
|
||||||
|
|
||||||
|
buildOnGoing atomic.Bool
|
||||||
|
statusChan chan *pb.StatusResponse
|
||||||
|
cancelBuild func()
|
||||||
|
buildOptions *pb.BuildOptions
|
||||||
|
inputPipe *io.PipeWriter
|
||||||
|
|
||||||
|
result *build.ResultHandle
|
||||||
|
|
||||||
|
processes *processes.Manager
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) close() {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.processes.CancelRunningProcesses()
|
||||||
|
if s.cancelBuild != nil {
|
||||||
|
s.cancelBuild()
|
||||||
|
}
|
||||||
|
if s.result != nil {
|
||||||
|
s.result.Done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) getProcess(id string) (*processes.Process, bool) {
|
||||||
|
return s.processes.Get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) listProcesses() []*pb.ProcessInfo {
|
||||||
|
return s.processes.ListProcesses()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) deleteProcess(id string) error {
|
||||||
|
return s.processes.DeleteProcess(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) startProcess(id string, cfg *pb.InvokeConfig) (*processes.Process, error) {
|
||||||
|
res := s.result
|
||||||
|
return s.processes.StartProcess(id, res, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) build(ctx context.Context, options *pb.BuildOptions, wrapBuildError func(error) error) (*client.SolveResponse, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
if !s.buildOnGoing.CompareAndSwap(false, true) {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return nil, errors.New("build ongoing")
|
||||||
|
}
|
||||||
|
if s.processes != nil {
|
||||||
|
s.processes.CancelRunningProcesses()
|
||||||
|
}
|
||||||
|
s.processes = processes.NewManager()
|
||||||
|
s.result = nil
|
||||||
|
s.statusChan = make(chan *pb.StatusResponse)
|
||||||
|
|
||||||
|
inR, inW := io.Pipe()
|
||||||
|
s.inputPipe = inW
|
||||||
|
|
||||||
|
bCtx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
var doneOnce sync.Once
|
||||||
|
s.cancelBuild = func() {
|
||||||
|
doneOnce.Do(func() {
|
||||||
|
s.mu.Lock()
|
||||||
|
cancel()
|
||||||
|
close(s.statusChan)
|
||||||
|
s.statusChan = nil
|
||||||
|
s.buildOnGoing.Store(false)
|
||||||
|
s.mu.Unlock()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defer s.cancelBuild()
|
||||||
|
s.manager.updateCond.Broadcast()
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
resp, res, buildErr := s.manager.buildFunc(bCtx, options, inR, pb.NewProgressWriter(s.statusChan))
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
// NOTE: buildFunc can return *build.ResultHandle even on error (e.g. when it's implemented using (github.com/docker/buildx/controller/build).RunBuild).
|
||||||
|
if res != nil {
|
||||||
|
s.result = res
|
||||||
|
s.buildOptions = options
|
||||||
|
s.manager.updateCond.Broadcast()
|
||||||
|
if buildErr != nil {
|
||||||
|
buildErr = wrapBuildError(buildErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
return resp, buildErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) getStatusChan() chan *pb.StatusResponse {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.statusChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) getInputWriter() *io.PipeWriter {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.inputPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) getBuildOptions() *pb.BuildOptions {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.buildOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSessionManager(buildFunc BuildFunc) *sessionManager {
|
||||||
|
var mu sync.Mutex
|
||||||
|
return &sessionManager{
|
||||||
|
updateCond: sync.NewCond(&mu),
|
||||||
|
updateCondMu: &mu,
|
||||||
|
buildFunc: buildFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sessionManager struct {
|
||||||
|
sessions sync.Map
|
||||||
|
buildFunc BuildFunc
|
||||||
|
|
||||||
|
updateCond *sync.Cond
|
||||||
|
updateCondMu *sync.Mutex
|
||||||
|
|
||||||
|
created atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sessionManager) newSession(ref string) *session {
|
||||||
|
s := &session{manager: m, processes: processes.NewManager()}
|
||||||
|
m.add(ref, s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sessionManager) add(id string, v *session) {
|
||||||
|
m.sessions.Store(id, v)
|
||||||
|
m.updateCond.Broadcast()
|
||||||
|
m.created.Store(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sessionManager) get(id string) *session {
|
||||||
|
v, ok := m.sessions.Load(id)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.(*session)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sessionManager) delete(id string) error {
|
||||||
|
v, ok := m.sessions.LoadAndDelete(id)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("unknown session %q", id)
|
||||||
|
}
|
||||||
|
v.(*session).close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sessionManager) list() (res []string) {
|
||||||
|
m.sessions.Range(func(key, value any) bool {
|
||||||
|
res = append(res, key.(string))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sessionManager) close() error {
|
||||||
|
m.sessions.Range(func(key, value any) bool {
|
||||||
|
value.(*session).close()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sessionManager) waitAndGet(ctx context.Context, id string, f func(s *session) bool) (*session, error) {
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
m.updateCond.Broadcast()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
s := m.get(id)
|
||||||
|
if s == nil || !f(s) {
|
||||||
|
m.updateCondMu.Lock()
|
||||||
|
m.updateCond.Wait()
|
||||||
|
m.updateCondMu.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *sessionManager) sessionCreated() bool {
|
||||||
|
return m.created.Load()
|
||||||
|
}
|
Loading…
Reference in New Issue