more code comments, don't bother inserting statuses if timeline not preloaded

This commit is contained in:
kim 2025-04-08 18:14:26 +01:00
parent 2bd26dbedc
commit 13d5adbfe0
3 changed files with 63 additions and 35 deletions

View file

@ -33,10 +33,34 @@ import (
// - brand-new = nil (functionally same as 'needs preload')
type preloader struct{ p atomic.Pointer[any] }
// Check will concurrency-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) Check(preload func()) {
// Check will return the current preload state,
// waiting if a preload is currently in progress.
func (p *preloader) Check() bool {
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 {
// Get state ptr.
ptr := p.p.Load()
@ -95,7 +119,7 @@ func (p *preloader) start(old *any, preload func()) bool {
// done marks state as preloaded,
// i.e. no more preload required.
func (p *preloader) done() {
func (p *preloader) Done() {
old := p.p.Swap(new(any))
if old == nil { // was brand-new
return
@ -109,7 +133,7 @@ func (p *preloader) done() {
// clear will clear the state, marking a "preload" as required.
// i.e. next call to Check() will call provided preload func.
func (p *preloader) clear() {
func (p *preloader) Clear() {
b := false
a := any(b)
for {

View file

@ -32,6 +32,12 @@ import (
"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
// StatusMeta contains minimum viable metadata
@ -190,14 +196,14 @@ func (t *StatusTimeline) Preload(
n int,
err error,
) {
t.preloader.Check(func() {
t.preloader.CheckPreload(func() {
n, err = t.preload(loadPage, filter)
if err != nil {
return
}
// Mark preloaded.
t.preloader.done()
t.preloader.Done()
})
return
}
@ -547,6 +553,13 @@ func loadStatusTimeline(
// 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.
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 != "" {
// Check through top $repeatBoostDepth number of timeline items.
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,
// 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{}
// models, and use given function to return prepared frontend API models.

View file

@ -108,29 +108,9 @@ func (p *Processor) getStatusTimeline(
// input paging cursor.
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
// getting lo, hi values for next, prev pages.
apiStatuses, lo, hi, err = timeline.Load(ctx,
apiStatuses, lo, hi, err := timeline.Load(ctx,
// Status page
// to load.
@ -145,13 +125,24 @@ func (p *Processor) getStatusTimeline(
return p.state.DB.GetStatusesByIDs(ctx, ids)
},
// Filtering function,
// i.e. filter before caching.
// Call provided status
// filtering function.
filter,
// Frontend API model
// preparation function.
prepare,
// Frontend API model preparation function.
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
},
)
if err != nil {