[chore] move caches to a separate State{} structure (#1078)

* move caches to a separate State{} structure

Signed-off-by: kim <grufwub@gmail.com>

* fix call to log.Panic not using formatted call

Signed-off-by: kim <grufwub@gmail.com>

* move caches to use interfaces, to make switchouts easier in future

Signed-off-by: kim <grufwub@gmail.com>

* fix rebase issue

Signed-off-by: kim <grufwub@gmail.com>

* improve code comment

Signed-off-by: kim <grufwub@gmail.com>

* fix further issues after rebase

Signed-off-by: kim <grufwub@gmail.com>

* heh

Signed-off-by: kim <grufwub@gmail.com>

* add missing license text

Signed-off-by: kim <grufwub@gmail.com>

Signed-off-by: kim <grufwub@gmail.com>
This commit is contained in:
kim 2022-12-08 17:35:14 +00:00 committed by GitHub
parent dd1a4cd892
commit e58d2d8122
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 725 additions and 332 deletions

View file

@ -27,17 +27,24 @@
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/validate"
"golang.org/x/crypto/bcrypt"
)
// Create creates a new account in the database using the provided flags.
var Create action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx)
var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@ -88,11 +95,17 @@
// Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now.
var Confirm action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx)
var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@ -125,11 +138,17 @@
// Promote sets a user to admin.
var Promote action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx)
var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@ -159,11 +178,17 @@
// Demote sets admin on a user to false.
var Demote action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx)
var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@ -193,11 +218,17 @@
// Disable sets Disabled to true on a user.
var Disable action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx)
var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@ -227,11 +258,17 @@
// Password sets the password of target account.
var Password action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx)
var state state.State
state.Caches.Init()
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")

View file

@ -27,12 +27,16 @@
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/state"
gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"
)
// Orphaned prunes orphaned media from storage.
var Orphaned action.GTSAction = func(ctx context.Context) error {
dbService, err := bundb.NewBunDBService(ctx)
var state state.State
state.Caches.Init()
dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
@ -54,7 +58,7 @@
return fmt.Errorf("error pruning: %s", err)
}
if dry {
if dry /* dick heyyoooooo */ {
log.Infof("DRY RUN: %d stored items are orphaned and eligible to be pruned", pruned)
} else {
log.Infof("%d stored items were orphaned and pruned", pruned)

View file

@ -26,16 +26,22 @@
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/trans"
)
// Export exports info from the database into a file
var Export action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx)
var state state.State
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
exporter := trans.NewExporter(dbConn)
path := config.GetAdminTransPath()

View file

@ -26,16 +26,22 @@
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/trans"
)
// Import imports info from a file into the database
var Import action.GTSAction = func(ctx context.Context) error {
dbConn, err := bundb.NewBunDBService(ctx)
var state state.State
dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbConn
importer := trans.NewImporter(dbConn)
path := config.GetAdminTransPath()

View file

@ -65,6 +65,7 @@
"github.com/superseriousbusiness/gotosocial/internal/oidc"
"github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/router"
"github.com/superseriousbusiness/gotosocial/internal/state"
gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
@ -73,11 +74,20 @@
// Start creates and starts a gotosocial server
var Start action.GTSAction = func(ctx context.Context) error {
dbService, err := bundb.NewBunDBService(ctx)
var state state.State
// Initialize caches
state.Caches.Init()
// Open connection to the database
dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
// Set the state DB connection
state.DB = dbService
if err := dbService.CreateInstanceAccount(ctx); err != nil {
return fmt.Errorf("error creating instance account: %s", err)
}

44
internal/cache/ap.go vendored Normal file
View file

@ -0,0 +1,44 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
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
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()
// Start will attempt to start all of the ActivityPub caches, or panic.
Start()
// Stop will attempt to stop all of the ActivityPub caches, or panic.
Stop()
}
// 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() {}

60
internal/cache/cache.go vendored Normal file
View file

@ -0,0 +1,60 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
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
type Caches struct {
// GTS provides access to the collection of gtsmodel object caches.
GTS GTSCaches
// AP provides access to the collection of ActivityPub object caches.
AP APCaches
// prevent pass-by-value.
_ nocopy
}
// 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()
}
// Start will start both the GTS and AP cache collections.
func (c *Caches) Start() {
c.GTS.Start()
c.AP.Start()
}
// Stop will stop both the GTS and AP cache collections.
func (c *Caches) Stop() {
c.GTS.Stop()
c.AP.Stop()
}

313
internal/cache/gts.go vendored Normal file
View file

@ -0,0 +1,313 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
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"
"codeberg.org/gruf/go-cache/v3/result"
"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 gtsmodel DomainBlock database cache.
DomainBlock() *result.Cache[*gtsmodel.DomainBlock]
// 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]
// Notification provides access to the gtsmodel Notification database cache.
Notification() *result.Cache[*gtsmodel.Notification]
// 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]
}
// 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]
domainBlock *result.Cache[*gtsmodel.DomainBlock]
emoji *result.Cache[*gtsmodel.Emoji]
emojiCategory *result.Cache[*gtsmodel.EmojiCategory]
mention *result.Cache[*gtsmodel.Mention]
notification *result.Cache[*gtsmodel.Notification]
status *result.Cache[*gtsmodel.Status]
tombstone *result.Cache[*gtsmodel.Tombstone]
user *result.Cache[*gtsmodel.User]
}
func (c *gtsCaches) Init() {
c.initAccount()
c.initBlock()
c.initDomainBlock()
c.initEmoji()
c.initEmojiCategory()
c.initMention()
c.initNotification()
c.initStatus()
c.initTombstone()
c.initUser()
}
func (c *gtsCaches) Start() {
tryUntil("starting gtsmodel.Account cache", 5, func() bool {
return c.account.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Block cache", 5, func() bool {
return c.block.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.DomainBlock cache", 5, func() bool {
return c.domainBlock.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Emoji cache", 5, func() bool {
return c.emoji.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.EmojiCategory cache", 5, func() bool {
return c.emojiCategory.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Mention cache", 5, func() bool {
return c.mention.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Notification cache", 5, func() bool {
return c.notification.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Status cache", 5, func() bool {
return c.status.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.Tombstone cache", 5, func() bool {
return c.tombstone.Start(time.Second * 10)
})
tryUntil("starting gtsmodel.User cache", 5, func() bool {
return c.user.Start(time.Second * 10)
})
}
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.Mention cache", 5, c.mention.Stop)
tryUntil("stopping gtsmodel.Notification cache", 5, c.notification.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)
}
func (c *gtsCaches) Account() *result.Cache[*gtsmodel.Account] {
return c.account
}
func (c *gtsCaches) Block() *result.Cache[*gtsmodel.Block] {
return c.block
}
func (c *gtsCaches) DomainBlock() *result.Cache[*gtsmodel.DomainBlock] {
return c.domainBlock
}
func (c *gtsCaches) Emoji() *result.Cache[*gtsmodel.Emoji] {
return c.emoji
}
func (c *gtsCaches) EmojiCategory() *result.Cache[*gtsmodel.EmojiCategory] {
return c.emojiCategory
}
func (c *gtsCaches) Mention() *result.Cache[*gtsmodel.Mention] {
return c.mention
}
func (c *gtsCaches) Notification() *result.Cache[*gtsmodel.Notification] {
return c.notification
}
func (c *gtsCaches) Status() *result.Cache[*gtsmodel.Status] {
return c.status
}
func (c *gtsCaches) Tombstone() *result.Cache[*gtsmodel.Tombstone] {
return c.tombstone
}
func (c *gtsCaches) User() *result.Cache[*gtsmodel.User] {
return c.user
}
func (c *gtsCaches) initAccount() {
c.account = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "URL"},
{Name: "Username.Domain"},
{Name: "PublicKeyURI"},
}, func(a1 *gtsmodel.Account) *gtsmodel.Account {
a2 := new(gtsmodel.Account)
*a2 = *a1
return a2
}, 1000)
c.account.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initBlock() {
c.block = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "AccountID.TargetAccountID"},
{Name: "URI"},
}, func(b1 *gtsmodel.Block) *gtsmodel.Block {
b2 := new(gtsmodel.Block)
*b2 = *b1
return b2
}, 1000)
c.block.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initDomainBlock() {
c.domainBlock = result.NewSized([]result.Lookup{
{Name: "Domain"},
}, func(d1 *gtsmodel.DomainBlock) *gtsmodel.DomainBlock {
d2 := new(gtsmodel.DomainBlock)
*d2 = *d1
return d2
}, 1000)
c.domainBlock.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initEmoji() {
c.emoji = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "Shortcode.Domain"},
{Name: "ImageStaticURL"},
}, func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji {
e2 := new(gtsmodel.Emoji)
*e2 = *e1
return e2
}, 1000)
c.emoji.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initEmojiCategory() {
c.emojiCategory = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "Name"},
}, func(c1 *gtsmodel.EmojiCategory) *gtsmodel.EmojiCategory {
c2 := new(gtsmodel.EmojiCategory)
*c2 = *c1
return c2
}, 1000)
c.emojiCategory.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initMention() {
c.mention = result.NewSized([]result.Lookup{
{Name: "ID"},
}, func(m1 *gtsmodel.Mention) *gtsmodel.Mention {
m2 := new(gtsmodel.Mention)
*m2 = *m1
return m2
}, 1000)
c.mention.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initNotification() {
c.notification = result.NewSized([]result.Lookup{
{Name: "ID"},
}, func(n1 *gtsmodel.Notification) *gtsmodel.Notification {
n2 := new(gtsmodel.Notification)
*n2 = *n1
return n2
}, 1000)
c.notification.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initStatus() {
c.status = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "URL"},
}, func(s1 *gtsmodel.Status) *gtsmodel.Status {
s2 := new(gtsmodel.Status)
*s2 = *s1
return s2
}, 1000)
c.status.SetTTL(time.Minute*5, false)
}
// initTombstone will initialize the gtsmodel.Tombstone cache.
func (c *gtsCaches) initTombstone() {
c.tombstone = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
}, func(t1 *gtsmodel.Tombstone) *gtsmodel.Tombstone {
t2 := new(gtsmodel.Tombstone)
*t2 = *t1
return t2
}, 100)
c.tombstone.SetTTL(time.Minute*5, false)
}
func (c *gtsCaches) initUser() {
c.user = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "AccountID"},
{Name: "Email"},
{Name: "ConfirmationToken"},
{Name: "ExternalID"},
}, func(u1 *gtsmodel.User) *gtsmodel.User {
u2 := new(gtsmodel.User)
*u2 = *u1
return u2
}, 1000)
c.user.SetTTL(time.Minute*5, false)
}

36
internal/cache/util.go vendored Normal file
View file

@ -0,0 +1,36 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
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 "github.com/superseriousbusiness/gotosocial/internal/log"
// nocopy when embedded will signal linter to
// error on pass-by-value of parent struct.
type nocopy struct{}
func (*nocopy) Lock() {}
func (*nocopy) Unlock() {}
// 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 && !do(); i++ {
}
log.Panicf("failed %s after %d tries", msg, count)
}

View file

@ -25,39 +25,18 @@
"strings"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
)
type accountDB struct {
conn *DBConn
cache *result.Cache[*gtsmodel.Account]
emojis *emojiDB
status *statusDB
}
func (a *accountDB) init() {
// Initialize account result cache
a.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "URL"},
{Name: "Username.Domain"},
{Name: "PublicKeyURI"},
}, func(a1 *gtsmodel.Account) *gtsmodel.Account {
a2 := new(gtsmodel.Account)
*a2 = *a1
return a2
}, 1000)
// Set cache TTL and start sweep routine
a.cache.SetTTL(time.Minute*5, false)
a.cache.Start(time.Second * 10)
state *state.State
}
func (a *accountDB) newAccountQ(account *gtsmodel.Account) *bun.SelectQuery {
@ -152,7 +131,7 @@ func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gts
func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Account) error, keyParts ...any) (*gtsmodel.Account, db.Error) {
// Fetch account from database cache with loader callback
account, err := a.cache.Load(lookup, func() (*gtsmodel.Account, error) {
account, err := a.state.Caches.GTS.Account().Load(lookup, func() (*gtsmodel.Account, error) {
var account gtsmodel.Account
// Not cached! Perform database query
@ -168,7 +147,7 @@ func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(
if len(account.EmojiIDs) > 0 {
// Set the account's related emojis
account.Emojis, err = a.emojis.emojisFromIDs(ctx, account.EmojiIDs)
account.Emojis, err = a.state.DB.GetEmojisByIDs(ctx, account.EmojiIDs)
if err != nil {
return nil, fmt.Errorf("error getting account emojis: %w", err)
}
@ -178,7 +157,7 @@ func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(
}
func (a *accountDB) PutAccount(ctx context.Context, account *gtsmodel.Account) db.Error {
return a.cache.Store(account, func() error {
return a.state.Caches.GTS.Account().Store(account, func() error {
// It is safe to run this database transaction within cache.Store
// as the cache does not attempt a mutex lock until AFTER hook.
//
@ -204,7 +183,7 @@ func (a *accountDB) UpdateAccount(ctx context.Context, account *gtsmodel.Account
// Update the account's last-updated
account.UpdatedAt = time.Now()
return a.cache.Store(account, func() error {
return a.state.Caches.GTS.Account().Store(account, func() error {
// It is safe to run this database transaction within cache.Store
// as the cache does not attempt a mutex lock until AFTER hook.
//
@ -263,7 +242,7 @@ func (a *accountDB) DeleteAccount(ctx context.Context, id string) db.Error {
return err
}
a.cache.Invalidate("ID", id)
a.state.Caches.GTS.Account().Invalidate("ID", id)
return nil
}
@ -514,7 +493,7 @@ func (a *accountDB) statusesFromIDs(ctx context.Context, statusIDs []string) ([]
for _, id := range statusIDs {
// Fetch from status from database by ID
status, err := a.status.GetStatusByID(ctx, id)
status, err := a.state.DB.GetStatusByID(ctx, id)
if err != nil {
log.Errorf("statusesFromIDs: error getting status %q: %v", id, err)
continue

View file

@ -34,6 +34,7 @@
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/uris"
"github.com/uptrace/bun"
"golang.org/x/crypto/bcrypt"
@ -44,8 +45,7 @@
type adminDB struct {
conn *DBConn
accounts *accountDB
users *userDB
state *state.State
}
func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) (bool, db.Error) {
@ -139,7 +139,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string,
}
// insert the new account!
if err := a.accounts.PutAccount(ctx, acct); err != nil {
if err := a.state.DB.PutAccount(ctx, acct); err != nil {
return nil, err
}
}
@ -185,7 +185,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string,
}
// insert the user!
if err := a.users.PutUser(ctx, u); err != nil {
if err := a.state.DB.PutUser(ctx, u); err != nil {
return nil, err
}
@ -241,7 +241,7 @@ func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error {
}
// insert the new account!
if err := a.accounts.PutAccount(ctx, acct); err != nil {
if err := a.state.DB.PutAccount(ctx, acct); err != nil {
return err
}

View file

@ -40,6 +40,7 @@
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/dialect/sqlitedialect"
@ -122,7 +123,7 @@ func doMigration(ctx context.Context, db *bun.DB) error {
// NewBunDBService returns a bunDB derived from the provided config, which implements the go-fed DB interface.
// Under the hood, it uses https://github.com/uptrace/bun to create and maintain a database connection.
func NewBunDBService(ctx context.Context) (db.DB, error) {
func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
var conn *DBConn
var err error
dbType := strings.ToLower(config.GetDbType())
@ -158,68 +159,63 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
return nil, fmt.Errorf("db migration error: %s", err)
}
// Create DB structs that require ptrs to each other
account := &accountDB{conn: conn}
admin := &adminDB{conn: conn}
domain := &domainDB{conn: conn}
mention := &mentionDB{conn: conn}
notif := &notificationDB{conn: conn}
status := &statusDB{conn: conn}
emoji := &emojiDB{conn: conn}
relationship := &relationshipDB{conn: conn}
timeline := &timelineDB{conn: conn}
tombstone := &tombstoneDB{conn: conn}
user := &userDB{conn: conn}
// Setup DB cross-referencing
account.emojis = emoji
account.status = status
admin.users = user
relationship.accounts = account
status.accounts = account
status.emojis = emoji
status.mentions = mention
timeline.status = status
// Initialize db structs
account.init()
domain.init()
emoji.init()
mention.init()
notif.init()
relationship.init()
status.init()
tombstone.init()
user.init()
ps := &DBService{
Account: account,
Account: &accountDB{
conn: conn,
state: state,
},
Admin: &adminDB{
conn: conn,
accounts: account,
users: user,
state: state,
},
Basic: &basicDB{
conn: conn,
},
Domain: domain,
Emoji: emoji,
Domain: &domainDB{
conn: conn,
state: state,
},
Emoji: &emojiDB{
conn: conn,
state: state,
},
Instance: &instanceDB{
conn: conn,
},
Media: &mediaDB{
conn: conn,
},
Mention: mention,
Notification: notif,
Relationship: relationship,
Mention: &mentionDB{
conn: conn,
state: state,
},
Notification: &notificationDB{
conn: conn,
state: state,
},
Relationship: &relationshipDB{
conn: conn,
state: state,
},
Session: &sessionDB{
conn: conn,
},
Status: status,
Timeline: timeline,
User: user,
Tombstone: tombstone,
Status: &statusDB{
conn: conn,
state: state,
},
Timeline: &timelineDB{
conn: conn,
state: state,
},
User: &userDB{
conn: conn,
state: state,
},
Tombstone: &tombstoneDB{
conn: conn,
state: state,
},
conn: conn,
}

View file

@ -33,7 +33,7 @@ type BundbNewTestSuite struct {
func (suite *BundbNewTestSuite) TestCreateNewDB() {
// create a new db with standard test settings
db, err := bundb.NewBunDBService(context.Background())
db, err := bundb.NewBunDBService(context.Background(), nil)
suite.NoError(err)
suite.NotNil(db)
}
@ -42,7 +42,7 @@ func (suite *BundbNewTestSuite) TestCreateNewSqliteDBNoAddress() {
// create a new db with no address specified
config.SetDbAddress("")
config.SetDbType("sqlite")
db, err := bundb.NewBunDBService(context.Background())
db, err := bundb.NewBunDBService(context.Background(), nil)
suite.EqualError(err, "'db-address' was not set when attempting to start sqlite")
suite.Nil(db)
}

View file

@ -22,34 +22,18 @@
"context"
"net/url"
"strings"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"golang.org/x/net/idna"
)
type domainDB struct {
conn *DBConn
cache *result.Cache[*gtsmodel.DomainBlock]
}
func (d *domainDB) init() {
// Initialize domain block result cache
d.cache = result.NewSized([]result.Lookup{
{Name: "Domain"},
}, func(d1 *gtsmodel.DomainBlock) *gtsmodel.DomainBlock {
d2 := new(gtsmodel.DomainBlock)
*d2 = *d1
return d2
}, 1000)
// Set cache TTL and start sweep routine
d.cache.SetTTL(time.Minute*5, false)
d.cache.Start(time.Second * 10)
state *state.State
}
// normalizeDomain converts the given domain to lowercase
@ -71,7 +55,7 @@ func (d *domainDB) CreateDomainBlock(ctx context.Context, block *gtsmodel.Domain
return err
}
return d.cache.Store(block, func() error {
return d.state.Caches.GTS.DomainBlock().Store(block, func() error {
_, err := d.conn.NewInsert().
Model(block).
Exec(ctx)
@ -87,7 +71,7 @@ func (d *domainDB) GetDomainBlock(ctx context.Context, domain string) (*gtsmodel
return nil, err
}
return d.cache.Load("Domain", func() (*gtsmodel.DomainBlock, error) {
return d.state.Caches.GTS.DomainBlock().Load("Domain", func() (*gtsmodel.DomainBlock, error) {
// Check for easy case, domain referencing *us*
if domain == "" || domain == config.GetAccountDomain() {
return nil, db.ErrNoEntries
@ -125,7 +109,7 @@ func (d *domainDB) DeleteDomainBlock(ctx context.Context, domain string) db.Erro
}
// Clear domain from cache
d.cache.Invalidate("Domain", domain)
d.state.Caches.GTS.DomainBlock().Invalidate(domain)
return nil
}

View file

@ -23,50 +23,17 @@
"strings"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
)
type emojiDB struct {
conn *DBConn
emojiCache *result.Cache[*gtsmodel.Emoji]
categoryCache *result.Cache[*gtsmodel.EmojiCategory]
}
func (e *emojiDB) init() {
// Initialize emoji result cache
e.emojiCache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "Shortcode.Domain"},
{Name: "ImageStaticURL"},
}, func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji {
e2 := new(gtsmodel.Emoji)
*e2 = *e1
return e2
}, 1000)
// Set cache TTL and start sweep routine
e.emojiCache.SetTTL(time.Minute*5, false)
e.emojiCache.Start(time.Second * 10)
// Initialize category result cache
e.categoryCache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "Name"},
}, func(c1 *gtsmodel.EmojiCategory) *gtsmodel.EmojiCategory {
c2 := new(gtsmodel.EmojiCategory)
*c2 = *c1
return c2
}, 1000)
// Set cache TTL and start sweep routine
e.categoryCache.SetTTL(time.Minute*5, false)
e.categoryCache.Start(time.Second * 10)
state *state.State
}
func (e *emojiDB) newEmojiQ(emoji *gtsmodel.Emoji) *bun.SelectQuery {
@ -83,7 +50,7 @@ func (e *emojiDB) newEmojiCategoryQ(emojiCategory *gtsmodel.EmojiCategory) *bun.
}
func (e *emojiDB) PutEmoji(ctx context.Context, emoji *gtsmodel.Emoji) db.Error {
return e.emojiCache.Store(emoji, func() error {
return e.state.Caches.GTS.Emoji().Store(emoji, func() error {
_, err := e.conn.NewInsert().Model(emoji).Exec(ctx)
return e.conn.ProcessError(err)
})
@ -102,7 +69,7 @@ func (e *emojiDB) UpdateEmoji(ctx context.Context, emoji *gtsmodel.Emoji, column
return nil, e.conn.ProcessError(err)
}
e.emojiCache.Invalidate("ID", emoji.ID)
e.state.Caches.GTS.Emoji().Invalidate("ID", emoji.ID)
return emoji, nil
}
@ -139,7 +106,7 @@ func (e *emojiDB) DeleteEmojiByID(ctx context.Context, id string) db.Error {
return err
}
e.emojiCache.Invalidate("ID", id)
e.state.Caches.GTS.Emoji().Invalidate("ID", id)
return nil
}
@ -257,7 +224,7 @@ func (e *emojiDB) GetEmojis(ctx context.Context, domain string, includeDisabled
}
}
return e.emojisFromIDs(ctx, emojiIDs)
return e.GetEmojisByIDs(ctx, emojiIDs)
}
func (e *emojiDB) GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, db.Error) {
@ -276,7 +243,7 @@ func (e *emojiDB) GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, db.E
return nil, e.conn.ProcessError(err)
}
return e.emojisFromIDs(ctx, emojiIDs)
return e.GetEmojisByIDs(ctx, emojiIDs)
}
func (e *emojiDB) GetEmojiByID(ctx context.Context, id string) (*gtsmodel.Emoji, db.Error) {
@ -338,7 +305,7 @@ func(emoji *gtsmodel.Emoji) error {
}
func (e *emojiDB) PutEmojiCategory(ctx context.Context, emojiCategory *gtsmodel.EmojiCategory) db.Error {
return e.categoryCache.Store(emojiCategory, func() error {
return e.state.Caches.GTS.EmojiCategory().Store(emojiCategory, func() error {
_, err := e.conn.NewInsert().Model(emojiCategory).Exec(ctx)
return e.conn.ProcessError(err)
})
@ -357,7 +324,7 @@ func (e *emojiDB) GetEmojiCategories(ctx context.Context) ([]*gtsmodel.EmojiCate
return nil, e.conn.ProcessError(err)
}
return e.emojiCategoriesFromIDs(ctx, emojiCategoryIDs)
return e.GetEmojiCategoriesByIDs(ctx, emojiCategoryIDs)
}
func (e *emojiDB) GetEmojiCategory(ctx context.Context, id string) (*gtsmodel.EmojiCategory, db.Error) {
@ -383,7 +350,7 @@ func(emojiCategory *gtsmodel.EmojiCategory) error {
}
func (e *emojiDB) getEmoji(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Emoji) error, keyParts ...any) (*gtsmodel.Emoji, db.Error) {
return e.emojiCache.Load(lookup, func() (*gtsmodel.Emoji, error) {
return e.state.Caches.GTS.Emoji().Load(lookup, func() (*gtsmodel.Emoji, error) {
var emoji gtsmodel.Emoji
// Not cached! Perform database query
@ -395,8 +362,7 @@ func (e *emojiDB) getEmoji(ctx context.Context, lookup string, dbQuery func(*gts
}, keyParts...)
}
func (e *emojiDB) emojisFromIDs(ctx context.Context, emojiIDs []string) ([]*gtsmodel.Emoji, db.Error) {
// Catch case of no emojis early
func (e *emojiDB) GetEmojisByIDs(ctx context.Context, emojiIDs []string) ([]*gtsmodel.Emoji, db.Error) {
if len(emojiIDs) == 0 {
return nil, db.ErrNoEntries
}
@ -417,7 +383,7 @@ func (e *emojiDB) emojisFromIDs(ctx context.Context, emojiIDs []string) ([]*gtsm
}
func (e *emojiDB) getEmojiCategory(ctx context.Context, lookup string, dbQuery func(*gtsmodel.EmojiCategory) error, keyParts ...any) (*gtsmodel.EmojiCategory, db.Error) {
return e.categoryCache.Load(lookup, func() (*gtsmodel.EmojiCategory, error) {
return e.state.Caches.GTS.EmojiCategory().Load(lookup, func() (*gtsmodel.EmojiCategory, error) {
var category gtsmodel.EmojiCategory
// Not cached! Perform database query
@ -429,8 +395,7 @@ func (e *emojiDB) getEmojiCategory(ctx context.Context, lookup string, dbQuery f
}, keyParts...)
}
func (e *emojiDB) emojiCategoriesFromIDs(ctx context.Context, emojiCategoryIDs []string) ([]*gtsmodel.EmojiCategory, db.Error) {
// Catch case of no emoji categories early
func (e *emojiDB) GetEmojiCategoriesByIDs(ctx context.Context, emojiCategoryIDs []string) ([]*gtsmodel.EmojiCategory, db.Error) {
if len(emojiCategoryIDs) == 0 {
return nil, db.ErrNoEntries
}

View file

@ -20,33 +20,17 @@
import (
"context"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type mentionDB struct {
conn *DBConn
cache *result.Cache[*gtsmodel.Mention]
}
func (m *mentionDB) init() {
// Initialize notification result cache
m.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
}, func(m1 *gtsmodel.Mention) *gtsmodel.Mention {
m2 := new(gtsmodel.Mention)
*m2 = *m1
return m2
}, 1000)
// Set cache TTL and start sweep routine
m.cache.SetTTL(time.Minute*5, false)
m.cache.Start(time.Second * 10)
state *state.State
}
func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery {
@ -59,7 +43,7 @@ func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery {
}
func (m *mentionDB) GetMention(ctx context.Context, id string) (*gtsmodel.Mention, db.Error) {
return m.cache.Load("ID", func() (*gtsmodel.Mention, error) {
return m.state.Caches.GTS.Mention().Load("ID", func() (*gtsmodel.Mention, error) {
var mention gtsmodel.Mention
q := m.newMentionQ(&mention).

View file

@ -20,37 +20,21 @@
import (
"context"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type notificationDB struct {
conn *DBConn
cache *result.Cache[*gtsmodel.Notification]
}
func (n *notificationDB) init() {
// Initialize notification result cache
n.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
}, func(n1 *gtsmodel.Notification) *gtsmodel.Notification {
n2 := new(gtsmodel.Notification)
*n2 = *n1
return n2
}, 1000)
// Set cache TTL and start sweep routine
n.cache.SetTTL(time.Minute*5, false)
n.cache.Start(time.Second * 10)
state *state.State
}
func (n *notificationDB) GetNotification(ctx context.Context, id string) (*gtsmodel.Notification, db.Error) {
return n.cache.Load("ID", func() (*gtsmodel.Notification, error) {
return n.state.Caches.GTS.Notification().Load("ID", func() (*gtsmodel.Notification, error) {
var notif gtsmodel.Notification
q := n.conn.NewSelect().
@ -130,6 +114,6 @@ func (n *notificationDB) ClearNotifications(ctx context.Context, accountID strin
return n.conn.ProcessError(err)
}
n.cache.Clear()
n.state.Caches.GTS.Notification().Clear()
return nil
}

View file

@ -23,35 +23,16 @@
"database/sql"
"errors"
"fmt"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type relationshipDB struct {
conn *DBConn
accounts *accountDB
blockCache *result.Cache[*gtsmodel.Block]
}
func (r *relationshipDB) init() {
// Initialize block result cache
r.blockCache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "AccountID.TargetAccountID"},
{Name: "URI"},
}, func(b1 *gtsmodel.Block) *gtsmodel.Block {
b2 := new(gtsmodel.Block)
*b2 = *b1
return b2
}, 1000)
// Set cache TTL and start sweep routine
r.blockCache.SetTTL(time.Minute*5, false)
r.blockCache.Start(time.Second * 10)
state *state.State
}
func (r *relationshipDB) newFollowQ(follow interface{}) *bun.SelectQuery {
@ -94,13 +75,13 @@ func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2
}
// Set the block originating account
block.Account, err = r.accounts.GetAccountByID(ctx, block.AccountID)
block.Account, err = r.state.DB.GetAccountByID(ctx, block.AccountID)
if err != nil {
return nil, err
}
// Set the block target account
block.TargetAccount, err = r.accounts.GetAccountByID(ctx, block.TargetAccountID)
block.TargetAccount, err = r.state.DB.GetAccountByID(ctx, block.TargetAccountID)
if err != nil {
return nil, err
}
@ -109,7 +90,7 @@ func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2
}
func (r *relationshipDB) getBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, db.Error) {
return r.blockCache.Load("AccountID.TargetAccountID", func() (*gtsmodel.Block, error) {
return r.state.Caches.GTS.Block().Load("AccountID.TargetAccountID", func() (*gtsmodel.Block, error) {
var block gtsmodel.Block
q := r.conn.NewSelect().Model(&block).
@ -124,7 +105,7 @@ func (r *relationshipDB) getBlock(ctx context.Context, account1 string, account2
}
func (r *relationshipDB) PutBlock(ctx context.Context, block *gtsmodel.Block) db.Error {
return r.blockCache.Store(block, func() error {
return r.state.Caches.GTS.Block().Store(block, func() error {
_, err := r.conn.NewInsert().Model(block).Exec(ctx)
return r.conn.ProcessError(err)
})
@ -140,7 +121,7 @@ func (r *relationshipDB) DeleteBlockByID(ctx context.Context, id string) db.Erro
}
// Drop any old value from cache by this ID
r.blockCache.Invalidate("ID", id)
r.state.Caches.GTS.Block().Invalidate("ID", id)
return nil
}
@ -154,7 +135,7 @@ func (r *relationshipDB) DeleteBlockByURI(ctx context.Context, uri string) db.Er
}
// Drop any old value from cache by this URI
r.blockCache.Invalidate("URI", uri)
r.state.Caches.GTS.Block().Invalidate("URI", uri)
return nil
}

View file

@ -26,36 +26,16 @@
"fmt"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type statusDB struct {
conn *DBConn
cache *result.Cache[*gtsmodel.Status]
accounts *accountDB
emojis *emojiDB
mentions *mentionDB
}
func (s *statusDB) init() {
// Initialize status result cache
s.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
{Name: "URL"},
}, func(s1 *gtsmodel.Status) *gtsmodel.Status {
s2 := new(gtsmodel.Status)
*s2 = *s1
return s2
}, 1000)
// Set cache TTL and start sweep routine
s.cache.SetTTL(time.Minute*5, false)
s.cache.Start(time.Second * 10)
state *state.State
}
func (s *statusDB) newStatusQ(status interface{}) *bun.SelectQuery {
@ -111,7 +91,7 @@ func(status *gtsmodel.Status) error {
func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Status) error, keyParts ...any) (*gtsmodel.Status, db.Error) {
// Fetch status from database cache with loader callback
status, err := s.cache.Load(lookup, func() (*gtsmodel.Status, error) {
status, err := s.state.Caches.GTS.Status().Load(lookup, func() (*gtsmodel.Status, error) {
var status gtsmodel.Status
// Not cached! Perform database query
@ -149,14 +129,14 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
}
// Set the status author account
status.Account, err = s.accounts.GetAccountByID(ctx, status.AccountID)
status.Account, err = s.state.DB.GetAccountByID(ctx, status.AccountID)
if err != nil {
return nil, fmt.Errorf("error getting status account: %w", err)
}
if id := status.BoostOfAccountID; id != "" {
// Set boost of status' author account
status.BoostOfAccount, err = s.accounts.GetAccountByID(ctx, id)
status.BoostOfAccount, err = s.state.DB.GetAccountByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("error getting boosted status account: %w", err)
}
@ -164,7 +144,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
if id := status.InReplyToAccountID; id != "" {
// Set in-reply-to status' author account
status.InReplyToAccount, err = s.accounts.GetAccountByID(ctx, id)
status.InReplyToAccount, err = s.state.DB.GetAccountByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("error getting in reply to status account: %w", err)
}
@ -172,7 +152,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
if len(status.EmojiIDs) > 0 {
// Fetch status emojis
status.Emojis, err = s.emojis.emojisFromIDs(ctx, status.EmojiIDs)
status.Emojis, err = s.state.DB.GetEmojisByIDs(ctx, status.EmojiIDs)
if err != nil {
return nil, fmt.Errorf("error getting status emojis: %w", err)
}
@ -180,7 +160,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
if len(status.MentionIDs) > 0 {
// Fetch status mentions
status.Mentions, err = s.mentions.GetMentions(ctx, status.MentionIDs)
status.Mentions, err = s.state.DB.GetMentions(ctx, status.MentionIDs)
if err != nil {
return nil, fmt.Errorf("error getting status mentions: %w", err)
}
@ -190,7 +170,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
}
func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Error {
return s.cache.Store(status, func() error {
return s.state.Caches.GTS.Status().Store(status, func() error {
// It is safe to run this database transaction within cache.Store
// as the cache does not attempt a mutex lock until AFTER hook.
//
@ -308,7 +288,7 @@ func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status) db
}
// Drop any old value from cache by this ID
s.cache.Invalidate("ID", status.ID)
s.state.Caches.GTS.Status().Invalidate("ID", status.ID)
return nil
}
@ -347,7 +327,7 @@ func (s *statusDB) DeleteStatusByID(ctx context.Context, id string) db.Error {
}
// Drop any old value from cache by this ID
s.cache.Invalidate("ID", id)
s.state.Caches.GTS.Status().Invalidate("ID", id)
return nil
}

View file

@ -26,13 +26,14 @@
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"golang.org/x/exp/slices"
)
type timelineDB struct {
conn *DBConn
status *statusDB
state *state.State
}
func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, db.Error) {
@ -111,7 +112,7 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI
for _, id := range statusIDs {
// Fetch status from db for ID
status, err := t.status.GetStatusByID(ctx, id)
status, err := t.state.DB.GetStatusByID(ctx, id)
if err != nil {
log.Errorf("GetHomeTimeline: error fetching status %q: %v", id, err)
continue
@ -179,7 +180,7 @@ func (t *timelineDB) GetPublicTimeline(ctx context.Context, maxID string, sinceI
for _, id := range statusIDs {
// Fetch status from db for ID
status, err := t.status.GetStatusByID(ctx, id)
status, err := t.state.DB.GetStatusByID(ctx, id)
if err != nil {
log.Errorf("GetPublicTimeline: error fetching status %q: %v", id, err)
continue
@ -239,7 +240,7 @@ func (t *timelineDB) GetFavedTimeline(ctx context.Context, accountID string, max
for _, fave := range faves {
// Fetch status from db for corresponding favourite
status, err := t.status.GetStatusByID(ctx, fave.StatusID)
status, err := t.state.DB.GetStatusByID(ctx, fave.StatusID)
if err != nil {
log.Errorf("GetFavedTimeline: error fetching status for fave %q: %v", fave.ID, err)
continue

View file

@ -20,38 +20,20 @@
import (
"context"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"codeberg.org/gruf/go-cache/v3/result"
)
type tombstoneDB struct {
conn *DBConn
cache *result.Cache[*gtsmodel.Tombstone]
}
func (t *tombstoneDB) init() {
// Initialize tombstone result cache
t.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "URI"},
}, func(t1 *gtsmodel.Tombstone) *gtsmodel.Tombstone {
t2 := new(gtsmodel.Tombstone)
*t2 = *t1
return t2
}, 100)
// Set cache TTL and start sweep routine
t.cache.SetTTL(time.Minute*5, false)
t.cache.Start(time.Second * 10)
state *state.State
}
func (t *tombstoneDB) GetTombstoneByURI(ctx context.Context, uri string) (*gtsmodel.Tombstone, db.Error) {
return t.cache.Load("URI", func() (*gtsmodel.Tombstone, error) {
return t.state.Caches.GTS.Tombstone().Load("URI", func() (*gtsmodel.Tombstone, error) {
var tomb gtsmodel.Tombstone
q := t.conn.
@ -76,7 +58,7 @@ func (t *tombstoneDB) TombstoneExistsWithURI(ctx context.Context, uri string) (b
}
func (t *tombstoneDB) PutTombstone(ctx context.Context, tombstone *gtsmodel.Tombstone) db.Error {
return t.cache.Store(tombstone, func() error {
return t.state.Caches.GTS.Tombstone().Store(tombstone, func() error {
_, err := t.conn.
NewInsert().
Model(tombstone).
@ -95,7 +77,7 @@ func (t *tombstoneDB) DeleteTombstone(ctx context.Context, id string) db.Error {
}
// Invalidate from cache by ID
t.cache.Invalidate("ID", id)
t.state.Caches.GTS.Tombstone().Invalidate("ID", id)
return nil
}

View file

@ -22,38 +22,19 @@
"context"
"time"
"codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type userDB struct {
conn *DBConn
cache *result.Cache[*gtsmodel.User]
}
func (u *userDB) init() {
// Initialize user result cache
u.cache = result.NewSized([]result.Lookup{
{Name: "ID"},
{Name: "AccountID"},
{Name: "Email"},
{Name: "ConfirmationToken"},
{Name: "ExternalID"},
}, func(u1 *gtsmodel.User) *gtsmodel.User {
u2 := new(gtsmodel.User)
*u2 = *u1
return u2
}, 1000)
// Set cache TTL and start sweep routine
u.cache.SetTTL(time.Minute*5, false)
u.cache.Start(time.Second * 10)
state *state.State
}
func (u *userDB) GetUserByID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
return u.cache.Load("ID", func() (*gtsmodel.User, error) {
return u.state.Caches.GTS.User().Load("ID", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@ -71,7 +52,7 @@ func (u *userDB) GetUserByID(ctx context.Context, id string) (*gtsmodel.User, db
}
func (u *userDB) GetUserByAccountID(ctx context.Context, accountID string) (*gtsmodel.User, db.Error) {
return u.cache.Load("AccountID", func() (*gtsmodel.User, error) {
return u.state.Caches.GTS.User().Load("AccountID", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@ -89,7 +70,7 @@ func (u *userDB) GetUserByAccountID(ctx context.Context, accountID string) (*gts
}
func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string) (*gtsmodel.User, db.Error) {
return u.cache.Load("Email", func() (*gtsmodel.User, error) {
return u.state.Caches.GTS.User().Load("Email", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@ -105,9 +86,9 @@ func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string)
return &user, nil
}, emailAddress)
}
func (u *userDB) GetUserByExternalID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
return u.cache.Load("ExternalID", func() (*gtsmodel.User, error) {
func (u *userDB) GetUserByExternalID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
return u.state.Caches.GTS.User().Load("ExternalID", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@ -125,7 +106,7 @@ func (u *userDB) GetUserByExternalID(ctx context.Context, id string) (*gtsmodel.
}
func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationToken string) (*gtsmodel.User, db.Error) {
return u.cache.Load("ConfirmationToken", func() (*gtsmodel.User, error) {
return u.state.Caches.GTS.User().Load("ConfirmationToken", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@ -143,7 +124,7 @@ func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationTok
}
func (u *userDB) PutUser(ctx context.Context, user *gtsmodel.User) db.Error {
return u.cache.Store(user, func() error {
return u.state.Caches.GTS.User().Store(user, func() error {
_, err := u.conn.
NewInsert().
Model(user).
@ -172,8 +153,8 @@ func (u *userDB) UpdateUser(ctx context.Context, user *gtsmodel.User, columns ..
return u.conn.ProcessError(err)
}
// Invalidate in cache
u.cache.Invalidate("ID", user.ID)
// Invalidate user from cache
u.state.Caches.GTS.User().Invalidate("ID", user.ID)
return nil
}
@ -187,6 +168,6 @@ func (u *userDB) DeleteUserByID(ctx context.Context, userID string) db.Error {
}
// Invalidate user from cache
u.cache.Invalidate("ID", userID)
u.state.Caches.GTS.User().Invalidate("ID", userID)
return nil
}

View file

@ -37,6 +37,8 @@ type Emoji interface {
UpdateEmoji(ctx context.Context, emoji *gtsmodel.Emoji, columns ...string) (*gtsmodel.Emoji, Error)
// DeleteEmojiByID deletes one emoji by its database ID.
DeleteEmojiByID(ctx context.Context, id string) Error
// GetEmojisByIDs gets emojis for the given IDs.
GetEmojisByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Emoji, Error)
// GetUseableEmojis gets all emojis which are useable by accounts on this instance.
GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, Error)
// GetEmojis gets emojis based on given parameters. Useful for admin actions.
@ -52,6 +54,8 @@ type Emoji interface {
GetEmojiByStaticURL(ctx context.Context, imageStaticURL string) (*gtsmodel.Emoji, Error)
// PutEmojiCategory puts one new emoji category in the database.
PutEmojiCategory(ctx context.Context, emojiCategory *gtsmodel.EmojiCategory) Error
// GetEmojiCategoriesByIDs gets emoji categories for given IDs.
GetEmojiCategoriesByIDs(ctx context.Context, ids []string) ([]*gtsmodel.EmojiCategory, Error)
// GetEmojiCategories gets a slice of the names of all existing emoji categories.
GetEmojiCategories(ctx context.Context) ([]*gtsmodel.EmojiCategory, Error)
// GetEmojiCategory gets one emoji category by its id.

49
internal/state/state.go Normal file
View file

@ -0,0 +1,49 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
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 state
import (
"github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/db"
)
// State provides a means of dependency injection and sharing of resources
// across different subpackages of the GoToSocial codebase. DO NOT assume
// that any particular field will be initialized if you are accessing this
// during initialization. A pointer to a State{} is often passed during
// subpackage initialization, while the returned subpackage type will later
// then be set and stored within the State{} itself.
type State struct {
// Caches provides access to this state's collection of caches.
Caches cache.Caches
// DB provides access to the database.
DB db.DB
// prevent pass-by-value.
_ nocopy
}
// nocopy when embedded will signal linter to
// error on pass-by-value of parent struct.
type nocopy struct{}
func (*nocopy) Lock() {}
func (*nocopy) Unlock() {}

View file

@ -148,7 +148,8 @@ func NewTimeline(
grabFunction GrabFunction,
filterFunction FilterFunction,
prepareFunction PrepareFunction,
skipInsertFunction SkipInsertFunction) (Timeline, error) {
skipInsertFunction SkipInsertFunction,
) (Timeline, error) {
return &timeline{
indexedItems: &indexedItems{
skipInsert: skipInsertFunction,

View file

@ -2,7 +2,6 @@
import (
"context"
"fmt"
"time"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@ -13,7 +12,6 @@
func (c *converter) FollowRequestToFollow(ctx context.Context, f *gtsmodel.FollowRequest) *gtsmodel.Follow {
showReblogs := *f.ShowReblogs
notify := *f.Notify
return &gtsmodel.Follow{
ID: f.ID,
CreatedAt: f.CreatedAt,
@ -33,8 +31,9 @@ func (c *converter) StatusToBoost(ctx context.Context, s *gtsmodel.Status, boost
if err != nil {
return nil, err
}
boostWrapperStatusURI := fmt.Sprintf("%s/%s", accountURIs.StatusesURI, boostWrapperStatusID)
boostWrapperStatusURL := fmt.Sprintf("%s/%s", accountURIs.StatusesURL, boostWrapperStatusID)
boostWrapperStatusURI := accountURIs.StatusesURI + "/" + boostWrapperStatusID
boostWrapperStatusURL := accountURIs.StatusesURL + "/" + boostWrapperStatusID
local := true
if boostingAccount.Domain != "" {

View file

@ -28,6 +28,7 @@
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
)
var testModels = []interface{}{
@ -92,10 +93,16 @@ func NewTestDB() db.DB {
})
}
testDB, err := bundb.NewBunDBService(context.Background())
var state state.State
state.Caches.Init()
testDB, err := bundb.NewBunDBService(context.Background(), &state)
if err != nil {
log.Panic(err)
}
state.DB = testDB
return testDB
}