diff --git a/internal/bootstrap/op.go b/internal/bootstrap/op.go index 3b8d762..ec74026 100644 --- a/internal/bootstrap/op.go +++ b/internal/bootstrap/op.go @@ -2,11 +2,17 @@ package bootstrap import ( "context" + "time" + "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/internal/op" ) func InitOp(ctx context.Context) error { - op.Init(4096) + d, err := time.ParseDuration(conf.Conf.Room.TTL) + if err != nil { + return err + } + op.Init(4096, d) return nil } diff --git a/internal/conf/room.go b/internal/conf/room.go index e57fd55..fe0403b 100644 --- a/internal/conf/room.go +++ b/internal/conf/room.go @@ -1,11 +1,13 @@ package conf type RoomConfig struct { - MustPassword bool `yaml:"must_password" hc:"must input password to create room" env:"ROOM_MUST_PASSWORD"` + MustPassword bool `yaml:"must_password" hc:"must input password to create room" env:"ROOM_MUST_PASSWORD"` + TTL string `yaml:"ttl" hc:"set how long the room will be inactive before the memory will be reclaimed"` } func DefaultRoomConfig() RoomConfig { return RoomConfig{ MustPassword: false, + TTL: "48h", } } diff --git a/internal/op/op.go b/internal/op/op.go index 8a8f93c..7d07dec 100644 --- a/internal/op/op.go +++ b/internal/op/op.go @@ -1,10 +1,17 @@ package op import ( + "time" + "github.com/bluele/gcache" + synccache "github.com/synctv-org/synctv/utils/syncCache" ) -func Init(size int) error { +func Init(size int, ttl time.Duration) error { + roomTTL = ttl + roomCache = synccache.NewSyncCache[uint, *Room](time.Minute*5, synccache.WithDeletedCallback[uint, *Room](func(v *Room) { + v.close() + })) userCache = gcache.New(size). LRU(). Build() diff --git a/internal/op/rooms.go b/internal/op/rooms.go index 84c79bd..8bfd843 100644 --- a/internal/op/rooms.go +++ b/internal/op/rooms.go @@ -3,13 +3,15 @@ package op import ( "errors" "hash/crc32" + "time" "github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/model" - "github.com/zijiren233/gencontainer/rwmap" + synccache "github.com/synctv-org/synctv/utils/syncCache" ) -var roomCache rwmap.RWMap[uint, *Room] +var roomTTL = time.Hour * 24 * 2 +var roomCache *synccache.SyncCache[uint, *Room] func CreateRoom(name, password string, conf ...db.CreateRoomConfig) (*Room, error) { r, err := db.CreateRoom(name, password, conf...) @@ -28,22 +30,26 @@ func InitRoom(room *model.Room) (*Room, error) { roomID: room.ID, }, } - r, loaded := roomCache.LoadOrStore(room.ID, r) + i, loaded := roomCache.LoadOrStore(room.ID, r, roomTTL) if loaded { return r, errors.New("room already init") } - return r, nil + return i.Value(), nil } func LoadOrInitRoom(room *model.Room) (*Room, bool) { - return roomCache.LoadOrStore(room.ID, &Room{ + i, loaded := roomCache.LoadOrStore(room.ID, &Room{ Room: *room, version: crc32.ChecksumIEEE(room.HashedPassword), current: newCurrent(), movies: movies{ roomID: room.ID, }, - }) + }, roomTTL) + if loaded { + i.SetExpiration(time.Now().Add(roomTTL)) + } + return i.Value(), loaded } func DeleteRoom(roomID uint) error { @@ -57,36 +63,38 @@ func DeleteRoom(roomID uint) error { func CloseRoom(roomID uint) error { r, loaded := roomCache.LoadAndDelete(roomID) if loaded { - r.close() + r.Value().close() } - return nil + return errors.New("room not found in cache") } func LoadRoomByID(id uint) (*Room, error) { - r2, ok := roomCache.Load(id) - if ok { - return r2, nil + r2, loaded := roomCache.Load(id) + if loaded { + r2.SetExpiration(time.Now().Add(roomTTL)) + return r2.Value(), nil } return nil, errors.New("room not found") } func LoadOrInitRoomByID(id uint) (*Room, error) { - r, ok := roomCache.Load(id) - if ok { - return r, nil + i, loaded := roomCache.Load(id) + if loaded { + i.SetExpiration(time.Now().Add(roomTTL)) + return i.Value(), nil } room, err := db.GetRoomByID(id) if err != nil { return nil, err } - r, _ = LoadOrInitRoom(room) + r, _ := LoadOrInitRoom(room) return r, nil } func ClientNum(roomID uint) int64 { - r, ok := roomCache.Load(roomID) - if ok { - return r.ClientNum() + r, loaded := roomCache.Load(roomID) + if loaded { + return r.Value().ClientNum() } return 0 } @@ -119,20 +127,12 @@ func SetRoomPassword(roomID uint, password string) error { return r.SetPassword(password) } -func GetAllRoomsInCache() []*Room { - rooms := make([]*Room, roomCache.Len()) - roomCache.Range(func(key uint, value *Room) bool { - rooms = append(rooms, value) - return true - }) - return rooms -} - func GetAllRoomsInCacheWithNoNeedPassword() []*Room { - rooms := make([]*Room, roomCache.Len()) - roomCache.Range(func(key uint, value *Room) bool { - if !value.NeedPassword() { - rooms = append(rooms, value) + rooms := make([]*Room, 0) + roomCache.Range(func(key uint, value *synccache.Entry[*Room]) bool { + v := value.Value() + if !v.NeedPassword() { + rooms = append(rooms, v) } return true }) @@ -140,10 +140,11 @@ func GetAllRoomsInCacheWithNoNeedPassword() []*Room { } func GetAllRoomsInCacheWithoutHidden() []*Room { - rooms := make([]*Room, 0, roomCache.Len()) - roomCache.Range(func(key uint, value *Room) bool { - if !value.Settings.Hidden { - rooms = append(rooms, value) + rooms := make([]*Room, 0) + roomCache.Range(func(key uint, value *synccache.Entry[*Room]) bool { + v := value.Value() + if !v.Settings.Hidden { + rooms = append(rooms, v) } return true }) diff --git a/internal/op/users.go b/internal/op/users.go index 814217e..7eab1b7 100644 --- a/internal/op/users.go +++ b/internal/op/users.go @@ -8,6 +8,7 @@ import ( "github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/internal/provider" + synccache "github.com/synctv-org/synctv/utils/syncCache" ) var userCache gcache.Cache @@ -78,10 +79,11 @@ func DeleteUserByID(userID uint) error { } userCache.Remove(userID) - roomCache.Range(func(key uint, value *Room) bool { - if value.CreatorID == userID { + roomCache.Range(func(key uint, value *synccache.Entry[*Room]) bool { + v := value.Value() + if v.CreatorID == userID { roomCache.Delete(key) - value.close() + v.close() } return true }) diff --git a/utils/syncCache/cache.go b/utils/syncCache/cache.go index b0f29f8..2b4d25f 100644 --- a/utils/syncCache/cache.go +++ b/utils/syncCache/cache.go @@ -7,7 +7,7 @@ import ( ) type SyncCache[K comparable, V any] struct { - cache rwmap.RWMap[K, *entry[V]] + cache rwmap.RWMap[K, *Entry[V]] deletedCallback func(v V) ticker *time.Ticker } @@ -20,10 +20,13 @@ func WithDeletedCallback[K comparable, V any](callback func(v V)) SyncCacheConfi } } -func NewSyncCache[K comparable, V any](trimTime time.Duration) *SyncCache[K, V] { +func NewSyncCache[K comparable, V any](trimTime time.Duration, conf ...SyncCacheConfig[K, V]) *SyncCache[K, V] { sc := &SyncCache[K, V]{ ticker: time.NewTicker(trimTime), } + for _, c := range conf { + c(sc) + } go func() { for range sc.ticker.C { sc.trim() @@ -38,7 +41,7 @@ func (sc *SyncCache[K, V]) Releases() { } func (sc *SyncCache[K, V]) trim() { - sc.cache.Range(func(key K, value *entry[V]) bool { + sc.cache.Range(func(key K, value *Entry[V]) bool { if value.IsExpired() { e, loaded := sc.cache.LoadAndDelete(key) if loaded && sc.deletedCallback != nil { @@ -53,21 +56,22 @@ func (sc *SyncCache[K, V]) Store(key K, value V, expire time.Duration) { sc.LoadOrStore(key, value, expire) } -func (sc *SyncCache[K, V]) Load(key K) (value V, loaded bool) { +func (sc *SyncCache[K, V]) Load(key K) (value *Entry[V], loaded bool) { e, ok := sc.cache.Load(key) if ok && !e.IsExpired() { - return e.value, ok + return e, ok } return } -func (sc *SyncCache[K, V]) LoadOrStore(key K, value V, expire time.Duration) (actual V, loaded bool) { +func (sc *SyncCache[K, V]) LoadOrStore(key K, value V, expire time.Duration) (actual *Entry[V], loaded bool) { e, loaded := sc.cache.LoadOrStore(key, NewEntry[V](value, expire)) if e.IsExpired() { - sc.cache.Store(key, NewEntry[V](value, expire)) - return value, false + e = NewEntry[V](value, expire) + sc.cache.Store(key, e) + return e, false } - return e.value, loaded + return e, loaded } func (sc *SyncCache[K, V]) AddExpiration(key K, d time.Duration) { @@ -88,10 +92,10 @@ func (sc *SyncCache[K, V]) Delete(key K) { sc.LoadAndDelete(key) } -func (sc *SyncCache[K, V]) LoadAndDelete(key K) (value V, loaded bool) { +func (sc *SyncCache[K, V]) LoadAndDelete(key K) (value *Entry[V], loaded bool) { e, loaded := sc.cache.LoadAndDelete(key) if loaded && !e.IsExpired() { - return e.value, loaded + return e, loaded } return } @@ -99,3 +103,12 @@ func (sc *SyncCache[K, V]) LoadAndDelete(key K) (value V, loaded bool) { func (sc *SyncCache[K, V]) Clear() { sc.cache.Clear() } + +func (sc *SyncCache[K, V]) Range(f func(key K, value *Entry[V]) bool) { + sc.cache.Range(func(key K, value *Entry[V]) bool { + if !value.IsExpired() { + return f(key, value) + } + return true + }) +} diff --git a/utils/syncCache/item.go b/utils/syncCache/item.go index 2dd70a8..128a2f9 100644 --- a/utils/syncCache/item.go +++ b/utils/syncCache/item.go @@ -5,26 +5,30 @@ import ( "time" ) -type entry[V any] struct { +type Entry[V any] struct { expiration int64 value V } -func NewEntry[V any](value V, expire time.Duration) *entry[V] { - return &entry[V]{ +func NewEntry[V any](value V, expire time.Duration) *Entry[V] { + return &Entry[V]{ expiration: time.Now().Add(expire).UnixMilli(), value: value, } } -func (e *entry[V]) IsExpired() bool { +func (e *Entry[V]) Value() V { + return e.value +} + +func (e *Entry[V]) IsExpired() bool { return time.Now().After(time.UnixMilli(atomic.LoadInt64(&e.expiration))) } -func (e *entry[V]) AddExpiration(d time.Duration) { +func (e *Entry[V]) AddExpiration(d time.Duration) { atomic.AddInt64(&e.expiration, int64(d)) } -func (e *entry[V]) SetExpiration(t time.Time) { +func (e *Entry[V]) SetExpiration(t time.Time) { atomic.StoreInt64(&e.expiration, t.UnixMilli()) }