mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 12:02:26 -05:00 
			
		
		
		
	* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values 🤦
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
		
	
			
		
			
				
	
	
		
			198 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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 timeline
 | |
| 
 | |
| import (
 | |
| 	"maps"
 | |
| 	"sync/atomic"
 | |
| )
 | |
| 
 | |
| // StatusTimelines is a concurrency safe map of StatusTimeline{}
 | |
| // objects, optimizing *very heavily* for reads over writes.
 | |
| type StatusTimelines struct {
 | |
| 	ptr atomic.Pointer[map[string]*StatusTimeline] // ronly except by CAS
 | |
| 	cap int
 | |
| }
 | |
| 
 | |
| // Init stores the given argument(s) such that any created StatusTimeline{}
 | |
| // objects by MustGet() will initialize them with the given arguments.
 | |
| func (t *StatusTimelines) Init(cap int) { t.cap = cap }
 | |
| 
 | |
| // MustGet will attempt to fetch StatusTimeline{} stored under key, else creating one.
 | |
| func (t *StatusTimelines) MustGet(key string) *StatusTimeline {
 | |
| 	var tt *StatusTimeline
 | |
| 
 | |
| 	for {
 | |
| 		// Load current ptr.
 | |
| 		cur := t.ptr.Load()
 | |
| 
 | |
| 		// Get timeline map to work on.
 | |
| 		var m map[string]*StatusTimeline
 | |
| 
 | |
| 		if cur != nil {
 | |
| 			// Look for existing
 | |
| 			// timeline in cache.
 | |
| 			tt = (*cur)[key]
 | |
| 			if tt != nil {
 | |
| 				return tt
 | |
| 			}
 | |
| 
 | |
| 			// Get clone of current
 | |
| 			// before modifications.
 | |
| 			m = maps.Clone(*cur)
 | |
| 		} else {
 | |
| 			// Allocate new timeline map for below.
 | |
| 			m = make(map[string]*StatusTimeline)
 | |
| 		}
 | |
| 
 | |
| 		if tt == nil {
 | |
| 			// Allocate new timeline.
 | |
| 			tt = new(StatusTimeline)
 | |
| 			tt.Init(t.cap)
 | |
| 		}
 | |
| 
 | |
| 		// Store timeline
 | |
| 		// in new map.
 | |
| 		m[key] = tt
 | |
| 
 | |
| 		// Attempt to update the map ptr.
 | |
| 		if !t.ptr.CompareAndSwap(cur, &m) {
 | |
| 
 | |
| 			// We failed the
 | |
| 			// CAS, reloop.
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Successfully inserted
 | |
| 		// new timeline model.
 | |
| 		return tt
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Delete will delete the stored StatusTimeline{} under key, if any.
 | |
| func (t *StatusTimelines) Delete(key string) {
 | |
| 	for {
 | |
| 		// Load current ptr.
 | |
| 		cur := t.ptr.Load()
 | |
| 
 | |
| 		// Check for empty map / not in map.
 | |
| 		if cur == nil || (*cur)[key] == nil {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		// Get clone of current
 | |
| 		// before modifications.
 | |
| 		m := maps.Clone(*cur)
 | |
| 
 | |
| 		// Delete ID.
 | |
| 		delete(m, key)
 | |
| 
 | |
| 		// Attempt to update the map ptr.
 | |
| 		if !t.ptr.CompareAndSwap(cur, &m) {
 | |
| 
 | |
| 			// We failed the
 | |
| 			// CAS, reloop.
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Successfully
 | |
| 		// deleted ID.
 | |
| 		return
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // RemoveByStatusIDs calls RemoveByStatusIDs() for each of the stored StatusTimeline{}s.
 | |
| func (t *StatusTimelines) RemoveByStatusIDs(statusIDs ...string) {
 | |
| 	if p := t.ptr.Load(); p != nil {
 | |
| 		for _, tt := range *p {
 | |
| 			tt.RemoveByStatusIDs(statusIDs...)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // RemoveByAccountIDs calls RemoveByAccountIDs() for each of the stored StatusTimeline{}s.
 | |
| func (t *StatusTimelines) RemoveByAccountIDs(accountIDs ...string) {
 | |
| 	if p := t.ptr.Load(); p != nil {
 | |
| 		for _, tt := range *p {
 | |
| 			tt.RemoveByAccountIDs(accountIDs...)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnprepareByStatusIDs calls UnprepareByStatusIDs() for each of the stored StatusTimeline{}s.
 | |
| func (t *StatusTimelines) UnprepareByStatusIDs(statusIDs ...string) {
 | |
| 	if p := t.ptr.Load(); p != nil {
 | |
| 		for _, tt := range *p {
 | |
| 			tt.UnprepareByStatusIDs(statusIDs...)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnprepareByAccountIDs calls UnprepareByAccountIDs() for each of the stored StatusTimeline{}s.
 | |
| func (t *StatusTimelines) UnprepareByAccountIDs(accountIDs ...string) {
 | |
| 	if p := t.ptr.Load(); p != nil {
 | |
| 		for _, tt := range *p {
 | |
| 			tt.UnprepareByAccountIDs(accountIDs...)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Unprepare attempts to call UnprepareAll() for StatusTimeline{} under key.
 | |
| func (t *StatusTimelines) Unprepare(key string) {
 | |
| 	if p := t.ptr.Load(); p != nil {
 | |
| 		if tt := (*p)[key]; tt != nil {
 | |
| 			tt.UnprepareAll()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnprepareAll calls UnprepareAll() for each of the stored StatusTimeline{}s.
 | |
| func (t *StatusTimelines) UnprepareAll() {
 | |
| 	if p := t.ptr.Load(); p != nil {
 | |
| 		for _, tt := range *p {
 | |
| 			tt.UnprepareAll()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Trim calls Trim() for each of the stored StatusTimeline{}s.
 | |
| func (t *StatusTimelines) Trim() {
 | |
| 	if p := t.ptr.Load(); p != nil {
 | |
| 		for _, tt := range *p {
 | |
| 			tt.Trim()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Clear attempts to call Clear() for StatusTimeline{} under key.
 | |
| func (t *StatusTimelines) Clear(key string) {
 | |
| 	if p := t.ptr.Load(); p != nil {
 | |
| 		if tt := (*p)[key]; tt != nil {
 | |
| 			tt.Clear()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ClearAll calls Clear() for each of the stored StatusTimeline{}s.
 | |
| func (t *StatusTimelines) ClearAll() {
 | |
| 	if p := t.ptr.Load(); p != nil {
 | |
| 		for _, tt := range *p {
 | |
| 			tt.Clear()
 | |
| 		}
 | |
| 	}
 | |
| }
 |