mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 22:12:26 -06:00 
			
		
		
		
	
		
			
	
	
		
			160 lines
		
	
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			160 lines
		
	
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								package runners
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"context"
							 | 
						||
| 
								 | 
							
									"sync"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Service provides a means of tracking a single long-running service, provided protected state
							 | 
						||
| 
								 | 
							
								// changes and preventing multiple instances running. Also providing service state information.
							 | 
						||
| 
								 | 
							
								type Service struct {
							 | 
						||
| 
								 | 
							
									state uint32             // 0=stopped, 1=running, 2=stopping
							 | 
						||
| 
								 | 
							
									wait  sync.Mutex         // wait is the mutex used as a single-entity wait-group, i.e. just a "wait" :p
							 | 
						||
| 
								 | 
							
									cncl  context.CancelFunc // cncl is the cancel function set for the current context
							 | 
						||
| 
								 | 
							
									ctx   context.Context    // ctx is the current context for running function (or nil if not running)
							 | 
						||
| 
								 | 
							
									mu    sync.Mutex         // mu protects state changes
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Run will run the supplied function until completion, use given context to propagate cancel.
							 | 
						||
| 
								 | 
							
								// Immediately returns false if the Service is already running, and true after completed run.
							 | 
						||
| 
								 | 
							
								func (svc *Service) Run(fn func(context.Context)) bool {
							 | 
						||
| 
								 | 
							
									// Attempt to start the svc
							 | 
						||
| 
								 | 
							
									ctx, ok := svc.doStart()
							 | 
						||
| 
								 | 
							
									if !ok {
							 | 
						||
| 
								 | 
							
										return false
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									defer func() {
							 | 
						||
| 
								 | 
							
										// unlock single wait
							 | 
						||
| 
								 | 
							
										svc.wait.Unlock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// ensure stopped
							 | 
						||
| 
								 | 
							
										svc.Stop()
							 | 
						||
| 
								 | 
							
									}()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Run user func
							 | 
						||
| 
								 | 
							
									if fn != nil {
							 | 
						||
| 
								 | 
							
										fn(ctx)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return true
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Stop will attempt to stop the service, cancelling the running function's context. Immediately
							 | 
						||
| 
								 | 
							
								// returns false if not running, and true only after Service is fully stopped.
							 | 
						||
| 
								 | 
							
								func (svc *Service) Stop() bool {
							 | 
						||
| 
								 | 
							
									// Attempt to stop the svc
							 | 
						||
| 
								 | 
							
									cncl, ok := svc.doStop()
							 | 
						||
| 
								 | 
							
									if !ok {
							 | 
						||
| 
								 | 
							
										return false
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									defer func() {
							 | 
						||
| 
								 | 
							
										// Get svc lock
							 | 
						||
| 
								 | 
							
										svc.mu.Lock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Wait until stopped
							 | 
						||
| 
								 | 
							
										svc.wait.Lock()
							 | 
						||
| 
								 | 
							
										svc.wait.Unlock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Reset the svc
							 | 
						||
| 
								 | 
							
										svc.ctx = nil
							 | 
						||
| 
								 | 
							
										svc.cncl = nil
							 | 
						||
| 
								 | 
							
										svc.state = 0
							 | 
						||
| 
								 | 
							
										svc.mu.Unlock()
							 | 
						||
| 
								 | 
							
									}()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									cncl() // cancel ctx
							 | 
						||
| 
								 | 
							
									return true
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// doStart will safely set Service state to started, returning a ptr to this context insance.
							 | 
						||
| 
								 | 
							
								func (svc *Service) doStart() (context.Context, bool) {
							 | 
						||
| 
								 | 
							
									// Protect startup
							 | 
						||
| 
								 | 
							
									svc.mu.Lock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if svc.state != 0 /* not stopped */ {
							 | 
						||
| 
								 | 
							
										svc.mu.Unlock()
							 | 
						||
| 
								 | 
							
										return nil, false
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// state started
							 | 
						||
| 
								 | 
							
									svc.state = 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Take our own ptr
							 | 
						||
| 
								 | 
							
									var ctx context.Context
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if svc.ctx == nil {
							 | 
						||
| 
								 | 
							
										// Context required allocating
							 | 
						||
| 
								 | 
							
										svc.ctx, svc.cncl = ContextWithCancel()
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Start the waiter
							 | 
						||
| 
								 | 
							
									svc.wait.Lock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Set our ptr + unlock
							 | 
						||
| 
								 | 
							
									ctx = svc.ctx
							 | 
						||
| 
								 | 
							
									svc.mu.Unlock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return ctx, true
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// doStop will safely set Service state to stopping, returning a ptr to this cancelfunc instance.
							 | 
						||
| 
								 | 
							
								func (svc *Service) doStop() (context.CancelFunc, bool) {
							 | 
						||
| 
								 | 
							
									// Protect stop
							 | 
						||
| 
								 | 
							
									svc.mu.Lock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if svc.state != 1 /* not started */ {
							 | 
						||
| 
								 | 
							
										svc.mu.Unlock()
							 | 
						||
| 
								 | 
							
										return nil, false
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// state stopping
							 | 
						||
| 
								 | 
							
									svc.state = 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Take our own ptr
							 | 
						||
| 
								 | 
							
									// and unlock state
							 | 
						||
| 
								 | 
							
									cncl := svc.cncl
							 | 
						||
| 
								 | 
							
									svc.mu.Unlock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return cncl, true
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Running returns if Service is running (i.e. state NOT stopped / stopping).
							 | 
						||
| 
								 | 
							
								func (svc *Service) Running() bool {
							 | 
						||
| 
								 | 
							
									svc.mu.Lock()
							 | 
						||
| 
								 | 
							
									state := svc.state
							 | 
						||
| 
								 | 
							
									svc.mu.Unlock()
							 | 
						||
| 
								 | 
							
									return (state == 1)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Done returns a channel that's closed when Service.Stop() is called. It is
							 | 
						||
| 
								 | 
							
								// the same channel provided to the currently running service function.
							 | 
						||
| 
								 | 
							
								func (svc *Service) Done() <-chan struct{} {
							 | 
						||
| 
								 | 
							
									var done <-chan struct{}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									svc.mu.Lock()
							 | 
						||
| 
								 | 
							
									switch svc.state {
							 | 
						||
| 
								 | 
							
									// stopped
							 | 
						||
| 
								 | 
							
									// (here we create a new context so that the
							 | 
						||
| 
								 | 
							
									// returned 'done' channel here will still
							 | 
						||
| 
								 | 
							
									// be valid for when Service is next started)
							 | 
						||
| 
								 | 
							
									case 0:
							 | 
						||
| 
								 | 
							
										if svc.ctx == nil {
							 | 
						||
| 
								 | 
							
											// need to allocate new context
							 | 
						||
| 
								 | 
							
											svc.ctx, svc.cncl = ContextWithCancel()
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										done = svc.ctx.Done()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// started
							 | 
						||
| 
								 | 
							
									case 1:
							 | 
						||
| 
								 | 
							
										done = svc.ctx.Done()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// stopping
							 | 
						||
| 
								 | 
							
									case 2:
							 | 
						||
| 
								 | 
							
										done = svc.ctx.Done()
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									svc.mu.Unlock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return done
							 | 
						||
| 
								 | 
							
								}
							 |