237 lines
5.8 KiB
Go
237 lines
5.8 KiB
Go
package project
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"iter"
|
|
"sync"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
)
|
|
|
|
type checkerPool struct {
|
|
maxCheckers int
|
|
program *compiler.Program
|
|
|
|
mu sync.Mutex
|
|
cond *sync.Cond
|
|
createCheckersOnce sync.Once
|
|
checkers []*checker.Checker
|
|
inUse map[*checker.Checker]bool
|
|
fileAssociations map[*ast.SourceFile]int
|
|
requestAssociations map[string]int
|
|
log func(msg string)
|
|
}
|
|
|
|
var _ compiler.CheckerPool = (*checkerPool)(nil)
|
|
|
|
func newCheckerPool(maxCheckers int, program *compiler.Program, log func(msg string)) *checkerPool {
|
|
pool := &checkerPool{
|
|
program: program,
|
|
maxCheckers: maxCheckers,
|
|
checkers: make([]*checker.Checker, maxCheckers),
|
|
inUse: make(map[*checker.Checker]bool),
|
|
requestAssociations: make(map[string]int),
|
|
log: log,
|
|
}
|
|
|
|
pool.cond = sync.NewCond(&pool.mu)
|
|
return pool
|
|
}
|
|
|
|
func (p *checkerPool) GetCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
requestID := core.GetRequestID(ctx)
|
|
if requestID != "" {
|
|
if checker, release := p.getRequestCheckerLocked(requestID); checker != nil {
|
|
return checker, release
|
|
}
|
|
}
|
|
|
|
if p.fileAssociations == nil {
|
|
p.fileAssociations = make(map[*ast.SourceFile]int)
|
|
}
|
|
|
|
if index, ok := p.fileAssociations[file]; ok {
|
|
checker := p.checkers[index]
|
|
if checker != nil {
|
|
if inUse := p.inUse[checker]; !inUse {
|
|
p.inUse[checker] = true
|
|
if requestID != "" {
|
|
p.requestAssociations[requestID] = index
|
|
}
|
|
return checker, p.createRelease(requestID, index, checker)
|
|
}
|
|
}
|
|
}
|
|
|
|
checker, index := p.getCheckerLocked(requestID)
|
|
p.fileAssociations[file] = index
|
|
return checker, p.createRelease(requestID, index, checker)
|
|
}
|
|
|
|
func (p *checkerPool) GetChecker(ctx context.Context) (*checker.Checker, func()) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
checker, index := p.getCheckerLocked(core.GetRequestID(ctx))
|
|
return checker, p.createRelease(core.GetRequestID(ctx), index, checker)
|
|
}
|
|
|
|
func (p *checkerPool) Files(checker *checker.Checker) iter.Seq[*ast.SourceFile] {
|
|
panic("unimplemented")
|
|
}
|
|
|
|
func (p *checkerPool) GetAllCheckers(ctx context.Context) ([]*checker.Checker, func()) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
requestID := core.GetRequestID(ctx)
|
|
if requestID == "" {
|
|
panic("cannot call GetAllCheckers on a project.checkerPool without a request ID")
|
|
}
|
|
|
|
// A request can only access one checker
|
|
if c, release := p.getRequestCheckerLocked(requestID); c != nil {
|
|
return []*checker.Checker{c}, release
|
|
}
|
|
|
|
c, release := p.GetChecker(ctx)
|
|
return []*checker.Checker{c}, release
|
|
}
|
|
|
|
func (p *checkerPool) getCheckerLocked(requestID string) (*checker.Checker, int) {
|
|
if checker, index := p.getImmediatelyAvailableChecker(); checker != nil {
|
|
p.inUse[checker] = true
|
|
if requestID != "" {
|
|
p.requestAssociations[requestID] = index
|
|
}
|
|
return checker, index
|
|
}
|
|
|
|
if !p.isFullLocked() {
|
|
checker, index := p.createCheckerLocked()
|
|
p.inUse[checker] = true
|
|
if requestID != "" {
|
|
p.requestAssociations[requestID] = index
|
|
}
|
|
return checker, index
|
|
}
|
|
|
|
checker, index := p.waitForAvailableChecker()
|
|
p.inUse[checker] = true
|
|
if requestID != "" {
|
|
p.requestAssociations[requestID] = index
|
|
}
|
|
return checker, index
|
|
}
|
|
|
|
func (p *checkerPool) getRequestCheckerLocked(requestID string) (*checker.Checker, func()) {
|
|
if index, ok := p.requestAssociations[requestID]; ok {
|
|
checker := p.checkers[index]
|
|
if checker != nil {
|
|
if inUse := p.inUse[checker]; !inUse {
|
|
p.inUse[checker] = true
|
|
return checker, p.createRelease(requestID, index, checker)
|
|
}
|
|
// Checker is in use, but by the same request - assume it's the
|
|
// same goroutine or is managing its own synchronization
|
|
return checker, noop
|
|
}
|
|
}
|
|
return nil, noop
|
|
}
|
|
|
|
func (p *checkerPool) getImmediatelyAvailableChecker() (*checker.Checker, int) {
|
|
for i, checker := range p.checkers {
|
|
if checker == nil {
|
|
continue
|
|
}
|
|
if inUse := p.inUse[checker]; !inUse {
|
|
return checker, i
|
|
}
|
|
}
|
|
|
|
return nil, -1
|
|
}
|
|
|
|
func (p *checkerPool) waitForAvailableChecker() (*checker.Checker, int) {
|
|
p.log("checkerpool: Waiting for an available checker")
|
|
for {
|
|
p.cond.Wait()
|
|
checker, index := p.getImmediatelyAvailableChecker()
|
|
if checker != nil {
|
|
return checker, index
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *checkerPool) createRelease(requestId string, index int, checker *checker.Checker) func() {
|
|
return func() {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
delete(p.requestAssociations, requestId)
|
|
if checker.WasCanceled() {
|
|
// Canceled checkers must be disposed
|
|
p.log(fmt.Sprintf("checkerpool: Checker for request %s was canceled, disposing it", requestId))
|
|
p.checkers[index] = nil
|
|
delete(p.inUse, checker)
|
|
} else {
|
|
p.inUse[checker] = false
|
|
}
|
|
p.cond.Signal()
|
|
}
|
|
}
|
|
|
|
func (p *checkerPool) isFullLocked() bool {
|
|
for _, checker := range p.checkers {
|
|
if checker == nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p *checkerPool) createCheckerLocked() (*checker.Checker, int) {
|
|
for i, existing := range p.checkers {
|
|
if existing == nil {
|
|
checker := checker.NewChecker(p.program)
|
|
p.checkers[i] = checker
|
|
return checker, i
|
|
}
|
|
}
|
|
panic("called createCheckerLocked when pool is full")
|
|
}
|
|
|
|
func (p *checkerPool) isRequestCheckerInUse(requestID string) bool {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if index, ok := p.requestAssociations[requestID]; ok {
|
|
checker := p.checkers[index]
|
|
if checker != nil {
|
|
return p.inUse[checker]
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *checkerPool) size() int {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
size := 0
|
|
for _, checker := range p.checkers {
|
|
if checker != nil {
|
|
size++
|
|
}
|
|
}
|
|
return size
|
|
}
|
|
|
|
func noop() {}
|