[bugfix] repeated posts on timeline endpoints (#4494)

- updates the go-structr library to add support for getting the current HEAD and TAIL primary key values, which allows us to not insert statuses if they're older than the current oldest timeline item
- ensures the nextPg parameters get updated after loading from the cache before then performing a database query

closes: https://codeberg.org/superseriousbusiness/gotosocial/issues/4491
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4494
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
This commit is contained in:
kim
2025-10-13 14:03:17 +02:00
committed by kim
parent 5fd52369c9
commit 566a0bc912
5 changed files with 47 additions and 4 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ require (
codeberg.org/gruf/go-sched v1.2.4
codeberg.org/gruf/go-split v1.2.0
codeberg.org/gruf/go-storage v0.3.1
codeberg.org/gruf/go-structr v0.9.12
codeberg.org/gruf/go-structr v0.9.13
github.com/DmitriyVTitov/size v1.5.0
github.com/KimMachineGun/automemlimit v0.7.4
github.com/SherClockHolmes/webpush-go v1.4.0
Generated
+2 -2
View File
@@ -54,8 +54,8 @@ codeberg.org/gruf/go-split v1.2.0 h1:PmzL23nVEVHm8VxjsJmv4m4wGQz2bGgQw52dgSSj65c
codeberg.org/gruf/go-split v1.2.0/go.mod h1:0rejWJpqvOoFAd7nwm5tIXYKaAqjtFGOXmTqQV+VO38=
codeberg.org/gruf/go-storage v0.3.1 h1:g66UIM/xXnEk9ejT+W0T9s/PODBZhXa/8ajzeY/MELI=
codeberg.org/gruf/go-storage v0.3.1/go.mod h1:r43n/zi7YGOCl2iSl7AMI27D1zcWS65Bi2+5xDzypeo=
codeberg.org/gruf/go-structr v0.9.12 h1:yMopvexnuKgZme9WgvIhrJaAuAjfper/x38xsVuJOOo=
codeberg.org/gruf/go-structr v0.9.12/go.mod h1:sP2ZSjM5X5XKlxuhAbTKuVQm9DWbHsrQRuTl3MUwbHw=
codeberg.org/gruf/go-structr v0.9.13 h1:tTAR30OnSKrcvBomlGZdwE2nE+cfWhkX8UhX0GTAbMo=
codeberg.org/gruf/go-structr v0.9.13/go.mod h1:sP2ZSjM5X5XKlxuhAbTKuVQm9DWbHsrQRuTl3MUwbHw=
codeberg.org/gruf/go-xunsafe v0.0.0-20250809104800-512a9df57d73 h1:pRaOwIOS1WSZoPCAvE0H1zpv+D4gF37OVppybffqdI8=
codeberg.org/gruf/go-xunsafe v0.0.0-20250809104800-512a9df57d73/go.mod h1:9wkq+dmHjUhB/0ZxDUWAwsWuXwwGyx5N1dDCB9hpWs8=
codeberg.org/superseriousbusiness/go-swagger v0.32.3-gts-go1.23-fix h1:k76/Th+bruqU/d+dB0Ru466ctTF2aVjKpisy/471ILE=
+10
View File
@@ -387,6 +387,9 @@ func (t *StatusTimeline) Load(
return nil, "", "", gtserror.Newf("error loading statuses: %w", err)
}
// Update nextPg cursor parameter for database query.
nextPageParams(nextPg, metas[len(metas)-1].ID, order)
// Prepare frontend API models for
// the cached statuses. For now this
// also does its own extra filtering.
@@ -536,6 +539,13 @@ func (t *StatusTimeline) InsertOne(status *gtsmodel.Status, prepared *apimodel.S
return false
}
// If item is beyond end of the
// timeline, don't bother adding.
if tailID := t.cache.Tail(); //
tailID == nil || status.ID < *tailID {
return false
}
if status.BoostOfID != "" {
// Check through top $repeatBoostDepth number of items.
for i, value := range t.cache.RangeUnsafe(structr.Desc) {
+33
View File
@@ -7,6 +7,7 @@ import (
"slices"
"strings"
"sync"
"sync/atomic"
"unsafe"
"codeberg.org/gruf/go-mempool"
@@ -83,6 +84,11 @@ type Timeline[StructType any, PK cmp.Ordered] struct {
// types by user defined sets of fields.
indices []Index
// atomically updated head
// / tail primary key values.
headPK unsafe.Pointer
tailPK unsafe.Pointer
// protective mutex, guards:
// - Timeline{}.*
// - Index{}.data
@@ -150,6 +156,16 @@ func (t *Timeline[T, PK]) Index(name string) *Index {
panic("unknown index: " + name)
}
// Head returns the current head primary key.
func (t *Timeline[T, PK]) Head() *PK {
return (*PK)(atomic.LoadPointer(&t.headPK))
}
// Tail returns the current tail primary key.
func (t *Timeline[T, PK]) Tail() *PK {
return (*PK)(atomic.LoadPointer(&t.tailPK))
}
// Select allows you to retrieve a slice of values, in order, from the timeline.
// This slice is defined by the minimum and maximum primary key parameters, up to
// a given length in size. The direction in which you select will determine which
@@ -570,6 +586,10 @@ func (t *Timeline[T, PK]) Trim(max int, dir Direction) {
bottom := t.list.tail
if bottom == nil {
// Zero head + tail primary keys.
atomic.StorePointer(&t.headPK, nil)
atomic.StorePointer(&t.tailPK, nil)
// reached
// end.
break
@@ -589,6 +609,10 @@ func (t *Timeline[T, PK]) Trim(max int, dir Direction) {
top := t.list.head
if top == nil {
// Zero head + tail primary keys.
atomic.StorePointer(&t.headPK, nil)
atomic.StorePointer(&t.tailPK, nil)
// reached
// end.
break
@@ -1034,6 +1058,15 @@ func (t *Timeline[T, PK]) store_one(last *list_elem, value value_with_pk[T, PK])
goto indexing
indexing:
// Set new head / tail
// primary key values.
switch last {
case t.list.head:
atomic.StorePointer(&t.headPK, value.kptr)
case t.list.tail:
atomic.StorePointer(&t.tailPK, value.kptr)
}
// Append already-extracted
// primary key to 0th index.
_ = idx0.add(key, i_item)
+1 -1
View File
@@ -293,7 +293,7 @@ codeberg.org/gruf/go-storage/disk
codeberg.org/gruf/go-storage/internal
codeberg.org/gruf/go-storage/memory
codeberg.org/gruf/go-storage/s3
# codeberg.org/gruf/go-structr v0.9.12
# codeberg.org/gruf/go-structr v0.9.13
## explicit; go 1.24.5
codeberg.org/gruf/go-structr
# codeberg.org/gruf/go-xunsafe v0.0.0-20250809104800-512a9df57d73