mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 04:12:25 -05:00 
			
		
		
		
	[bugfix] Server and closer bugfixes (#839)
* defer streaming from storage more forcefully * shut down Server more gracefully * use command context as server BaseContext
This commit is contained in:
		
					parent
					
						
							
								c1585d5f8a
							
						
					
				
			
			
				commit
				
					
						3777f5c684
					
				
			
		
					 4 changed files with 47 additions and 29 deletions
				
			
		|  | @ -85,20 +85,22 @@ func (m *FileServer) ServeFile(c *gin.Context) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if content.URL != nil { |  | ||||||
| 		c.Redirect(http.StatusFound, content.URL.String()) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	defer func() { | 	defer func() { | ||||||
| 		// if the content is a ReadCloser, close it when we're done | 		// if the content is a ReadCloser (ie., it's streamed from storage), close it when we're done | ||||||
|  | 		if content.Content != nil { | ||||||
| 			if closer, ok := content.Content.(io.ReadCloser); ok { | 			if closer, ok := content.Content.(io.ReadCloser); ok { | ||||||
| 				if err := closer.Close(); err != nil { | 				if err := closer.Close(); err != nil { | ||||||
| 					log.Errorf("ServeFile: error closing readcloser: %s", err) | 					log.Errorf("ServeFile: error closing readcloser: %s", err) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
|  | 	if content.URL != nil { | ||||||
|  | 		c.Redirect(http.StatusFound, content.URL.String()) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// TODO: if the requester only accepts text/html we should try to serve them *something*. | 	// TODO: if the requester only accepts text/html we should try to serve them *something*. | ||||||
| 	// This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will | 	// This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will | ||||||
| 	// attempt to look up the content to provide a preview of the link, and they ask for text/html. | 	// attempt to look up the content to provide a preview of the link, and they ask for text/html. | ||||||
|  |  | ||||||
|  | @ -121,6 +121,12 @@ func (p *ProcessingEmoji) loadStatic(ctx context.Context) error { | ||||||
| 			return p.err | 			return p.err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		defer func() { | ||||||
|  | 			if err := stored.Close(); err != nil { | ||||||
|  | 				log.Errorf("loadStatic: error closing stored full size: %s", err) | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 
 | ||||||
| 		// we haven't processed a static version of this emoji yet so do it now | 		// we haven't processed a static version of this emoji yet so do it now | ||||||
| 		static, err := deriveStaticEmoji(stored, p.emoji.ImageContentType) | 		static, err := deriveStaticEmoji(stored, p.emoji.ImageContentType) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -129,12 +135,6 @@ func (p *ProcessingEmoji) loadStatic(ctx context.Context) error { | ||||||
| 			return p.err | 			return p.err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if err := stored.Close(); err != nil { |  | ||||||
| 			p.err = fmt.Errorf("loadStatic: error closing stored full size: %s", err) |  | ||||||
| 			atomic.StoreInt32(&p.staticState, int32(errored)) |  | ||||||
| 			return p.err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// put the static in storage | 		// put the static in storage | ||||||
| 		if err := p.storage.Put(ctx, p.emoji.ImageStaticPath, static.small); err != nil { | 		if err := p.storage.Put(ctx, p.emoji.ImageStaticPath, static.small); err != nil { | ||||||
| 			p.err = fmt.Errorf("loadStatic: error storing static: %s", err) | 			p.err = fmt.Errorf("loadStatic: error storing static: %s", err) | ||||||
|  |  | ||||||
|  | @ -145,9 +145,7 @@ func (p *ProcessingMedia) loadThumb(ctx context.Context) error { | ||||||
| 			return p.err | 			return p.err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// whatever happens, close the stream when we're done |  | ||||||
| 		defer func() { | 		defer func() { | ||||||
| 			log.Tracef("loadThumb: closing stored stream %s", p.attachment.URL) |  | ||||||
| 			if err := stored.Close(); err != nil { | 			if err := stored.Close(); err != nil { | ||||||
| 				log.Errorf("loadThumb: error closing stored full size: %s", err) | 				log.Errorf("loadThumb: error closing stored full size: %s", err) | ||||||
| 			} | 			} | ||||||
|  | @ -210,6 +208,12 @@ func (p *ProcessingMedia) loadFullSize(ctx context.Context) error { | ||||||
| 			return p.err | 			return p.err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		defer func() { | ||||||
|  | 			if err := stored.Close(); err != nil { | ||||||
|  | 				log.Errorf("loadFullSize: error closing stored full size: %s", err) | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 
 | ||||||
| 		// decode the image | 		// decode the image | ||||||
| 		ct := p.attachment.File.ContentType | 		ct := p.attachment.File.ContentType | ||||||
| 		switch ct { | 		switch ct { | ||||||
|  | @ -227,12 +231,6 @@ func (p *ProcessingMedia) loadFullSize(ctx context.Context) error { | ||||||
| 			return p.err | 			return p.err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if err := stored.Close(); err != nil { |  | ||||||
| 			p.err = fmt.Errorf("loadFullSize: error closing stored full size: %s", err) |  | ||||||
| 			atomic.StoreInt32(&p.fullSizeState, int32(errored)) |  | ||||||
| 			return p.err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// set appropriate fields on the attachment based on the image we derived | 		// set appropriate fields on the attachment based on the image we derived | ||||||
| 		p.attachment.FileMeta.Original = gtsmodel.Original{ | 		p.attachment.FileMeta.Original = gtsmodel.Original{ | ||||||
| 			Width:  decoded.width, | 			Width:  decoded.width, | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ package router | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"net" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | @ -37,6 +38,7 @@ const ( | ||||||
| 	writeTimeout      = 30 * time.Second | 	writeTimeout      = 30 * time.Second | ||||||
| 	idleTimeout       = 30 * time.Second | 	idleTimeout       = 30 * time.Second | ||||||
| 	readHeaderTimeout = 30 * time.Second | 	readHeaderTimeout = 30 * time.Second | ||||||
|  | 	shutdownTimeout   = 30 * time.Second | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Router provides the REST interface for gotosocial, using gin. | // Router provides the REST interface for gotosocial, using gin. | ||||||
|  | @ -128,7 +130,16 @@ func (r *router) Start() { | ||||||
| 
 | 
 | ||||||
| // Stop shuts down the router nicely | // Stop shuts down the router nicely | ||||||
| func (r *router) Stop(ctx context.Context) error { | func (r *router) Stop(ctx context.Context) error { | ||||||
| 	return r.srv.Shutdown(ctx) | 	log.Infof("shutting down http router with %s grace period", shutdownTimeout) | ||||||
|  | 	timeout, cancel := context.WithTimeout(ctx, shutdownTimeout) | ||||||
|  | 	defer cancel() | ||||||
|  | 
 | ||||||
|  | 	if err := r.srv.Shutdown(timeout); err != nil { | ||||||
|  | 		return fmt.Errorf("error shutting down http router: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	log.Info("http router closed connections and shut down gracefully") | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New returns a new Router with the specified configuration. | // New returns a new Router with the specified configuration. | ||||||
|  | @ -174,17 +185,24 @@ func New(ctx context.Context, db db.DB) (Router, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// create the http server here, passing the gin engine as handler | 	// use the passed-in command context as the base context for the server, | ||||||
|  | 	// since we'll never want the server to live past the command anyway | ||||||
|  | 	baseCtx := func(_ net.Listener) context.Context { | ||||||
|  | 		return ctx | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	bindAddress := config.GetBindAddress() | 	bindAddress := config.GetBindAddress() | ||||||
| 	port := config.GetPort() | 	port := config.GetPort() | ||||||
| 	listen := fmt.Sprintf("%s:%d", bindAddress, port) | 	addr := fmt.Sprintf("%s:%d", bindAddress, port) | ||||||
|  | 
 | ||||||
| 	s := &http.Server{ | 	s := &http.Server{ | ||||||
| 		Addr:              listen, | 		Addr:              addr, | ||||||
| 		Handler:           engine, | 		Handler:           engine, // use gin engine as handler | ||||||
| 		ReadTimeout:       readTimeout, | 		ReadTimeout:       readTimeout, | ||||||
|  | 		ReadHeaderTimeout: readHeaderTimeout, | ||||||
| 		WriteTimeout:      writeTimeout, | 		WriteTimeout:      writeTimeout, | ||||||
| 		IdleTimeout:       idleTimeout, | 		IdleTimeout:       idleTimeout, | ||||||
| 		ReadHeaderTimeout: readHeaderTimeout, | 		BaseContext:       baseCtx, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// We need to spawn the underlying server slightly differently depending on whether lets encrypt is enabled or not. | 	// We need to spawn the underlying server slightly differently depending on whether lets encrypt is enabled or not. | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue