{"id":18840228,"url":"https://github.com/maxim2266/mvr","last_synced_at":"2025-04-14T07:07:12.247Z","repository":{"id":144202062,"uuid":"146324895","full_name":"maxim2266/mvr","owner":"maxim2266","description":"Minimal Viable Runtime (MVR)","archived":false,"fork":false,"pushed_at":"2022-06-22T15:41:37.000Z","size":22,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-14T07:07:07.168Z","etag":null,"topics":["go","golang","logging","runtime","thread-pool"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxim2266.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-27T16:32:18.000Z","updated_at":"2024-05-05T13:58:58.000Z","dependencies_parsed_at":"2023-06-18T05:15:39.237Z","dependency_job_id":null,"html_url":"https://github.com/maxim2266/mvr","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxim2266%2Fmvr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxim2266%2Fmvr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxim2266%2Fmvr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxim2266%2Fmvr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxim2266","download_url":"https://codeload.github.com/maxim2266/mvr/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248837278,"owners_count":21169374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","logging","runtime","thread-pool"],"created_at":"2024-11-08T02:46:55.613Z","updated_at":"2025-04-14T07:07:12.231Z","avatar_url":"https://github.com/maxim2266.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Minimal Viable Runtime (MVR)\n\n[![GoDoc](https://godoc.org/github.com/maxim2266/mvr?status.svg)](https://godoc.org/github.com/maxim2266/mvr)\n[![Go Report Card](https://goreportcard.com/badge/github.com/maxim2266/mvr)](https://goreportcard.com/report/github.com/maxim2266/mvr)\n[![License: BSD 3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-yellow.svg)](https://opensource.org/licenses/BSD-3-Clause)\n\n## Motivation\nWhen starting a new project there always is a certain amount of low-level code that has to be\nwritten in order to provide for some basic runtime functionality, like top-level context, signal handlers, etc.\nOften this kind of code is either written from scratch, or brought in with an external library.\nProgramming the same functionality from scratch tends to be tedious and error-prone, while external libraries\nmay sometimes be just too heavy for the intended use, introduce significant overhead, or\nimpose an uncomfortable programming model. This project is an attempt to bring a number of frequently used\nruntime functions into one place without introducing another fat API or adding many external dependencies.\n\nThe package adds the following functionality:\n- Top-level context with signal handlers to cancel the context when a signal is delivered;\n- Graceful shutdown to make sure all goroutines have completed before the application terminates;\n- A simple way of running a bunch of tasks on a pool of goroutines;\n- Asynchronous logging, where the actual writing to the log file is done in the background to make sure\nperformance-critical code is not exposed to the i/o latency of writing to the log.\n\n\n## Application entry point\nThe application entry point should be a function of type `func() int`, returning an integer error code\nthat will be passed down to `os.Exit()` when the application terminates. Typically, the entry point is\ninvoked like:\n```go\nfunc main() {\n\tmvr.Run(appMain)\n}\n\nfunc appMain() int { ... }\n```\nThe `mvr.Run()` function never returns.\n\n## Top-level context\nThe top-level context gets initialised (along with the rest of the package) when the application\ninvokes `mvr.Run()` function. The context is accessible via `mvr.Context()` function, with the\nshortcuts `mvr.Done()` and `mvr.Err()` both giving access to the corresponding methods of the top context.\nThe context is cancelled when any of `SIGQUIT`, `SIGINT`, or `SIGTERM` is delivered,\nor when `mvr.Cancel()` function is called. A termination handler can be implemented either as a goroutine\nwaiting on `mvr.Done()` channel, or via the provided convenience function `mvr.OnCancel()`, for example:\n\n```go\nsrv := \u0026http.Server{ ... }\n\n// termination handler\nmvr.OnCancel(10 * time.Second, func(ctx context.Context) {\n\tif err := srv.Shutdown(ctx); err != nil {\n\t\tlog.Println(err)\n\t}\n})\n\n// serve\nreturn srv.ListenAndServe()\n```\n\n## Goroutine invocation\nIn order to ensure graceful shutdown the package keeps track of all goroutines invoked\n(directly or indirectly) through its API. The simplest way to start a goroutine is `mvr.Go()` function that\nprovides functionality similar to the `go` keyword:\n```go\nmvr.Go(func() { ... })\n```\n\nAnother way of running a function in a separate\ngoroutine is `mvr.Async()`, which takes a function to launch, of type `func() error`, and returns\na channel to which the error (if any) will be delivered upon the function completion.\nTypical usage scenario:\n```go\n// start a function\nerrch := mvr.Async(func() error {\n\t// ...\n\treturn err\n})\n\n// do other things here...\n\n// wait for completion and check the error\nif err := \u003c-errch; err != nil {\n\t// handle the error\n}\n\n// another option: simply wait for completion and return (aka Await)\nreturn \u003c-errch\n```\n\n## Goroutine pool\nAs simple example of executing tasks on a pool of goroutines consider the case where a number of files\nneed to be compressed in parallel:\n```go\n// define a function that compresses one file\nfunc compressFile(name string) error { ... }\n\n// a list of files to compress (fixed list for this example)\nfiles := []string{\"aaa.json\", \"bbb.json\", \"ccc.json\", \"ddd.json\"}\n\n// start parallel compression using 2 goroutines\nerrch, cancel := mvr.Parallel(2, mvr.ForEachString(files, compressFile))\n\ndefer cancel()\t// to clean the associated resources afterwards\n\n// do other things...\n\n// retrieve errors (the error channel is closed when the processing is done)\nfor err := range errch {\n\t// process the error\n}\n\n// another option: wait to get the first error (if any) and stop further processing.\n// if there is no error, then the pool runs to completion and the channel gets closed, returning nil\nreturn \u003c-errch\n```\n\nThe second parameter to `mvr.Parallel()` is a channel of tasks, so in a more advanced scenario\nthere may be a separate goroutine continuously supplying tasks to the pool, like in the\nfollowing example adapted from `mvr_test.go`:\n```go\nfunc TestParallelFeed(t *testing.T) {\n\tconst N = 10\t// number of tasks\n\n\tvar res int32\n\n\t// input task channel (in practice should probably have some non-zero size)\n\tinch := make(chan func() error)\n\n\t// start feeder\n\tmvr.Go(func() {\n\t\tdefer close(inch) // don't forget this!\n\n\t\tfor i := 0; i \u003c N; i++ {\n\t\t\tinch \u003c- func() error {\n\t\t\t\tatomic.AddInt32(\u0026res, 1) // just for this example\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t})\n\n\t// launch tasks\n\terrs, cancel := mvr.Parallel(0, inch) // pool of runtime.NumCPU() goroutines\n\n\tdefer cancel()\n\n\t// check errors\n\tfor err := range errs {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\t// etc.\n}\n```\n\nThere is another function, `mvr.ParallelCtx()`, that takes a `context.Context` as its first parameter\nto allow for a user-managed context to control the goroutine pool.\n\n## Logging\nThe package does _not_ replace the logger from the standard library, and it provides no additional\nAPI. Instead, the library replaces the target `io.Writer` to which the logger writes. This _should_\nhave no effect on any other logging layer built on top of the standard `log` package. To use a non-default\nwriter call `log.SetOutput()` before `mvr.Run()`.\n\n## Testing\nFor unit-testing of an application utilising this package the correct initialisation of the runtime can be\nensured by defining `TestMain` function from which all the tests are invoked, typically:\n```go\nfunc TestMain(m *testing.M) {\n\tmvr.Run(m.Run)\n}\n```\n\n## Limitations\n- The package has **no way** of intercepting calls to terminating functions like\n`log.Fatal()` or `os.Exit()`, and no guarantees can be given if any of those functions is invoked.\n- The package replaces the `io.Writer` used by the standard logger, so the writer must not be replaced\nagain after `mvr.Run()` has started;\n- Only goroutines started via the package API are waited on before termination;\n- The package does not handle panics, although certain effort has been made to make sure resources\nare released when a panic is triggered.\n\n#### Project status\nTested on Linux Mint 19.2 using Go version 1.13.3.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxim2266%2Fmvr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxim2266%2Fmvr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxim2266%2Fmvr/lists"}