mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 21:12:25 -05:00
more code comments, don't bother inserting statuses if timeline not preloaded
This commit is contained in:
parent
00d8a1f8ac
commit
b76398f685
3 changed files with 63 additions and 35 deletions
36
internal/cache/timeline/preload.go
vendored
36
internal/cache/timeline/preload.go
vendored
|
|
@ -33,10 +33,34 @@ import (
|
||||||
// - brand-new = nil (functionally same as 'needs preload')
|
// - brand-new = nil (functionally same as 'needs preload')
|
||||||
type preloader struct{ p atomic.Pointer[any] }
|
type preloader struct{ p atomic.Pointer[any] }
|
||||||
|
|
||||||
// Check will concurrency-safely check the preload
|
// Check will return the current preload state,
|
||||||
// state, and if needed call the provided function.
|
// waiting if a preload is currently in progress.
|
||||||
// if a preload is in progress, it will wait until complete.
|
func (p *preloader) Check() bool {
|
||||||
func (p *preloader) Check(preload func()) {
|
for {
|
||||||
|
// Get state ptr.
|
||||||
|
ptr := p.p.Load()
|
||||||
|
|
||||||
|
// Check if requires preloading.
|
||||||
|
if ptr == nil || *ptr == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a preload currently in progress.
|
||||||
|
if wg, _ := (*ptr).(*sync.WaitGroup); wg != nil {
|
||||||
|
wg.Wait()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything else
|
||||||
|
// means success.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPreload will safely check the preload state,
|
||||||
|
// and if needed call the provided function. if a
|
||||||
|
// preload is in progress, it will wait until complete.
|
||||||
|
func (p *preloader) CheckPreload(preload func()) {
|
||||||
for {
|
for {
|
||||||
// Get state ptr.
|
// Get state ptr.
|
||||||
ptr := p.p.Load()
|
ptr := p.p.Load()
|
||||||
|
|
@ -95,7 +119,7 @@ func (p *preloader) start(old *any, preload func()) bool {
|
||||||
|
|
||||||
// done marks state as preloaded,
|
// done marks state as preloaded,
|
||||||
// i.e. no more preload required.
|
// i.e. no more preload required.
|
||||||
func (p *preloader) done() {
|
func (p *preloader) Done() {
|
||||||
old := p.p.Swap(new(any))
|
old := p.p.Swap(new(any))
|
||||||
if old == nil { // was brand-new
|
if old == nil { // was brand-new
|
||||||
return
|
return
|
||||||
|
|
@ -109,7 +133,7 @@ func (p *preloader) done() {
|
||||||
|
|
||||||
// clear will clear the state, marking a "preload" as required.
|
// clear will clear the state, marking a "preload" as required.
|
||||||
// i.e. next call to Check() will call provided preload func.
|
// i.e. next call to Check() will call provided preload func.
|
||||||
func (p *preloader) clear() {
|
func (p *preloader) Clear() {
|
||||||
b := false
|
b := false
|
||||||
a := any(b)
|
a := any(b)
|
||||||
for {
|
for {
|
||||||
|
|
|
||||||
19
internal/cache/timeline/status.go
vendored
19
internal/cache/timeline/status.go
vendored
|
|
@ -32,6 +32,12 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
|
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// repeatBoostDepth determines the minimum count
|
||||||
|
// of statuses after which repeat boosts, or boosts
|
||||||
|
// of the original, may appear. This is may not end
|
||||||
|
// up *exact*, as small races between insert and the
|
||||||
|
// repeatBoost calculation may allow 1 or so extra
|
||||||
|
// to sneak in ahead of time. but it mostly works!
|
||||||
const repeatBoostDepth = 40
|
const repeatBoostDepth = 40
|
||||||
|
|
||||||
// StatusMeta contains minimum viable metadata
|
// StatusMeta contains minimum viable metadata
|
||||||
|
|
@ -190,14 +196,14 @@ func (t *StatusTimeline) Preload(
|
||||||
n int,
|
n int,
|
||||||
err error,
|
err error,
|
||||||
) {
|
) {
|
||||||
t.preloader.Check(func() {
|
t.preloader.CheckPreload(func() {
|
||||||
n, err = t.preload(loadPage, filter)
|
n, err = t.preload(loadPage, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark preloaded.
|
// Mark preloaded.
|
||||||
t.preloader.done()
|
t.preloader.Done()
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -547,6 +553,13 @@ func loadStatusTimeline(
|
||||||
// InsertOne allows you to insert a single status into the timeline, with optional prepared API model.
|
// InsertOne allows you to insert a single status into the timeline, with optional prepared API model.
|
||||||
// The return value indicates whether status should be skipped from streams, e.g. if already boosted recently.
|
// The return value indicates whether status should be skipped from streams, e.g. if already boosted recently.
|
||||||
func (t *StatusTimeline) InsertOne(status *gtsmodel.Status, prepared *apimodel.Status) (skip bool) {
|
func (t *StatusTimeline) InsertOne(status *gtsmodel.Status, prepared *apimodel.Status) (skip bool) {
|
||||||
|
|
||||||
|
// If timeline no preloaded, i.e.
|
||||||
|
// no-one using it, don't insert.
|
||||||
|
if !t.preloader.Check() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if status.BoostOfID != "" {
|
if status.BoostOfID != "" {
|
||||||
// Check through top $repeatBoostDepth number of timeline items.
|
// Check through top $repeatBoostDepth number of timeline items.
|
||||||
for i, value := range t.cache.RangeUnsafe(structr.Desc) {
|
for i, value := range t.cache.RangeUnsafe(structr.Desc) {
|
||||||
|
|
@ -718,7 +731,7 @@ func (t *StatusTimeline) Trim() { t.cache.Trim(t.cut, structr.Asc) }
|
||||||
|
|
||||||
// Clear will mark the entire timeline as requiring preload,
|
// Clear will mark the entire timeline as requiring preload,
|
||||||
// which will trigger a clear and reload of the entire thing.
|
// which will trigger a clear and reload of the entire thing.
|
||||||
func (t *StatusTimeline) Clear() { t.preloader.clear() }
|
func (t *StatusTimeline) Clear() { t.preloader.Clear() }
|
||||||
|
|
||||||
// prepareStatuses takes a slice of cached (or, freshly loaded!) StatusMeta{}
|
// prepareStatuses takes a slice of cached (or, freshly loaded!) StatusMeta{}
|
||||||
// models, and use given function to return prepared frontend API models.
|
// models, and use given function to return prepared frontend API models.
|
||||||
|
|
|
||||||
|
|
@ -108,29 +108,9 @@ func (p *Processor) getStatusTimeline(
|
||||||
// input paging cursor.
|
// input paging cursor.
|
||||||
id.ValidatePage(page)
|
id.ValidatePage(page)
|
||||||
|
|
||||||
// Returned models and page params.
|
|
||||||
var apiStatuses []*apimodel.Status
|
|
||||||
var lo, hi string
|
|
||||||
|
|
||||||
// Pre-prepared filter function that just ensures we
|
|
||||||
// don't end up serving multiple copies of the same boost.
|
|
||||||
prepare := func(status *gtsmodel.Status) (*apimodel.Status, error) {
|
|
||||||
apiStatus, err := p.converter.StatusToAPIStatus(ctx,
|
|
||||||
status,
|
|
||||||
requester,
|
|
||||||
filterCtx,
|
|
||||||
filters,
|
|
||||||
mutes,
|
|
||||||
)
|
|
||||||
if err != nil && !errors.Is(err, statusfilter.ErrHideStatus) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return apiStatus, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load status page via timeline cache, also
|
// Load status page via timeline cache, also
|
||||||
// getting lo, hi values for next, prev pages.
|
// getting lo, hi values for next, prev pages.
|
||||||
apiStatuses, lo, hi, err = timeline.Load(ctx,
|
apiStatuses, lo, hi, err := timeline.Load(ctx,
|
||||||
|
|
||||||
// Status page
|
// Status page
|
||||||
// to load.
|
// to load.
|
||||||
|
|
@ -145,13 +125,24 @@ func (p *Processor) getStatusTimeline(
|
||||||
return p.state.DB.GetStatusesByIDs(ctx, ids)
|
return p.state.DB.GetStatusesByIDs(ctx, ids)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Filtering function,
|
// Call provided status
|
||||||
// i.e. filter before caching.
|
// filtering function.
|
||||||
filter,
|
filter,
|
||||||
|
|
||||||
// Frontend API model
|
// Frontend API model preparation function.
|
||||||
// preparation function.
|
func(status *gtsmodel.Status) (*apimodel.Status, error) {
|
||||||
prepare,
|
apiStatus, err := p.converter.StatusToAPIStatus(ctx,
|
||||||
|
status,
|
||||||
|
requester,
|
||||||
|
filterCtx,
|
||||||
|
filters,
|
||||||
|
mutes,
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, statusfilter.ErrHideStatus) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return apiStatus, nil
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue