mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 03:02:26 -06:00 
			
		
		
		
	
		
			
	
	
		
			131 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			131 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								package runners
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"context"
							 | 
						||
| 
								 | 
							
									"errors"
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"sync"
							 | 
						||
| 
								 | 
							
									"time"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// FuncRunner provides a means of managing long-running functions e.g. main logic loops.
							 | 
						||
| 
								 | 
							
								type FuncRunner struct {
							 | 
						||
| 
								 | 
							
									// HandOff is the time after which a blocking function will be considered handed off
							 | 
						||
| 
								 | 
							
									HandOff time.Duration
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// ErrorHandler is the function that errors are passed to when encountered by the
							 | 
						||
| 
								 | 
							
									// provided function. This can be used both for logging, and for error filtering
							 | 
						||
| 
								 | 
							
									ErrorHandler func(err error) error
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									svc Service    // underlying service to manage start/stop
							 | 
						||
| 
								 | 
							
									err error      // last-set error
							 | 
						||
| 
								 | 
							
									mu  sync.Mutex // protects err
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Go will attempt to run 'fn' asynchronously. The provided context is used to propagate requested
							 | 
						||
| 
								 | 
							
								// cancel if FuncRunner.Stop() is called. Any returned error will be passed to FuncRunner.ErrorHandler
							 | 
						||
| 
								 | 
							
								// for filtering/logging/etc. Any blocking functions will be waited on for FuncRunner.HandOff amount of
							 | 
						||
| 
								 | 
							
								// time before considering the function as handed off. Returned bool is success state, i.e. returns true
							 | 
						||
| 
								 | 
							
								// if function is successfully handed off or returns within hand off time with nil error.
							 | 
						||
| 
								 | 
							
								func (r *FuncRunner) Go(fn func(ctx context.Context) error) bool {
							 | 
						||
| 
								 | 
							
									done := make(chan struct{})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									go func() {
							 | 
						||
| 
								 | 
							
										var cancelled bool
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										has := r.svc.Run(func(ctx context.Context) {
							 | 
						||
| 
								 | 
							
											// reset error
							 | 
						||
| 
								 | 
							
											r.mu.Lock()
							 | 
						||
| 
								 | 
							
											r.err = nil
							 | 
						||
| 
								 | 
							
											r.mu.Unlock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											// Run supplied func and set errror if returned
							 | 
						||
| 
								 | 
							
											if err := Run(func() error { return fn(ctx) }); err != nil {
							 | 
						||
| 
								 | 
							
												r.mu.Lock()
							 | 
						||
| 
								 | 
							
												r.err = err
							 | 
						||
| 
								 | 
							
												r.mu.Unlock()
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											// signal done
							 | 
						||
| 
								 | 
							
											close(done)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											// Check if cancelled
							 | 
						||
| 
								 | 
							
											select {
							 | 
						||
| 
								 | 
							
											case <-ctx.Done():
							 | 
						||
| 
								 | 
							
												cancelled = true
							 | 
						||
| 
								 | 
							
											default:
							 | 
						||
| 
								 | 
							
												cancelled = false
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										switch has {
							 | 
						||
| 
								 | 
							
										// returned after starting
							 | 
						||
| 
								 | 
							
										case true:
							 | 
						||
| 
								 | 
							
											r.mu.Lock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											// filter out errors due FuncRunner.Stop() being called
							 | 
						||
| 
								 | 
							
											if cancelled && errors.Is(r.err, context.Canceled) {
							 | 
						||
| 
								 | 
							
												// filter out errors from FuncRunner.Stop() being called
							 | 
						||
| 
								 | 
							
												r.err = nil
							 | 
						||
| 
								 | 
							
											} else if r.err != nil && r.ErrorHandler != nil {
							 | 
						||
| 
								 | 
							
												// pass any non-nil error to set handler
							 | 
						||
| 
								 | 
							
												r.err = r.ErrorHandler(r.err)
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											r.mu.Unlock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// already running
							 | 
						||
| 
								 | 
							
										case false:
							 | 
						||
| 
								 | 
							
											close(done)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// get valid handoff to use
							 | 
						||
| 
								 | 
							
									handoff := r.HandOff
							 | 
						||
| 
								 | 
							
									if handoff < 1 {
							 | 
						||
| 
								 | 
							
										handoff = time.Second * 5
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									select {
							 | 
						||
| 
								 | 
							
									// handed off (long-run successful)
							 | 
						||
| 
								 | 
							
									case <-time.After(handoff):
							 | 
						||
| 
								 | 
							
										return true
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// 'fn' returned, check error
							 | 
						||
| 
								 | 
							
									case <-done:
							 | 
						||
| 
								 | 
							
										return (r.Err() == nil)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Stop will cancel the context supplied to the running function.
							 | 
						||
| 
								 | 
							
								func (r *FuncRunner) Stop() bool {
							 | 
						||
| 
								 | 
							
									return r.svc.Stop()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Err returns the last-set error value.
							 | 
						||
| 
								 | 
							
								func (r *FuncRunner) Err() error {
							 | 
						||
| 
								 | 
							
									r.mu.Lock()
							 | 
						||
| 
								 | 
							
									err := r.err
							 | 
						||
| 
								 | 
							
									r.mu.Unlock()
							 | 
						||
| 
								 | 
							
									return err
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Run will execute the supplied 'fn' catching any panics. Returns either function-returned error or formatted panic.
							 | 
						||
| 
								 | 
							
								func Run(fn func() error) (err error) {
							 | 
						||
| 
								 | 
							
									defer func() {
							 | 
						||
| 
								 | 
							
										if r := recover(); r != nil {
							 | 
						||
| 
								 | 
							
											if e, ok := r.(error); ok {
							 | 
						||
| 
								 | 
							
												// wrap and preserve existing error
							 | 
						||
| 
								 | 
							
												err = fmt.Errorf("caught panic: %w", e)
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
												// simply create new error fromt iface
							 | 
						||
| 
								 | 
							
												err = fmt.Errorf("caught panic: %v", r)
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// run supplied func
							 | 
						||
| 
								 | 
							
									err = fn()
							 | 
						||
| 
								 | 
							
									return
							 | 
						||
| 
								 | 
							
								}
							 |