summaryrefslogtreecommitdiff
path: root/internal/cache
diff options
context:
space:
mode:
Diffstat (limited to 'internal/cache')
-rw-r--r--internal/cache/ap.go29
-rw-r--r--internal/cache/cache.go80
-rw-r--r--internal/cache/gts.go313
-rw-r--r--internal/cache/util.go45
-rw-r--r--internal/cache/visibility.go81
5 files changed, 373 insertions, 175 deletions
diff --git a/internal/cache/ap.go b/internal/cache/ap.go
index 204752f54..6498d7991 100644
--- a/internal/cache/ap.go
+++ b/internal/cache/ap.go
@@ -17,27 +17,14 @@
package cache
-type APCaches interface {
- // Init will initialize all the ActivityPub caches in this collection.
- // NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
- Init()
+type APCaches struct{}
- // Start will attempt to start all of the ActivityPub caches, or panic.
- Start()
+// Init will initialize all the ActivityPub caches in this collection.
+// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
+func (c *APCaches) Init() {}
- // Stop will attempt to stop all of the ActivityPub caches, or panic.
- Stop()
-}
+// Start will attempt to start all of the ActivityPub caches, or panic.
+func (c *APCaches) Start() {}
-// NewAP returns a new default implementation of APCaches.
-func NewAP() APCaches {
- return &apCaches{}
-}
-
-type apCaches struct{}
-
-func (c *apCaches) Init() {}
-
-func (c *apCaches) Start() {}
-
-func (c *apCaches) Stop() {}
+// Stop will attempt to stop all of the ActivityPub caches, or panic.
+func (c *APCaches) Stop() {}
diff --git a/internal/cache/cache.go b/internal/cache/cache.go
index 834542a52..913d6eca7 100644
--- a/internal/cache/cache.go
+++ b/internal/cache/cache.go
@@ -17,13 +17,23 @@
package cache
+import (
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
type Caches struct {
// GTS provides access to the collection of gtsmodel object caches.
+ // (used by the database).
GTS GTSCaches
// AP provides access to the collection of ActivityPub object caches.
+ // (planned to be used by the typeconverter).
AP APCaches
+ // Visibility provides access to the item visibility cache.
+ // (used by the visibility filter).
+ Visibility VisibilityCache
+
// prevent pass-by-value.
_ nocopy
}
@@ -31,29 +41,77 @@ type Caches struct {
// Init will (re)initialize both the GTS and AP cache collections.
// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
func (c *Caches) Init() {
- if c.GTS == nil {
- // use default impl
- c.GTS = NewGTS()
- }
-
- if c.AP == nil {
- // use default impl
- c.AP = NewAP()
- }
-
- // initialize caches
c.GTS.Init()
c.AP.Init()
+ c.Visibility.Init()
+
+ // Setup cache invalidate hooks.
+ // !! READ THE METHOD COMMENT
+ c.setuphooks()
}
// Start will start both the GTS and AP cache collections.
func (c *Caches) Start() {
c.GTS.Start()
c.AP.Start()
+ c.Visibility.Start()
}
// Stop will stop both the GTS and AP cache collections.
func (c *Caches) Stop() {
c.GTS.Stop()
c.AP.Stop()
+ c.Visibility.Stop()
+}
+
+// setuphooks sets necessary cache invalidation hooks between caches,
+// as an invalidation indicates a database UPDATE / DELETE. INSERT is
+// not handled by invalidation hooks and must be invalidated manually.
+func (c *Caches) setuphooks() {
+ c.GTS.Account().SetInvalidateCallback(func(account *gtsmodel.Account) {
+ // Invalidate account ID cached visibility.
+ c.Visibility.Invalidate("ItemID", account.ID)
+ c.Visibility.Invalidate("RequesterID", account.ID)
+ })
+
+ c.GTS.Block().SetInvalidateCallback(func(block *gtsmodel.Block) {
+ // Invalidate block origin account ID cached visibility.
+ c.Visibility.Invalidate("ItemID", block.AccountID)
+ c.Visibility.Invalidate("RequesterID", block.AccountID)
+
+ // Invalidate block target account ID cached visibility.
+ c.Visibility.Invalidate("ItemID", block.TargetAccountID)
+ c.Visibility.Invalidate("RequesterID", block.TargetAccountID)
+ })
+
+ c.GTS.Follow().SetInvalidateCallback(func(follow *gtsmodel.Follow) {
+ // Invalidate follow origin account ID cached visibility.
+ c.Visibility.Invalidate("ItemID", follow.AccountID)
+ c.Visibility.Invalidate("RequesterID", follow.AccountID)
+
+ // Invalidate follow target account ID cached visibility.
+ c.Visibility.Invalidate("ItemID", follow.TargetAccountID)
+ c.Visibility.Invalidate("RequesterID", follow.TargetAccountID)
+ })
+
+ c.GTS.FollowRequest().SetInvalidateCallback(func(followReq *gtsmodel.FollowRequest) {
+ // Invalidate follow request origin account ID cached visibility.
+ c.Visibility.Invalidate("ItemID", followReq.AccountID)
+ c.Visibility.Invalidate("RequesterID", followReq.AccountID)
+
+ // Invalidate follow request target account ID cached visibility.
+ c.Visibility.Invalidate("ItemID", followReq.TargetAccountID)
+ c.Visibility.Invalidate("RequesterID", followReq.TargetAccountID)
+ })
+
+ c.GTS.Status().SetInvalidateCallback(func(status *gtsmodel.Status) {
+ // Invalidate status ID cached visibility.
+ c.Visibility.Invalidate("ItemID", status.ID)
+ })
+
+ c.GTS.User().SetInvalidateCallback(func(user *gtsmodel.User) {
+ // Invalidate local account ID cached visibility.
+ c.Visibility.Invalidate("ItemID", user.AccountID)
+ c.Visibility.Invalidate("RequesterID", user.AccountID)
+ })
}
diff --git a/internal/cache/gts.go b/internal/cache/gts.go
index 72c3211a8..392fc8449 100644
--- a/internal/cache/gts.go
+++ b/internal/cache/gts.go
@@ -25,240 +25,221 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
-type GTSCaches interface {
- // Init will initialize all the gtsmodel caches in this collection.
- // NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
- Init()
-
- // Start will attempt to start all of the gtsmodel caches, or panic.
- Start()
-
- // Stop will attempt to stop all of the gtsmodel caches, or panic.
- Stop()
-
- // Account provides access to the gtsmodel Account database cache.
- Account() *result.Cache[*gtsmodel.Account]
-
- // Block provides access to the gtsmodel Block (account) database cache.
- Block() *result.Cache[*gtsmodel.Block]
-
- // DomainBlock provides access to the domain block database cache.
- DomainBlock() *domain.BlockCache
-
- // Emoji provides access to the gtsmodel Emoji database cache.
- Emoji() *result.Cache[*gtsmodel.Emoji]
-
- // EmojiCategory provides access to the gtsmodel EmojiCategory database cache.
- EmojiCategory() *result.Cache[*gtsmodel.EmojiCategory]
-
- // Mention provides access to the gtsmodel Mention database cache.
- Mention() *result.Cache[*gtsmodel.Mention]
-
- // Media provides access to the gtsmodel Media database cache.
- Media() *result.Cache[*gtsmodel.MediaAttachment]
-
- // Notification provides access to the gtsmodel Notification database cache.
- Notification() *result.Cache[*gtsmodel.Notification]
-
- // Report provides access to the gtsmodel Report database cache.
- Report() *result.Cache[*gtsmodel.Report]
-
- // Status provides access to the gtsmodel Status database cache.
- Status() *result.Cache[*gtsmodel.Status]
-
- // Tombstone provides access to the gtsmodel Tombstone database cache.
- Tombstone() *result.Cache[*gtsmodel.Tombstone]
-
- // User provides access to the gtsmodel User database cache.
- User() *result.Cache[*gtsmodel.User]
-
- // Webfinger
- Webfinger() *ttl.Cache[string, string]
-}
-
-// NewGTS returns a new default implementation of GTSCaches.
-func NewGTS() GTSCaches {
- return &gtsCaches{}
-}
-
-type gtsCaches struct {
- account *result.Cache[*gtsmodel.Account]
- block *result.Cache[*gtsmodel.Block]
+type GTSCaches struct {
+ account *result.Cache[*gtsmodel.Account]
+ block *result.Cache[*gtsmodel.Block]
+ // TODO: maybe should be moved out of here since it's
+ // not actually doing anything with gtsmodel.DomainBlock.
domainBlock *domain.BlockCache
emoji *result.Cache[*gtsmodel.Emoji]
emojiCategory *result.Cache[*gtsmodel.EmojiCategory]
+ follow *result.Cache[*gtsmodel.Follow]
+ followRequest *result.Cache[*gtsmodel.FollowRequest]
media *result.Cache[*gtsmodel.MediaAttachment]
mention *result.Cache[*gtsmodel.Mention]
notification *result.Cache[*gtsmodel.Notification]
report *result.Cache[*gtsmodel.Report]
status *result.Cache[*gtsmodel.Status]
+ statusFave *result.Cache[*gtsmodel.StatusFave]
tombstone *result.Cache[*gtsmodel.Tombstone]
user *result.Cache[*gtsmodel.User]
- webfinger *ttl.Cache[string, string]
+ // TODO: move out of GTS caches since not using database models.
+ webfinger *ttl.Cache[string, string]
}
-func (c *gtsCaches) Init() {
+// Init will initialize all the gtsmodel caches in this collection.
+// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
+func (c *GTSCaches) Init() {
c.initAccount()
c.initBlock()
c.initDomainBlock()
c.initEmoji()
c.initEmojiCategory()
+ c.initFollow()
+ c.initFollowRequest()
c.initMedia()
c.initMention()
c.initNotification()
c.initReport()
c.initStatus()
+ c.initStatusFave()
c.initTombstone()
c.initUser()
c.initWebfinger()
}
-func (c *gtsCaches) Start() {
- tryUntil("starting gtsmodel.Account cache", 5, func() bool {
- return c.account.Start(config.GetCacheGTSAccountSweepFreq())
- })
- tryUntil("starting gtsmodel.Block cache", 5, func() bool {
- return c.block.Start(config.GetCacheGTSBlockSweepFreq())
- })
- tryUntil("starting gtsmodel.DomainBlock cache", 5, func() bool {
- return c.domainBlock.Start(config.GetCacheGTSDomainBlockSweepFreq())
- })
- tryUntil("starting gtsmodel.Emoji cache", 5, func() bool {
- return c.emoji.Start(config.GetCacheGTSEmojiSweepFreq())
- })
- tryUntil("starting gtsmodel.EmojiCategory cache", 5, func() bool {
- return c.emojiCategory.Start(config.GetCacheGTSEmojiCategorySweepFreq())
- })
- tryUntil("starting gtsmodel.MediaAttachment cache", 5, func() bool {
- return c.media.Start(config.GetCacheGTSMediaSweepFreq())
- })
- tryUntil("starting gtsmodel.Mention cache", 5, func() bool {
- return c.mention.Start(config.GetCacheGTSMentionSweepFreq())
- })
- tryUntil("starting gtsmodel.Notification cache", 5, func() bool {
- return c.notification.Start(config.GetCacheGTSNotificationSweepFreq())
- })
- tryUntil("starting gtsmodel.Report cache", 5, func() bool {
- return c.report.Start(config.GetCacheGTSReportSweepFreq())
+// Start will attempt to start all of the gtsmodel caches, or panic.
+func (c *GTSCaches) Start() {
+ tryStart(c.account, config.GetCacheGTSAccountSweepFreq())
+ tryStart(c.block, config.GetCacheGTSBlockSweepFreq())
+ tryUntil("starting domain block cache", 5, func() bool {
+ if sweep := config.GetCacheGTSDomainBlockSweepFreq(); sweep > 0 {
+ return c.domainBlock.Start(sweep)
+ }
+ return true
})
- tryUntil("starting gtsmodel.Status cache", 5, func() bool {
- return c.status.Start(config.GetCacheGTSStatusSweepFreq())
- })
- tryUntil("starting gtsmodel.Tombstone cache", 5, func() bool {
- return c.tombstone.Start(config.GetCacheGTSTombstoneSweepFreq())
- })
- tryUntil("starting gtsmodel.User cache", 5, func() bool {
- return c.user.Start(config.GetCacheGTSUserSweepFreq())
- })
- tryUntil("starting gtsmodel.Webfinger cache", 5, func() bool {
- return c.webfinger.Start(config.GetCacheGTSWebfingerSweepFreq())
+ tryStart(c.emoji, config.GetCacheGTSEmojiSweepFreq())
+ tryStart(c.emojiCategory, config.GetCacheGTSEmojiCategorySweepFreq())
+ tryStart(c.follow, config.GetCacheGTSFollowSweepFreq())
+ tryStart(c.followRequest, config.GetCacheGTSFollowRequestSweepFreq())
+ tryStart(c.media, config.GetCacheGTSMediaSweepFreq())
+ tryStart(c.mention, config.GetCacheGTSMentionSweepFreq())
+ tryStart(c.notification, config.GetCacheGTSNotificationSweepFreq())
+ tryStart(c.report, config.GetCacheGTSReportSweepFreq())
+ tryStart(c.status, config.GetCacheGTSStatusSweepFreq())
+ tryStart(c.statusFave, config.GetCacheGTSStatusFaveSweepFreq())
+ tryStart(c.tombstone, config.GetCacheGTSTombstoneSweepFreq())
+ tryStart(c.user, config.GetCacheGTSUserSweepFreq())
+ tryUntil("starting *gtsmodel.Webfinger cache", 5, func() bool {
+ if sweep := config.GetCacheGTSWebfingerSweepFreq(); sweep > 0 {
+ return c.webfinger.Start(sweep)
+ }
+ return true
})
}
-func (c *gtsCaches) Stop() {
- tryUntil("stopping gtsmodel.Account cache", 5, c.account.Stop)
- tryUntil("stopping gtsmodel.Block cache", 5, c.block.Stop)
- tryUntil("stopping gtsmodel.DomainBlock cache", 5, c.domainBlock.Stop)
- tryUntil("stopping gtsmodel.Emoji cache", 5, c.emoji.Stop)
- tryUntil("stopping gtsmodel.EmojiCategory cache", 5, c.emojiCategory.Stop)
- tryUntil("stopping gtsmodel.MediaAttachment cache", 5, c.media.Stop)
- tryUntil("stopping gtsmodel.Mention cache", 5, c.mention.Stop)
- tryUntil("stopping gtsmodel.Notification cache", 5, c.notification.Stop)
- tryUntil("stopping gtsmodel.Report cache", 5, c.report.Stop)
- tryUntil("stopping gtsmodel.Status cache", 5, c.status.Stop)
- tryUntil("stopping gtsmodel.Tombstone cache", 5, c.tombstone.Stop)
- tryUntil("stopping gtsmodel.User cache", 5, c.user.Stop)
- tryUntil("stopping gtsmodel.Webfinger cache", 5, c.webfinger.Stop)
-}
-
-func (c *gtsCaches) Account() *result.Cache[*gtsmodel.Account] {
+// Stop will attempt to stop all of the gtsmodel caches, or panic.
+func (c *GTSCaches) Stop() {
+ tryStop(c.account, config.GetCacheGTSAccountSweepFreq())
+ tryStop(c.block, config.GetCacheGTSBlockSweepFreq())
+ tryUntil("stopping domain block cache", 5, c.domainBlock.Stop)
+ tryStop(c.emoji, config.GetCacheGTSEmojiSweepFreq())
+ tryStop(c.emojiCategory, config.GetCacheGTSEmojiCategorySweepFreq())
+ tryStop(c.follow, config.GetCacheGTSFollowSweepFreq())
+ tryStop(c.followRequest, config.GetCacheGTSFollowRequestSweepFreq())
+ tryStop(c.media, config.GetCacheGTSMediaSweepFreq())
+ tryStop(c.mention, config.GetCacheGTSNotificationSweepFreq())
+ tryStop(c.notification, config.GetCacheGTSNotificationSweepFreq())
+ tryStop(c.report, config.GetCacheGTSReportSweepFreq())
+ tryStop(c.status, config.GetCacheGTSStatusSweepFreq())
+ tryStop(c.statusFave, config.GetCacheGTSStatusFaveSweepFreq())
+ tryStop(c.tombstone, config.GetCacheGTSTombstoneSweepFreq())
+ tryStop(c.user, config.GetCacheGTSUserSweepFreq())
+ tryUntil("stopping *gtsmodel.Webfinger cache", 5, c.webfinger.Stop)
+}
+
+// Account provides access to the gtsmodel Account database cache.
+func (c *GTSCaches) Account() *result.Cache[*gtsmodel.Account] {
return c.account
}
-func (c *gtsCaches) Block() *result.Cache[*gtsmodel.Block] {
+// Block provides access to the gtsmodel Block (account) database cache.
+func (c *GTSCaches) Block() *result.Cache[*gtsmodel.Block] {
return c.block
}
-func (c *gtsCaches) DomainBlock() *domain.BlockCache {
+// DomainBlock provides access to the domain block database cache.
+func (c *GTSCaches) DomainBlock() *domain.BlockCache {
return c.domainBlock
}
-func (c *gtsCaches) Emoji() *result.Cache[*gtsmodel.Emoji] {
+// Emoji provides access to the gtsmodel Emoji database cache.
+func (c *GTSCaches) Emoji() *result.Cache[*gtsmodel.Emoji] {
return c.emoji
}
-func (c *gtsCaches) EmojiCategory() *result.Cache[*gtsmodel.EmojiCategory] {
+// EmojiCategory provides access to the gtsmodel EmojiCategory database cache.
+func (c *GTSCaches) EmojiCategory() *result.Cache[*gtsmodel.EmojiCategory] {
return c.emojiCategory
}
-func (c *gtsCaches) Media() *result.Cache[*gtsmodel.MediaAttachment] {
+// Follow provides access to the gtsmodel Follow database cache.
+func (c *GTSCaches) Follow() *result.Cache[*gtsmodel.Follow] {
+ return c.follow
+}
+
+// FollowRequest provides access to the gtsmodel FollowRequest database cache.
+func (c *GTSCaches) FollowRequest() *result.Cache[*gtsmodel.FollowRequest] {
+ return c.followRequest
+}
+
+// Media provides access to the gtsmodel Media database cache.
+func (c *GTSCaches) Media() *result.Cache[*gtsmodel.MediaAttachment] {
return c.media
}
-func (c *gtsCaches) Mention() *result.Cache[*gtsmodel.Mention] {
+// Mention provides access to the gtsmodel Mention database cache.
+func (c *GTSCaches) Mention() *result.Cache[*gtsmodel.Mention] {
return c.mention
}
-func (c *gtsCaches) Notification() *result.Cache[*gtsmodel.Notification] {
+// Notification provides access to the gtsmodel Notification database cache.
+func (c *GTSCaches) Notification() *result.Cache[*gtsmodel.Notification] {
return c.notification
}
-func (c *gtsCaches) Report() *result.Cache[*gtsmodel.Report] {
+// Report provides access to the gtsmodel Report database cache.
+func (c *GTSCaches) Report() *result.Cache[*gtsmodel.Report] {
return c.report
}
-func (c *gtsCaches) Status() *result.Cache[*gtsmodel.Status] {
+// Status provides access to the gtsmodel Status database cache.
+func (c *GTSCaches) Status() *result.Cache[*gtsmodel.Status] {
return c.status
}
-func (c *gtsCaches) Tombstone() *result.Cache[*gtsmodel.Tombstone] {
+// StatusFave provides access to the gtsmodel StatusFave database cache.
+func (c *GTSCaches) StatusFave() *result.Cache[*gtsmodel.StatusFave] {
+ return c.statusFave
+}
+
+// Tombstone provides access to the gtsmodel Tombstone database cache.
+func (c *GTSCaches) Tombstone() *result.Cache[*gtsmodel.Tombstone] {
return c.tombstone
}
-func (c *gtsCaches) User() *result.Cache[*gtsmodel.User] {
+// User provides access to the gtsmodel User database cache.
+func (c *GTSCaches) User() *result.Cache[*gtsmodel.User] {
return c.user
}
-func (c *gtsCaches) Webfinger() *ttl.Cache[string, string] {
+// Webfinger provides access to the webfinger URL cache.
+func (c *GTSCaches) Webfinger() *ttl.Cache[string, string] {
return c.webfinger
}
-func (c *gtsCaches) initAccount() {
+func (c *GTSCaches) initAccount() {
c.account = result.New([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "URL"},
{Name: "Username.Domain"},
{Name: "PublicKeyURI"},
+ {Name: "InboxURI"},
+ {Name: "OutboxURI"},
+ {Name: "FollowersURI"},
+ {Name: "FollowingURI"},
}, func(a1 *gtsmodel.Account) *gtsmodel.Account {
a2 := new(gtsmodel.Account)
*a2 = *a1
return a2
}, config.GetCacheGTSAccountMaxSize())
c.account.SetTTL(config.GetCacheGTSAccountTTL(), true)
+ c.account.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initBlock() {
+func (c *GTSCaches) initBlock() {
c.block = result.New([]result.Lookup{
{Name: "ID"},
- {Name: "AccountID.TargetAccountID"},
{Name: "URI"},
+ {Name: "AccountID.TargetAccountID"},
}, func(b1 *gtsmodel.Block) *gtsmodel.Block {
b2 := new(gtsmodel.Block)
*b2 = *b1
return b2
}, config.GetCacheGTSBlockMaxSize())
c.block.SetTTL(config.GetCacheGTSBlockTTL(), true)
+ c.block.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initDomainBlock() {
+func (c *GTSCaches) initDomainBlock() {
c.domainBlock = domain.New(
config.GetCacheGTSDomainBlockMaxSize(),
config.GetCacheGTSDomainBlockTTL(),
)
}
-func (c *gtsCaches) initEmoji() {
+func (c *GTSCaches) initEmoji() {
c.emoji = result.New([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
@@ -270,9 +251,10 @@ func (c *gtsCaches) initEmoji() {
return e2
}, config.GetCacheGTSEmojiMaxSize())
c.emoji.SetTTL(config.GetCacheGTSEmojiTTL(), true)
+ c.emoji.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initEmojiCategory() {
+func (c *GTSCaches) initEmojiCategory() {
c.emojiCategory = result.New([]result.Lookup{
{Name: "ID"},
{Name: "Name"},
@@ -282,9 +264,36 @@ func (c *gtsCaches) initEmojiCategory() {
return c2
}, config.GetCacheGTSEmojiCategoryMaxSize())
c.emojiCategory.SetTTL(config.GetCacheGTSEmojiCategoryTTL(), true)
+ c.emojiCategory.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initMedia() {
+func (c *GTSCaches) initFollow() {
+ c.follow = result.New([]result.Lookup{
+ {Name: "ID"},
+ {Name: "URI"},
+ {Name: "AccountID.TargetAccountID"},
+ }, func(f1 *gtsmodel.Follow) *gtsmodel.Follow {
+ f2 := new(gtsmodel.Follow)
+ *f2 = *f1
+ return f2
+ }, config.GetCacheGTSFollowMaxSize())
+ c.follow.SetTTL(config.GetCacheGTSFollowTTL(), true)
+}
+
+func (c *GTSCaches) initFollowRequest() {
+ c.followRequest = result.New([]result.Lookup{
+ {Name: "ID"},
+ {Name: "URI"},
+ {Name: "AccountID.TargetAccountID"},
+ }, func(f1 *gtsmodel.FollowRequest) *gtsmodel.FollowRequest {
+ f2 := new(gtsmodel.FollowRequest)
+ *f2 = *f1
+ return f2
+ }, config.GetCacheGTSFollowRequestMaxSize())
+ c.followRequest.SetTTL(config.GetCacheGTSFollowRequestTTL(), true)
+}
+
+func (c *GTSCaches) initMedia() {
c.media = result.New([]result.Lookup{
{Name: "ID"},
}, func(m1 *gtsmodel.MediaAttachment) *gtsmodel.MediaAttachment {
@@ -293,9 +302,10 @@ func (c *gtsCaches) initMedia() {
return m2
}, config.GetCacheGTSMediaMaxSize())
c.media.SetTTL(config.GetCacheGTSMediaTTL(), true)
+ c.media.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initMention() {
+func (c *GTSCaches) initMention() {
c.mention = result.New([]result.Lookup{
{Name: "ID"},
}, func(m1 *gtsmodel.Mention) *gtsmodel.Mention {
@@ -304,9 +314,10 @@ func (c *gtsCaches) initMention() {
return m2
}, config.GetCacheGTSMentionMaxSize())
c.mention.SetTTL(config.GetCacheGTSMentionTTL(), true)
+ c.mention.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initNotification() {
+func (c *GTSCaches) initNotification() {
c.notification = result.New([]result.Lookup{
{Name: "ID"},
}, func(n1 *gtsmodel.Notification) *gtsmodel.Notification {
@@ -315,9 +326,10 @@ func (c *gtsCaches) initNotification() {
return n2
}, config.GetCacheGTSNotificationMaxSize())
c.notification.SetTTL(config.GetCacheGTSNotificationTTL(), true)
+ c.notification.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initReport() {
+func (c *GTSCaches) initReport() {
c.report = result.New([]result.Lookup{
{Name: "ID"},
}, func(r1 *gtsmodel.Report) *gtsmodel.Report {
@@ -326,9 +338,10 @@ func (c *gtsCaches) initReport() {
return r2
}, config.GetCacheGTSReportMaxSize())
c.report.SetTTL(config.GetCacheGTSReportTTL(), true)
+ c.report.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initStatus() {
+func (c *GTSCaches) initStatus() {
c.status = result.New([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
@@ -339,10 +352,24 @@ func (c *gtsCaches) initStatus() {
return s2
}, config.GetCacheGTSStatusMaxSize())
c.status.SetTTL(config.GetCacheGTSStatusTTL(), true)
+ c.status.IgnoreErrors(ignoreErrors)
+}
+
+func (c *GTSCaches) initStatusFave() {
+ c.statusFave = result.New([]result.Lookup{
+ {Name: "ID"},
+ {Name: "AccountID.StatusID"},
+ }, func(f1 *gtsmodel.StatusFave) *gtsmodel.StatusFave {
+ f2 := new(gtsmodel.StatusFave)
+ *f2 = *f1
+ return f2
+ }, config.GetCacheGTSStatusFaveMaxSize())
+ c.status.SetTTL(config.GetCacheGTSStatusFaveTTL(), true)
+ c.status.IgnoreErrors(ignoreErrors)
}
// initTombstone will initialize the gtsmodel.Tombstone cache.
-func (c *gtsCaches) initTombstone() {
+func (c *GTSCaches) initTombstone() {
c.tombstone = result.New([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
@@ -352,9 +379,10 @@ func (c *gtsCaches) initTombstone() {
return t2
}, config.GetCacheGTSTombstoneMaxSize())
c.tombstone.SetTTL(config.GetCacheGTSTombstoneTTL(), true)
+ c.tombstone.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initUser() {
+func (c *GTSCaches) initUser() {
c.user = result.New([]result.Lookup{
{Name: "ID"},
{Name: "AccountID"},
@@ -367,9 +395,10 @@ func (c *gtsCaches) initUser() {
return u2
}, config.GetCacheGTSUserMaxSize())
c.user.SetTTL(config.GetCacheGTSUserTTL(), true)
+ c.user.IgnoreErrors(ignoreErrors)
}
-func (c *gtsCaches) initWebfinger() {
+func (c *GTSCaches) initWebfinger() {
c.webfinger = ttl.New[string, string](
0,
config.GetCacheGTSWebfingerMaxSize(),
diff --git a/internal/cache/util.go b/internal/cache/util.go
index 066e477e9..1ffd72876 100644
--- a/internal/cache/util.go
+++ b/internal/cache/util.go
@@ -17,7 +17,30 @@
package cache
-import "github.com/superseriousbusiness/gotosocial/internal/log"
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "codeberg.org/gruf/go-cache/v3/result"
+ errorsv2 "codeberg.org/gruf/go-errors/v2"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+)
+
+// SentinelError is returned to indicate a non-permanent error return,
+// i.e. a situation in which we do not want a cache a negative result.
+var SentinelError = errors.New("BUG: error should not be returned") //nolint:revive
+
+// ignoreErrors is an error ignoring function capable of being passed to
+// caches, which specifically catches and ignores our sentinel error type.
+func ignoreErrors(err error) bool {
+ return errorsv2.Is(
+ SentinelError,
+ context.DeadlineExceeded,
+ context.Canceled,
+ )
+}
// nocopy when embedded will signal linter to
// error on pass-by-value of parent struct.
@@ -27,6 +50,26 @@ func (*nocopy) Lock() {}
func (*nocopy) Unlock() {}
+// tryStart will attempt to start the given cache only if sweep duration > 0 (sweeping is enabled).
+func tryStart[ValueType any](cache *result.Cache[ValueType], sweep time.Duration) {
+ if sweep > 0 {
+ var z ValueType
+ msg := fmt.Sprintf("starting %T cache", z)
+ tryUntil(msg, 5, func() bool {
+ return cache.Start(sweep)
+ })
+ }
+}
+
+// tryStop will attempt to stop the given cache only if sweep duration > 0 (sweeping is enabled).
+func tryStop[ValueType any](cache *result.Cache[ValueType], sweep time.Duration) {
+ if sweep > 0 {
+ var z ValueType
+ msg := fmt.Sprintf("stopping %T cache", z)
+ tryUntil(msg, 5, cache.Stop)
+ }
+}
+
// tryUntil will attempt to call 'do' for 'count' attempts, before panicking with 'msg'.
func tryUntil(msg string, count int, do func() bool) {
for i := 0; i < count; i++ {
diff --git a/internal/cache/visibility.go b/internal/cache/visibility.go
new file mode 100644
index 000000000..8706a8015
--- /dev/null
+++ b/internal/cache/visibility.go
@@ -0,0 +1,81 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package cache
+
+import (
+ "codeberg.org/gruf/go-cache/v3/result"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+)
+
+type VisibilityCache struct {
+ *result.Cache[*CachedVisibility]
+}
+
+// Init will initialize the visibility cache in this collection.
+// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
+func (c *VisibilityCache) Init() {
+ c.Cache = result.New([]result.Lookup{
+ {Name: "ItemID"},
+ {Name: "RequesterID"},
+ {Name: "Type.RequesterID.ItemID"},
+ }, func(v1 *CachedVisibility) *CachedVisibility {
+ v2 := new(CachedVisibility)
+ *v2 = *v1
+ return v2
+ }, config.GetCacheVisibilityMaxSize())
+ c.Cache.SetTTL(config.GetCacheVisibilityTTL(), true)
+ c.Cache.IgnoreErrors(ignoreErrors)
+}
+
+// Start will attempt to start the visibility cache, or panic.
+func (c *VisibilityCache) Start() {
+ tryStart(c.Cache, config.GetCacheVisibilitySweepFreq())
+}
+
+// Stop will attempt to stop the visibility cache, or panic.
+func (c *VisibilityCache) Stop() {
+ tryStop(c.Cache, config.GetCacheVisibilitySweepFreq())
+}
+
+// VisibilityType represents a visibility lookup type.
+// We use a byte type here to improve performance in the
+// result cache when generating the key.
+type VisibilityType byte
+
+const (
+ // Possible cache visibility lookup types.
+ VisibilityTypeAccount = VisibilityType('a')
+ VisibilityTypeStatus = VisibilityType('s')
+ VisibilityTypeHome = VisibilityType('h')
+ VisibilityTypePublic = VisibilityType('p')
+)
+
+// CachedVisibility represents a cached visibility lookup value.
+type CachedVisibility struct {
+ // ItemID is the ID of the item in question (status / account).
+ ItemID string
+
+ // RequesterID is the ID of the requesting account for this visibility lookup.
+ RequesterID string
+
+ // Type is the visibility lookup type.
+ Type VisibilityType
+
+ // Value is the actual visibility value.
+ Value bool
+}