penguin/AstroJS

Javascript framework for my blog

resolver-go/internal/closure/cache.go

raw ยท 1627 bytes

package closure

import (
	"context"
	"errors"
	"log"
	"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 {
			log.Printf("compile failed: %v", err)
			c.states[job.Hash] = CompileFailed
			c.errors[job.Hash] = err
		}
		c.mu.Unlock()
	}
}