package closure import ( "context" "errors" "time" ) func NewCompileCache(workers int) *CompileCache { c := &CompileCache{ client: NewClientFromEnv(), states: make(map[string]CompileState), results: make(map[string][]byte), errors: make(map[string]error), jobs: make(chan CompileJob, 128), } for i := 0; i < workers; i++ { go c.worker() } return c } func (c *CompileCache) Get(hash string) ([]byte, bool) { c.mu.Lock() defer c.mu.Unlock() if c.states[hash] != CompileReady { return nil, false } return c.results[hash], true } func (c *CompileCache) Enqueue(job CompileJob) { c.mu.Lock() switch c.states[job.Hash] { case CompilePending, CompileReady: c.mu.Unlock() return } c.states[job.Hash] = CompilePending c.mu.Unlock() select { case c.jobs <- job: default: // Queue full. Don't block request path. c.mu.Lock() c.states[job.Hash] = CompileMissing c.errors[job.Hash] = errors.New("compile queue full") c.mu.Unlock() } } func (c *CompileCache) worker() { for job := range c.jobs { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) req := CompileRequest{ ExternSources: job.ExternSources, JSSources: job.JSSources, Defines: job.Defines, } c.client.DebugPrintCurl(ctx, req) out, err := c.client.Compile(ctx, req) cancel() c.mu.Lock() if err == nil { c.states[job.Hash] = CompileReady c.results[job.Hash] = out delete(c.errors, job.Hash) } else { c.states[job.Hash] = CompileFailed c.errors[job.Hash] = err } c.mu.Unlock() } }