summaryrefslogtreecommitdiff
path: root/internal/cache
diff options
context:
space:
mode:
Diffstat (limited to 'internal/cache')
-rw-r--r--internal/cache/cache.go5
-rw-r--r--internal/cache/db.go7
-rw-r--r--internal/cache/invalidate.go86
-rw-r--r--internal/cache/mutes.go109
-rw-r--r--internal/cache/size.go73
-rw-r--r--internal/cache/visibility.go12
6 files changed, 209 insertions, 83 deletions
diff --git a/internal/cache/cache.go b/internal/cache/cache.go
index d05b85a15..54777441f 100644
--- a/internal/cache/cache.go
+++ b/internal/cache/cache.go
@@ -49,6 +49,10 @@ type Caches struct {
// Timelines ...
Timelines TimelineCaches
+ // Mutes provides access to the item mutes
+ // cache. (used by the item mutes filter).
+ Mutes MutesCache
+
// Visibility provides access to the item visibility
// cache. (used by the visibility filter).
Visibility VisibilityCache
@@ -125,6 +129,7 @@ func (c *Caches) Init() {
c.initWebfinger()
c.initWebPushSubscription()
c.initWebPushSubscriptionIDs()
+ c.initMutes()
c.initVisibility()
}
diff --git a/internal/cache/db.go b/internal/cache/db.go
index 78cc01e06..5592ca493 100644
--- a/internal/cache/db.go
+++ b/internal/cache/db.go
@@ -1531,9 +1531,10 @@ func (c *Caches) initThreadMute() {
{Fields: "AccountID", Multiple: true},
{Fields: "ThreadID,AccountID"},
},
- MaxSize: cap,
- IgnoreErr: ignoreErrors,
- Copy: copyF,
+ MaxSize: cap,
+ IgnoreErr: ignoreErrors,
+ Copy: copyF,
+ Invalidate: c.OnInvalidateThreadMute,
})
}
diff --git a/internal/cache/invalidate.go b/internal/cache/invalidate.go
index 3512bb51e..88b7415ae 100644
--- a/internal/cache/invalidate.go
+++ b/internal/cache/invalidate.go
@@ -28,12 +28,18 @@ import (
// HOOKS TO BE CALLED ON DELETE YOU MUST FIRST POPULATE IT IN THE CACHE.
func (c *Caches) OnInvalidateAccount(account *gtsmodel.Account) {
- // Invalidate stats for this account.
+ // Invalidate cached stats objects for this account.
c.DB.AccountStats.Invalidate("AccountID", account.ID)
- // Invalidate account ID cached visibility.
+ // Invalidate as possible visibility target result.
c.Visibility.Invalidate("ItemID", account.ID)
- c.Visibility.Invalidate("RequesterID", account.ID)
+
+ // If account is local, invalidate as
+ // possible mute / visibility result requester.
+ if account.IsLocal() {
+ c.Visibility.Invalidate("RequesterID", account.ID)
+ c.Mutes.Invalidate("RequesterID", account.ID)
+ }
// Invalidate this account's
// following / follower lists.
@@ -66,13 +72,31 @@ func (c *Caches) OnInvalidateApplication(app *gtsmodel.Application) {
}
func (c *Caches) OnInvalidateBlock(block *gtsmodel.Block) {
- // Invalidate block origin account ID cached visibility.
- c.Visibility.Invalidate("ItemID", block.AccountID)
- c.Visibility.Invalidate("RequesterID", block.AccountID)
+ // Invalidate both block origin and target as
+ // possible lookup targets for visibility results.
+ c.Visibility.InvalidateIDs("ItemID", []string{
+ block.TargetAccountID,
+ block.AccountID,
+ })
+
+ // Track which of block / target are local.
+ localAccountIDs := make([]string, 0, 2)
+
+ // If origin is local (or uncertain), also invalidate
+ // results for them as mute / visibility result requester.
+ if block.Account == nil || block.Account.IsLocal() {
+ localAccountIDs = append(localAccountIDs, block.AccountID)
+ }
- // Invalidate block target account ID cached visibility.
- c.Visibility.Invalidate("ItemID", block.TargetAccountID)
- c.Visibility.Invalidate("RequesterID", block.TargetAccountID)
+ // If target is local (or uncertain), also invalidate
+ // results for them as mute / visibility result requester.
+ if block.TargetAccount == nil || block.TargetAccount.IsLocal() {
+ localAccountIDs = append(localAccountIDs, block.TargetAccountID)
+ }
+
+ // Now perform local mute / visibility result invalidations.
+ c.Visibility.InvalidateIDs("RequesterID", localAccountIDs)
+ c.Mutes.InvalidateIDs("RequesterID", localAccountIDs)
// Invalidate source account's block lists.
c.DB.BlockIDs.Invalidate(block.AccountID)
@@ -92,13 +116,31 @@ func (c *Caches) OnInvalidateFollow(follow *gtsmodel.Follow) {
// Invalidate follow request with this same ID.
c.DB.FollowRequest.Invalidate("ID", follow.ID)
- // Invalidate follow origin account ID cached visibility.
- c.Visibility.Invalidate("ItemID", follow.AccountID)
- c.Visibility.Invalidate("RequesterID", follow.AccountID)
+ // Invalidate both follow origin and target as
+ // possible lookup targets for visibility results.
+ c.Visibility.InvalidateIDs("ItemID", []string{
+ follow.TargetAccountID,
+ follow.AccountID,
+ })
+
+ // Track which of follow / target are local.
+ localAccountIDs := make([]string, 0, 2)
+
+ // If origin is local (or uncertain), also invalidate
+ // results for them as mute / visibility result requester.
+ if follow.Account == nil || follow.Account.IsLocal() {
+ localAccountIDs = append(localAccountIDs, follow.AccountID)
+ }
+
+ // If target is local (or uncertain), also invalidate
+ // results for them as mute / visibility result requester.
+ if follow.TargetAccount == nil || follow.TargetAccount.IsLocal() {
+ localAccountIDs = append(localAccountIDs, follow.TargetAccountID)
+ }
- // Invalidate follow target account ID cached visibility.
- c.Visibility.Invalidate("ItemID", follow.TargetAccountID)
- c.Visibility.Invalidate("RequesterID", follow.TargetAccountID)
+ // Now perform local mute / visibility result invalidations.
+ c.Visibility.InvalidateIDs("RequesterID", localAccountIDs)
+ c.Mutes.InvalidateIDs("RequesterID", localAccountIDs)
// Invalidate ID slice cache.
c.DB.FollowIDs.Invalidate(
@@ -227,12 +269,16 @@ func (c *Caches) OnInvalidatePollVote(vote *gtsmodel.PollVote) {
}
func (c *Caches) OnInvalidateStatus(status *gtsmodel.Status) {
- // Invalidate stats for this account.
+ // Invalidate cached stats objects for this account.
c.DB.AccountStats.Invalidate("AccountID", status.AccountID)
// Invalidate status ID cached visibility.
c.Visibility.Invalidate("ItemID", status.ID)
+ // Invalidate mute results involving status.
+ c.Mutes.Invalidate("StatusID", status.ID)
+ c.Mutes.Invalidate("ThreadID", status.ThreadID)
+
// Invalidate each media by the IDs we're aware of.
// This must be done as the status table is aware of
// the media IDs in use before the media table is
@@ -277,6 +323,11 @@ func (c *Caches) OnInvalidateStatusFave(fave *gtsmodel.StatusFave) {
c.DB.StatusFaveIDs.Invalidate(fave.StatusID)
}
+func (c *Caches) OnInvalidateThreadMute(mute *gtsmodel.ThreadMute) {
+ // Invalidate cached mute ressults encapsulating this thread and account.
+ c.Mutes.Invalidate("RequesterID,ThreadID", mute.AccountID, mute.ThreadID)
+}
+
func (c *Caches) OnInvalidateToken(token *gtsmodel.Token) {
// Invalidate token's push subscription.
c.DB.WebPushSubscription.Invalidate("ID", token.ID)
@@ -294,6 +345,9 @@ func (c *Caches) OnInvalidateUser(user *gtsmodel.User) {
func (c *Caches) OnInvalidateUserMute(mute *gtsmodel.UserMute) {
// Invalidate source account's user mute lists.
c.DB.UserMuteIDs.Invalidate(mute.AccountID)
+
+ // Invalidate source account's cached mute results.
+ c.Mutes.Invalidate("RequesterID", mute.AccountID)
}
func (c *Caches) OnInvalidateWebPushSubscription(subscription *gtsmodel.WebPushSubscription) {
diff --git a/internal/cache/mutes.go b/internal/cache/mutes.go
new file mode 100644
index 000000000..9ad7736a0
--- /dev/null
+++ b/internal/cache/mutes.go
@@ -0,0 +1,109 @@
+// 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 (
+ "time"
+
+ "code.superseriousbusiness.org/gotosocial/internal/config"
+ "code.superseriousbusiness.org/gotosocial/internal/log"
+ "codeberg.org/gruf/go-structr"
+)
+
+type MutesCache struct {
+ StructCache[*CachedMute]
+}
+
+func (c *Caches) initMutes() {
+ // Calculate maximum cache size.
+ cap := calculateResultCacheMax(
+ sizeofMute(), // model in-mem size.
+ config.GetCacheMutesMemRatio(),
+ )
+
+ log.Infof(nil, "cache size = %d", cap)
+
+ copyF := func(m1 *CachedMute) *CachedMute {
+ m2 := new(CachedMute)
+ *m2 = *m1
+ return m2
+ }
+
+ c.Mutes.Init(structr.CacheConfig[*CachedMute]{
+ Indices: []structr.IndexConfig{
+ {Fields: "RequesterID,StatusID"},
+ {Fields: "RequesterID,ThreadID", Multiple: true},
+ {Fields: "StatusID", Multiple: true},
+ {Fields: "ThreadID", Multiple: true},
+ {Fields: "RequesterID", Multiple: true},
+ },
+ MaxSize: cap,
+ IgnoreErr: func(err error) bool {
+ // don't cache any errors,
+ // it gets a little too tricky
+ // otherwise with ensuring
+ // errors are cleared out
+ return true
+ },
+ Copy: copyF,
+ })
+}
+
+// CachedMute contains the details
+// of a cached mute lookup.
+type CachedMute struct {
+
+ // StatusID is the ID of the
+ // status this is a result for.
+ StatusID string
+
+ // ThreadID is the ID of the
+ // thread status is a part of.
+ ThreadID string
+
+ // RequesterID is the ID of the requesting
+ // account for this user mute lookup.
+ RequesterID string
+
+ // Mute indicates whether ItemID
+ // is muted by RequesterID.
+ Mute bool
+
+ // MuteExpiry stores the time at which
+ // (if any) the stored mute value expires.
+ MuteExpiry time.Time
+
+ // Notifications indicates whether
+ // this mute should prevent notifications
+ // being shown for ItemID to RequesterID.
+ Notifications bool
+
+ // NotificationExpiry stores the time at which
+ // (if any) the stored notification value expires.
+ NotificationExpiry time.Time
+}
+
+// MuteExpired returns whether the mute value has expired.
+func (m *CachedMute) MuteExpired(now time.Time) bool {
+ return !m.MuteExpiry.IsZero() && !m.MuteExpiry.After(now)
+}
+
+// NotificationExpired returns whether the notification mute value has expired.
+func (m *CachedMute) NotificationExpired(now time.Time) bool {
+ return !m.NotificationExpiry.IsZero() && !m.NotificationExpiry.After(now)
+}
diff --git a/internal/cache/size.go b/internal/cache/size.go
index 7898f9dfd..ef9259f88 100644
--- a/internal/cache/size.go
+++ b/internal/cache/size.go
@@ -150,7 +150,7 @@ func calculateCacheMax(keySz, valSz uintptr, ratio float64) int {
// The inputted memory ratio does not take into account the
// total of all ratios, so divide it here to get perc. ratio.
- totalRatio := ratio / totalOfRatios()
+ totalRatio := ratio / config.GetTotalOfMemRatios()
// TODO: we should also further weight this ratio depending
// on the combined keySz + valSz as a ratio of all available
@@ -172,65 +172,6 @@ func calculateCacheMax(keySz, valSz uintptr, ratio float64) int {
return int(fMaxMem / (fKeySz + fValSz + emptyBucketOverhead + float64(cacheElemOverhead)))
}
-// totalOfRatios returns the total of all cache ratios added together.
-func totalOfRatios() float64 {
-
- // NOTE: this is not performant calculating
- // this every damn time (mainly the mutex unlocks
- // required to access each config var). fortunately
- // we only do this on init so fuck it :D
- return 0 +
- config.GetCacheAccountMemRatio() +
- config.GetCacheAccountNoteMemRatio() +
- config.GetCacheAccountSettingsMemRatio() +
- config.GetCacheAccountStatsMemRatio() +
- config.GetCacheApplicationMemRatio() +
- config.GetCacheBlockMemRatio() +
- config.GetCacheBlockIDsMemRatio() +
- config.GetCacheBoostOfIDsMemRatio() +
- config.GetCacheClientMemRatio() +
- config.GetCacheEmojiMemRatio() +
- config.GetCacheEmojiCategoryMemRatio() +
- config.GetCacheFilterMemRatio() +
- config.GetCacheFilterKeywordMemRatio() +
- config.GetCacheFilterStatusMemRatio() +
- config.GetCacheFollowMemRatio() +
- config.GetCacheFollowIDsMemRatio() +
- config.GetCacheFollowRequestMemRatio() +
- config.GetCacheFollowRequestIDsMemRatio() +
- config.GetCacheFollowingTagIDsMemRatio() +
- config.GetCacheInReplyToIDsMemRatio() +
- config.GetCacheInstanceMemRatio() +
- config.GetCacheInteractionRequestMemRatio() +
- config.GetCacheListMemRatio() +
- config.GetCacheListIDsMemRatio() +
- config.GetCacheListedIDsMemRatio() +
- config.GetCacheMarkerMemRatio() +
- config.GetCacheMediaMemRatio() +
- config.GetCacheMentionMemRatio() +
- config.GetCacheMoveMemRatio() +
- config.GetCacheNotificationMemRatio() +
- config.GetCachePollMemRatio() +
- config.GetCachePollVoteMemRatio() +
- config.GetCachePollVoteIDsMemRatio() +
- config.GetCacheReportMemRatio() +
- config.GetCacheSinBinStatusMemRatio() +
- config.GetCacheStatusMemRatio() +
- config.GetCacheStatusBookmarkMemRatio() +
- config.GetCacheStatusBookmarkIDsMemRatio() +
- config.GetCacheStatusFaveMemRatio() +
- config.GetCacheStatusFaveIDsMemRatio() +
- config.GetCacheTagMemRatio() +
- config.GetCacheThreadMuteMemRatio() +
- config.GetCacheTokenMemRatio() +
- config.GetCacheTombstoneMemRatio() +
- config.GetCacheUserMemRatio() +
- config.GetCacheUserMuteMemRatio() +
- config.GetCacheUserMuteIDsMemRatio() +
- config.GetCacheWebfingerMemRatio() +
- config.GetCacheVisibilityMemRatio()
-}
-
func sizeofAccount() uintptr {
return uintptr(size.Of(&gtsmodel.Account{
ID: exampleID,
@@ -769,6 +710,18 @@ func sizeofTombstone() uintptr {
}))
}
+func sizeofMute() uintptr {
+ return uintptr(size.Of(&CachedMute{
+ StatusID: exampleID,
+ ThreadID: exampleID,
+ RequesterID: exampleID,
+ Mute: true,
+ MuteExpiry: exampleTime,
+ Notifications: true,
+ NotificationExpiry: exampleTime,
+ }))
+}
+
func sizeofVisibility() uintptr {
return uintptr(size.Of(&CachedVisibility{
ItemID: exampleID,
diff --git a/internal/cache/visibility.go b/internal/cache/visibility.go
index 63927cf08..3797ab701 100644
--- a/internal/cache/visibility.go
+++ b/internal/cache/visibility.go
@@ -34,7 +34,7 @@ func (c *Caches) initVisibility() {
config.GetCacheVisibilityMemRatio(),
)
- log.Infof(nil, "Visibility cache size = %d", cap)
+ log.Infof(nil, "cache size = %d", cap)
copyF := func(v1 *CachedVisibility) *CachedVisibility {
v2 := new(CachedVisibility)
@@ -73,12 +73,16 @@ const (
VisibilityTypePublic = VisibilityType('p')
)
-// CachedVisibility represents a cached visibility lookup value.
+// CachedVisibility represents a
+// cached visibility lookup value.
type CachedVisibility struct {
- // ItemID is the ID of the item in question (status / account).
+
+ // 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 is the ID of the requesting
+ // account for this visibility lookup.
RequesterID string
// Type is the visibility lookup type.