package compilecache import ( "context" ) func New(compiler Compiler, opt Options) *Cache { if opt.Workers <= 0 { opt.Workers = 1 } if opt.QueueSize <= 0 { opt.QueueSize = 128 } c := &Cache{ compiler: compiler, disableCache: opt.DisableCache, states: make(map[string]State), results: make(map[string][]byte), errors: make(map[string]error), jobs: make(chan Job, opt.QueueSize), } if !opt.DisableCache { for i := 0; i < opt.Workers; i++ { go c.worker() } } return c } func (c *Cache) Get(hash string) (State, []byte, error) { if c.disableCache { return Missing, nil, nil } c.mu.Lock() defer c.mu.Unlock() state, ok := c.states[hash] if !ok { return Missing, nil, nil } switch state { case Ready: return Ready, c.results[hash], nil case Failed: return Failed, nil, c.errors[hash] default: return state, nil, nil } } func (c *Cache) Enqueue(job Job) State { if c.disableCache { // Pretend every request is new. // Caller will see Pending forever unless something else reads result, // so this only works if your caller does not rely on cache state. go c.runUncached(job) return Pending } c.mu.Lock() state, ok := c.states[job.Hash] if ok { c.mu.Unlock() return state } c.states[job.Hash] = Pending c.mu.Unlock() c.jobs <- job return Pending } func (c *Cache) runUncached(job Job) { _, _ = c.compiler.Compile(context.Background(), job) } func (c *Cache) worker() { for job := range c.jobs { c.run(job) } } func (c *Cache) run(job Job) { result, err := c.compiler.Compile(context.Background(), job) c.mu.Lock() defer c.mu.Unlock() if err != nil { c.states[job.Hash] = Failed c.errors[job.Hash] = err delete(c.results, job.Hash) return } c.states[job.Hash] = Ready c.results[job.Hash] = result delete(c.errors, job.Hash) }