mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 17:12:25 -05:00 
			
		
		
		
	
		
			
	
	
		
			76 lines
		
	
	
	
		
			1.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			76 lines
		
	
	
	
		
			1.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package runners | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"fmt" | ||
|  | 	"sync" | ||
|  | ) | ||
|  | 
 | ||
|  | // Processable defines a runnable process with error return | ||
|  | // that can be passed to a Processor instance for managed running. | ||
|  | type Processable func() error | ||
|  | 
 | ||
|  | // Processor acts similarly to a sync.Once object, except that it is reusable. After | ||
|  | // the first call to Process(), any further calls before this first has returned will | ||
|  | // block until the first call has returned, and return the same error. This ensures | ||
|  | // that only a single instance of it is ever running at any one time. | ||
|  | type Processor struct { | ||
|  | 	mutex sync.Mutex | ||
|  | 	state uint32 | ||
|  | 	wait  sync.WaitGroup | ||
|  | 	err   *error | ||
|  | } | ||
|  | 
 | ||
|  | // Process will process the given function if first-call, else blocking until | ||
|  | // the first function has returned, returning the same error result. | ||
|  | func (p *Processor) Process(proc Processable) (err error) { | ||
|  | 	// Acquire state lock. | ||
|  | 	p.mutex.Lock() | ||
|  | 
 | ||
|  | 	if p.state != 0 { | ||
|  | 		// Already running. | ||
|  | 		// | ||
|  | 		// Get current err ptr. | ||
|  | 		errPtr := p.err | ||
|  | 
 | ||
|  | 		// Wait until finish. | ||
|  | 		p.mutex.Unlock() | ||
|  | 		p.wait.Wait() | ||
|  | 		return *errPtr | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Reset error ptr. | ||
|  | 	p.err = new(error) | ||
|  | 
 | ||
|  | 	// Set started. | ||
|  | 	p.wait.Add(1) | ||
|  | 	p.state = 1 | ||
|  | 	p.mutex.Unlock() | ||
|  | 
 | ||
|  | 	defer func() { | ||
|  | 		if r := recover(); r != nil { | ||
|  | 			if err != nil { | ||
|  | 				rOld := r // wrap the panic so we don't lose existing returned error | ||
|  | 				r = fmt.Errorf("panic occured after error %q: %v", err.Error(), rOld) | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// Catch any panics and wrap as error. | ||
|  | 			err = fmt.Errorf("caught panic: %v", r) | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Store error. | ||
|  | 		*p.err = err | ||
|  | 
 | ||
|  | 		// Mark done. | ||
|  | 		p.wait.Done() | ||
|  | 
 | ||
|  | 		// Set stopped. | ||
|  | 		p.mutex.Lock() | ||
|  | 		p.state = 0 | ||
|  | 		p.mutex.Unlock() | ||
|  | 	}() | ||
|  | 
 | ||
|  | 	// Run process. | ||
|  | 	err = proc() | ||
|  | 	return | ||
|  | } |