penguin/AstroJS

Javascript framework for my blog

resolver-go/internal/compilecache/cache.go

raw ยท 1867 bytes

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)
}