mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 16:02:26 -05: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 | ||
|  | } |