package goroutineinstance import ( "encoding/json" "fmt" "time" "gfx.cafe/util/go/generic" "github.com/DataDog/gostackparse" "github.com/rs/xid" "github.com/spf13/afero" ) type InstanceStore interface { NewInstance(xs []*gostackparse.Goroutine) (*Instance, error) GetInstance(id xid.ID) (*Instance, error) } type fileBasedInstanceStore struct { expiry time.Duration fs afero.Fs cache generic.Map[xid.ID, *Instance] } func NewFileBasedInstanceStore(fs afero.Fs, expiry time.Duration) *fileBasedInstanceStore { return &fileBasedInstanceStore{ fs: fs, expiry: expiry, } } func (o *fileBasedInstanceStore) NewInstance(dat []*gostackparse.Goroutine) (*Instance, error) { // save instance id := xid.New() i := &Instance{ id: id, dat: dat, } jsonDat, err := json.Marshal(dat) if err != nil { return nil, err } file, err := o.fs.Create(id.String()) if err != nil { return nil, err } _, err = file.Write(jsonDat) if err != nil { file.Close() return nil, err } err = file.Close() if err != nil { return nil, err } o.cache.Store(id, i) return i, nil } func (o *fileBasedInstanceStore) GetInstance(id xid.ID) (*Instance, error) { if i, ok := o.cache.Load(id); ok { return i, nil } if o.expiry > 0 { if id.Time().After(time.Now().Add(o.expiry)) { o.fs.Remove(id.String()) return nil, fmt.Errorf("goroutine expired") } } bts, err := afero.ReadFile(o.fs, id.String()) if err != nil { return nil, err } i := &Instance{id: id} err = json.Unmarshal(bts, &i.dat) if err != nil { return nil, err } o.cache.Store(id, i) return i, nil }