| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 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 { | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	state uint32     // 0=stopped, 1=running, 2=stopping | 
					
						
							|  |  |  | 	mutex sync.Mutex // mutext protects overall state changes | 
					
						
							|  |  |  | 	wait  sync.Mutex // wait is used as a single-entity wait-group, only ever locked within 'mutex' | 
					
						
							|  |  |  | 	ctx   cancelctx  // ctx is the current context for running function (or nil if not running) | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 18:30:40 +01:00
										 |  |  | // Run will run the supplied function until completion, using given context to propagate cancel. | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | // 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 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		_ = svc.Stop() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	// Run | 
					
						
							|  |  |  | 	fn(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 18:30:40 +01:00
										 |  |  | // GoRun will run the supplied function until completion in a goroutine, using given context to | 
					
						
							|  |  |  | // propagate cancel. Immediately returns boolean indicating success, or that service is already running. | 
					
						
							|  |  |  | func (svc *Service) GoRun(fn func(context.Context)) bool { | 
					
						
							|  |  |  | 	// Attempt to start the svc | 
					
						
							|  |  |  | 	ctx, ok := svc.doStart() | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		defer func() { | 
					
						
							|  |  |  | 			// unlock single wait | 
					
						
							|  |  |  | 			svc.wait.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// ensure stopped | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 			_ = svc.Stop() | 
					
						
							| 
									
										
										
										
											2022-09-28 18:30:40 +01:00
										 |  |  | 		}() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		// Run | 
					
						
							|  |  |  | 		fn(ctx) | 
					
						
							| 
									
										
										
										
											2022-09-28 18:30:40 +01:00
										 |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | // 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 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	ctx, ok := svc.doStop() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		// Get svc lock | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		svc.mutex.Lock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Wait until stopped | 
					
						
							|  |  |  | 		svc.wait.Lock() | 
					
						
							|  |  |  | 		svc.wait.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Reset the svc | 
					
						
							|  |  |  | 		svc.ctx = nil | 
					
						
							|  |  |  | 		svc.state = 0 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		svc.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	// Cancel ctx | 
					
						
							|  |  |  | 	close(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | // While allows you to execute given function guaranteed within current | 
					
						
							|  |  |  | // service state. Please note that this will hold the underlying service | 
					
						
							|  |  |  | // state change mutex open while executing the function. | 
					
						
							|  |  |  | func (svc *Service) While(fn func()) { | 
					
						
							|  |  |  | 	// Protect state change | 
					
						
							|  |  |  | 	svc.mutex.Lock() | 
					
						
							|  |  |  | 	defer svc.mutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Run | 
					
						
							|  |  |  | 	fn() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | // doStart will safely set Service state to started, returning a ptr to this context insance. | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | func (svc *Service) doStart() (cancelctx, bool) { | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	// Protect startup | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	svc.mutex.Lock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if svc.state != 0 /* not stopped */ { | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		svc.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 		return nil, false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// state started | 
					
						
							|  |  |  | 	svc.state = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if svc.ctx == nil { | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		// this will only have been allocated | 
					
						
							|  |  |  | 		// if svc.Done() was already called. | 
					
						
							|  |  |  | 		svc.ctx = make(cancelctx) | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start the waiter | 
					
						
							|  |  |  | 	svc.wait.Lock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	// Take our own ptr | 
					
						
							|  |  |  | 	// and unlock state | 
					
						
							|  |  |  | 	ctx := svc.ctx | 
					
						
							|  |  |  | 	svc.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return ctx, true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // doStop will safely set Service state to stopping, returning a ptr to this cancelfunc instance. | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | func (svc *Service) doStop() (cancelctx, bool) { | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	// Protect stop | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	svc.mutex.Lock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if svc.state != 1 /* not started */ { | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		svc.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 		return nil, false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// state stopping | 
					
						
							|  |  |  | 	svc.state = 2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Take our own ptr | 
					
						
							|  |  |  | 	// and unlock state | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	ctx := svc.ctx | 
					
						
							|  |  |  | 	svc.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	return ctx, true | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Running returns if Service is running (i.e. state NOT stopped / stopping). | 
					
						
							|  |  |  | func (svc *Service) Running() bool { | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	svc.mutex.Lock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	state := svc.state | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	svc.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	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{} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	svc.mutex.Lock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	switch svc.state { | 
					
						
							|  |  |  | 	// stopped | 
					
						
							|  |  |  | 	case 0: | 
					
						
							|  |  |  | 		if svc.ctx == nil { | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 			// here we create a new context so that the | 
					
						
							|  |  |  | 			// returned 'done' channel here will still | 
					
						
							|  |  |  | 			// be valid for when Service is next started. | 
					
						
							|  |  |  | 			svc.ctx = make(cancelctx) | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		done = svc.ctx | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// started | 
					
						
							|  |  |  | 	case 1: | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		done = svc.ctx | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// stopping | 
					
						
							|  |  |  | 	case 2: | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 		done = svc.ctx | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-01-06 10:16:09 +00:00
										 |  |  | 	svc.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2022-01-03 17:37:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return done | 
					
						
							|  |  |  | } |