mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-28 13:52:25 -05:00 
			
		
		
		
	[performance] handle emoji refreshes asynchronously when fetched as part of account|status dereferences (#4486)
# Description Updates our dereferencer emoji handling to work asynchronously when going through the route of account or status dereferencing. closes https://codeberg.org/superseriousbusiness/gotosocial/issues/4485 ## Checklist - [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md). - [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat. - [x] I/we have not leveraged AI to create the proposed changes. - [x] I/we have performed a self-review of added code. - [x] I/we have written code that is legible and maintainable by others. - [ ] I/we have commented the added code, particularly in hard-to-understand areas. - [ ] I/we have made any necessary changes to documentation. - [ ] I/we have added tests that cover new code. - [ ] I/we have run tests and they pass locally with the changes. - [x] I/we have run `go fmt ./...` and `golangci-lint run`. Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4486 Co-authored-by: kim <grufwub@gmail.com> Co-committed-by: kim <grufwub@gmail.com>
This commit is contained in:
		
					parent
					
						
							
								baf2c54730
							
						
					
				
			
			
				commit
				
					
						5fd52369c9
					
				
			
		
					 5 changed files with 62 additions and 21 deletions
				
			
		|  | @ -51,6 +51,7 @@ func (d *Dereferencer) GetEmoji( | |||
| 	remoteURL string, | ||||
| 	info media.AdditionalEmojiInfo, | ||||
| 	refresh bool, | ||||
| 	async bool, | ||||
| ) ( | ||||
| 	*gtsmodel.Emoji, | ||||
| 	error, | ||||
|  | @ -66,7 +67,7 @@ func (d *Dereferencer) GetEmoji( | |||
| 
 | ||||
| 	if emoji != nil { | ||||
| 		// This was an existing emoji, pass to refresh func. | ||||
| 		return d.RefreshEmoji(ctx, emoji, info, refresh) | ||||
| 		return d.RefreshEmoji(ctx, emoji, info, refresh, async) | ||||
| 	} | ||||
| 
 | ||||
| 	if domain == "" { | ||||
|  | @ -112,6 +113,7 @@ func (d *Dereferencer) GetEmoji( | |||
| 				info, | ||||
| 			) | ||||
| 		}, | ||||
| 		async, | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
|  | @ -130,6 +132,7 @@ func (d *Dereferencer) RefreshEmoji( | |||
| 	emoji *gtsmodel.Emoji, | ||||
| 	info media.AdditionalEmojiInfo, | ||||
| 	force bool, | ||||
| 	async bool, | ||||
| ) ( | ||||
| 	*gtsmodel.Emoji, | ||||
| 	error, | ||||
|  | @ -162,7 +165,7 @@ func (d *Dereferencer) RefreshEmoji( | |||
| 		// We still want to make sure | ||||
| 		// the emoji is cached. Simply | ||||
| 		// check whether emoji is cached. | ||||
| 		return d.RecacheEmoji(ctx, emoji) | ||||
| 		return d.RecacheEmoji(ctx, emoji, async) | ||||
| 	} | ||||
| 
 | ||||
| 	// Can't refresh local. | ||||
|  | @ -207,6 +210,7 @@ func (d *Dereferencer) RefreshEmoji( | |||
| 				info, | ||||
| 			) | ||||
| 		}, | ||||
| 		async, | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
|  | @ -222,6 +226,7 @@ func (d *Dereferencer) RefreshEmoji( | |||
| func (d *Dereferencer) RecacheEmoji( | ||||
| 	ctx context.Context, | ||||
| 	emoji *gtsmodel.Emoji, | ||||
| 	async bool, | ||||
| ) ( | ||||
| 	*gtsmodel.Emoji, | ||||
| 	error, | ||||
|  | @ -272,23 +277,24 @@ func (d *Dereferencer) RecacheEmoji( | |||
| 				data, | ||||
| 			) | ||||
| 		}, | ||||
| 		async, | ||||
| 	) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // processingEmojiSafely provides concurrency-safe processing of | ||||
| // an emoji with given shortcode+domain. if a copy of the emoji is | ||||
| // not already being processed, the given 'process' callback will | ||||
| // be used to generate new *media.ProcessingEmoji{} instance. | ||||
| // be used to generate new *media.ProcessingEmoji{} instance. async | ||||
| // determines whether to load it immediately, or in the background. | ||||
| func (d *Dereferencer) processEmojiSafely( | ||||
| 	ctx context.Context, | ||||
| 	shortcodeDomain string, | ||||
| 	process func() (*media.ProcessingEmoji, error), | ||||
| 	async bool, | ||||
| ) ( | ||||
| 	emoji *gtsmodel.Emoji, | ||||
| 	err error, | ||||
| ) { | ||||
| 
 | ||||
| 	// Acquire map lock. | ||||
| 	d.derefEmojisMu.Lock() | ||||
| 
 | ||||
|  | @ -309,17 +315,25 @@ func (d *Dereferencer) processEmojiSafely( | |||
| 
 | ||||
| 		// Add processing emoji media to hash map. | ||||
| 		d.derefEmojis[shortcodeDomain] = processing | ||||
| 	} | ||||
| 
 | ||||
| 	// Unlock map. | ||||
| 	unlock() | ||||
| 
 | ||||
| 	if async { | ||||
| 		emoji = processing.LoadAsync(func() { | ||||
| 			// Remove on finish. | ||||
| 			d.derefEmojisMu.Lock() | ||||
| 			delete(d.derefEmojis, shortcodeDomain) | ||||
| 			d.derefEmojisMu.Unlock() | ||||
| 		}) | ||||
| 	} else { | ||||
| 		defer func() { | ||||
| 			// Remove on finish. | ||||
| 			d.derefEmojisMu.Lock() | ||||
| 			delete(d.derefEmojis, shortcodeDomain) | ||||
| 			d.derefEmojisMu.Unlock() | ||||
| 		}() | ||||
| 	} | ||||
| 
 | ||||
| 	// Unlock map. | ||||
| 	unlock() | ||||
| 
 | ||||
| 		// Perform emoji load operation. | ||||
| 		emoji, err = processing.Load(ctx) | ||||
|  | @ -329,6 +343,7 @@ func (d *Dereferencer) processEmojiSafely( | |||
| 			// TODO: in time we should return checkable flags by gtserror.Is___() | ||||
| 			// which can determine if loading error should allow remaining placeholder. | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
|  | @ -364,7 +379,7 @@ func (d *Dereferencer) fetchEmojis( | |||
| 				URI:                  &placeholder.URI, | ||||
| 				ImageRemoteURL:       &placeholder.ImageRemoteURL, | ||||
| 				ImageStaticRemoteURL: &placeholder.ImageStaticRemoteURL, | ||||
| 			}, force) | ||||
| 			}, force, true) | ||||
| 			if err != nil { | ||||
| 				log.Errorf(ctx, "error refreshing emoji: %v", err) | ||||
| 
 | ||||
|  | @ -396,6 +411,7 @@ func (d *Dereferencer) fetchEmojis( | |||
| 				ImageStaticRemoteURL: &placeholder.ImageStaticRemoteURL, | ||||
| 			}, | ||||
| 			false, | ||||
| 			true, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			if emoji == nil { | ||||
|  |  | |||
|  | @ -54,6 +54,7 @@ func (suite *EmojiTestSuite) TestDereferenceEmojiBlocking() { | |||
| 			VisibleInPicker:      &emojiVisibleInPicker, | ||||
| 		}, | ||||
| 		false, | ||||
| 		false, | ||||
| 	) | ||||
| 	suite.NoError(err) | ||||
| 	suite.NotNil(emoji) | ||||
|  |  | |||
|  | @ -44,11 +44,6 @@ type ProcessingEmoji struct { | |||
| 	mgr       *Manager          // mgr instance (access to db / storage) | ||||
| } | ||||
| 
 | ||||
| // ID returns the ID of the underlying emoji. | ||||
| func (p *ProcessingEmoji) ID() string { | ||||
| 	return p.emoji.ID // immutable, safe outside mutex. | ||||
| } | ||||
| 
 | ||||
| // LoadEmoji blocks until the static and fullsize image has been processed, and then returns the completed emoji. | ||||
| func (p *ProcessingEmoji) Load(ctx context.Context) (*gtsmodel.Emoji, error) { | ||||
| 	emoji, done, err := p.load(ctx) | ||||
|  | @ -63,6 +58,33 @@ func (p *ProcessingEmoji) Load(ctx context.Context) (*gtsmodel.Emoji, error) { | |||
| 	return emoji, err | ||||
| } | ||||
| 
 | ||||
| func (p *ProcessingEmoji) LoadAsync(deferred func()) *gtsmodel.Emoji { | ||||
| 	p.mgr.state.Workers.Dereference.Queue.Push(func(ctx context.Context) { | ||||
| 		if deferred != nil { | ||||
| 			defer deferred() | ||||
| 		} | ||||
| 
 | ||||
| 		if _, _, err := p.load(ctx); err != nil { | ||||
| 			log.Errorf(ctx, "error loading emoji: %v", err) | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	// Placeholder returns a copy of internally stored processing placeholder, | ||||
| 	// returning only the fields that may be known *before* completion, | ||||
| 	// and as such all fields which are safe to concurrently read. | ||||
| 	placeholder := new(gtsmodel.Emoji) | ||||
| 	placeholder.ID = p.emoji.ID | ||||
| 	placeholder.Shortcode = p.emoji.Shortcode | ||||
| 	placeholder.Domain = p.emoji.Domain | ||||
| 	placeholder.Cached = new(bool) | ||||
| 	placeholder.ImageRemoteURL = p.emoji.ImageRemoteURL | ||||
| 	placeholder.ImageStaticRemoteURL = p.emoji.ImageStaticRemoteURL | ||||
| 	placeholder.Disabled = p.emoji.Disabled | ||||
| 	placeholder.VisibleInPicker = p.emoji.VisibleInPicker | ||||
| 	placeholder.CategoryID = p.emoji.CategoryID | ||||
| 	return placeholder | ||||
| } | ||||
| 
 | ||||
| // load is the package private form of load() that is wrapped to catch context canceled. | ||||
| func (p *ProcessingEmoji) load(ctx context.Context) ( | ||||
| 	emoji *gtsmodel.Emoji, | ||||
|  |  | |||
|  | @ -293,6 +293,7 @@ func (p *Processor) emojiUpdateCopy( | |||
| 	// Ensure target emoji is locally cached. | ||||
| 	target, err := p.federator.RecacheEmoji(ctx, | ||||
| 		target, | ||||
| 		false, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		err := gtserror.Newf("error recaching emoji %s: %w", target.ImageRemoteURL, err) | ||||
|  |  | |||
|  | @ -247,6 +247,7 @@ func (p *Processor) getEmojiContent( | |||
| 	emoji, err = p.federator.RecacheEmoji( | ||||
| 		ctx, | ||||
| 		emoji, | ||||
| 		false, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		err := gtserror.Newf("error recaching emoji: %w", err) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue