Skip to content

pior/runnable

Folders and files

NameName
Last commit message
Last commit date

Latest commit

387f46f · Aug 25, 2024

History

99 Commits
Aug 25, 2024
May 16, 2022
May 19, 2020
Apr 22, 2022
Jul 29, 2023
Jul 29, 2023
Aug 25, 2024
Mar 8, 2020
Jul 29, 2023
Jul 29, 2023
May 16, 2022
Jul 29, 2023
Aug 25, 2024
Aug 25, 2024
Jul 29, 2023
Jul 29, 2023
May 19, 2020
Jul 29, 2023
Jul 29, 2023
Jul 29, 2023
Jul 29, 2023
May 16, 2022
Jul 29, 2023
Jul 29, 2023
Jul 29, 2023
Jul 29, 2023
Jul 29, 2023
Jul 29, 2023
Mar 25, 2024

Repository files navigation

Runnable

GoDoc Go Report Card

Tooling to manage the execution of a process based on a Runnable interface:

type Runnable interface {
	Run(context.Context) error
}

And a simpler RunnableFunc interface:

type RunnableFunc func(context.Context) error

Example of an implementation of the command "yes":

func main() {
	runnable.RunFunc(run)
}

func run(ctx context.Context) error {
	for {
		if ctx.Err() != nil {
			return ctx.Err()
		}
		fmt.Println("y")
	}
}

Tools:

Process start and shutdown

To trigger a clean shutdown, a process must react to the termination signals (SIGINT, SIGTERM).

The Run() method is intended to be the process entrypoint:

  • it immediately executes the runnable
  • it cancels the context.Context when a termination signal is received
  • it calls log.Fatal with the error if the runnable returned one

Example:

func main() {
	runnable.Run(
		app.Build(),
	)
}

The RunFunc() method is also provided for convenience.

Restart

The Restart runnable ensure that a component is running, even if it stops or crashes.

Example:

func main() {
	runnable.Run(
		runnable.Restart(
			task.New(),
		),
	)
}

HTTP Server

The HTTPServer runnable starts and gracefully shutdowns a *http.Server.

Example:

func main() {
	server := &http.Server{
		Addr:    "127.0.0.1:8000",
		Handler: http.RedirectHandler("https://go.dev", 307),
	}

	runnable.Run(
		runnable.HTTPServer(server),
	)
}

Dependency Manager

The Manager runnable starts and stops all runnables while respecting the dependency between them. Components with dependencies will be stopped before their dependencies.

Example with three components:

g := runnable.Manager(nil)
g.Add(jobQueue)
g.Add(httpServer, jobQueue) // jobs is a dependency
g.Add(monitor)

runnable.Run(g.Build())
Logs of a demo app
$ go run ./cmd/example
[RUNNABLE] 2020/10/22 22:42:26 INFO manager: main.JobQueue started
[RUNNABLE] 2020/10/22 22:42:26 INFO manager: runnable.httpServer started
[RUNNABLE] 2020/10/22 22:42:26 INFO manager: main.Monitor started
...
^C[RUNNABLE] 2020/10/22 22:42:34 INFO signal: received signal interrupt
[RUNNABLE] 2020/10/22 22:42:34 INFO manager: starting shutdown (context cancelled)
[RUNNABLE] 2020/10/22 22:42:34 INFO manager: runnable.httpServer cancelled
[RUNNABLE] 2020/10/22 22:42:34 INFO manager: main.Monitor cancelled
[RUNNABLE] 2020/10/22 22:42:34 INFO manager: main.Monitor stopped
[RUNNABLE] 2020/10/22 22:42:34 INFO manager: runnable.httpServer stopped
[RUNNABLE] 2020/10/22 22:42:34 INFO manager: main.JobQueue cancelled
[RUNNABLE] 2020/10/22 22:42:34 INFO manager: main.JobQueue stopped
[RUNNABLE] 2020/10/22 22:42:34 INFO manager: shutdown complete

License

The MIT License (MIT)