mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 23:12:26 -06: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
 | 
						|
}
 |