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