mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 18:42:26 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			130 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			130 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
 | |
| }
 |