{"id":17333471,"url":"https://github.com/damianh/littleforker","last_synced_at":"2025-08-13T14:34:05.446Z","repository":{"id":35925502,"uuid":"104918001","full_name":"damianh/LittleForker","owner":"damianh","description":"A .NET utility library to spawn, supervise and (optionally) cleanly shut down child processes.","archived":false,"fork":false,"pushed_at":"2024-06-13T13:54:02.000Z","size":1422,"stargazers_count":125,"open_issues_count":2,"forks_count":10,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-12-13T03:50:04.233Z","etag":null,"topics":["dotnet-standard","named-pipes","process-supervision","signalling"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/damianh.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":"2017-09-26T17:38:56.000Z","updated_at":"2024-06-28T11:48:35.000Z","dependencies_parsed_at":"2024-06-13T16:29:50.554Z","dependency_job_id":null,"html_url":"https://github.com/damianh/LittleForker","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damianh%2FLittleForker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damianh%2FLittleForker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damianh%2FLittleForker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/damianh%2FLittleForker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/damianh","download_url":"https://codeload.github.com/damianh/LittleForker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229487987,"owners_count":18080846,"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":["dotnet-standard","named-pipes","process-supervision","signalling"],"created_at":"2024-10-15T15:01:06.266Z","updated_at":"2024-12-15T00:10:02.292Z","avatar_url":"https://github.com/damianh.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Little Forker\n\n[![CI](https://github.com/damianh/LittleForker/workflows/CI/badge.svg)](https://github.com/damianh/LittleForker/actions?query=workflow%3ACI)\n[![NuGet](https://img.shields.io/nuget/v/LittleForker.svg)](https://www.nuget.org/packages/LittleForker)\n[![feedz.io](https://img.shields.io/badge/endpoint.svg?url=https%3A%2F%2Ff.feedz.io%2Fdh%2Foss-ci%2Fshield%2FLittleForker%2Flatest)](https://f.feedz.io/dh/oss-ci/nuget/index.json)\n\nA utility to aid in the launching and supervision of processes. The original use\ncase is installing a single service who then spawns other processes as part of a\nmulti-process application.\n\n## Features\n\n  1. **ProcessExitedHelper**: a helper around `Process.Exited` with some additional\n     logging and event raising if the process has already exited or not found.\n\n  2. **ProcessSupervisor**: allows a parent process to launch a child process\n     and lifecycle is represented as a state machine. Supervisors can participate\n     in co-operative shutdown if supported by the child process.\n\n  3. **CooperativeShutdown**: allows a process to listen for a shutdown signal\n     over a NamedPipe for a parent process to instruct a process to shutdown.\n\n## Installation\n\n```bash\ndotnet add package LittleForker\n```\n\nCI packages are on personal feed: https://www.myget.org/F/dh/api/v3/index.json\n\n## Using\n\n### 1. ProcessExitedHelper\n\nThis helper is typically used by \"child\" processes to monitor a \"parent\" process\nso that it exits itself when the parent exits. It's also safe guard in\nco-operative shut down if the parent failed to signal correctly (i.e. it\ncrashed).\n\nIt wraps `Process.Exited` with some additional behaviour:\n\n- Raises the event if the process is not found.\n- Raises the event if the process has already exited which would otherwise\n  result in an `InvalidOperationException`\n- Logging.\n\nThis is something simple to implement in your own code so you may\nconsider copying it if you don't want a dependency on `LittleForker`.\n\nTypically you will tell a process to monitor another process by passing in the\nother process's Id as a command line argument. Something like:\n\n```bash\n.\\MyApp --ParentProcessID=12345\n```\n\nHere we extract the CLI arg using `Microsoft.Extensions.Configuration`, watch\nfor a parent to exit and exit ourselves when that happens.\n\n```csharp\nvar configRoot = new ConfigurationBuilder()\n   .AddCommandLine(args)\n   .Build();\n\nvar parentPid = _configRoot.GetValue\u003cint\u003e(\"ParentProcessId\");\nusing(new ProcessExitedHelper(parentPid, exitedHelper =\u003e Environment.Exit(0)))\n{\n   // Rest of application\n}\n```\n\n`Environment.Exit(0)` is quite an abrupt way to shut town; you may want to\nhandle things more gracefully such as flush data, cancel requests in flight etc.\nFor an example, see\n[NotTerminatingProcess](src/NonTerminatingProcess/Program.cs) `Run()` that uses\na `CancellationTokenSource`.\n\n### 2. ProcessSupervisor\n\nProcess supervisor launches a process and tracks it's lifecycle that is represented by a\nstate machine. Typically use case is a \"parent\" processes launching one or more \"child\"\nprocesses.\n\nThere are two types of processes that are supported:\n\n1. **Self-Terminating** where the process will exit of it's own accord.\n2. **Non-Terminating** is a process that never shut down unless it is\n   signalled to do so (if it participates in co-operative shutdown) _or_ is killed.\n\nA process's state is represented by `ProcessSupervisor.State` enum:\n\n- NotStarted,\n- Running,\n- StartFailed,\n- Stopping,\n- ExitedSuccessfully,\n- ExitedWithError,\n- ExitedUnexpectedly,\n- ExitedKilled\n\n... with the transitions between them described with this state machine depending\nwhether self-terminating or non-terminating:\n\n![statemachine](state-machine.png)\n\nTypically, you will want to launch a process and wait until it is in a specific\nstate before continuing (or handle errors).\n\n```csharp\n// create the supervisor\nvar supervisor = new ProcessSupervisor(\n   processRunType: ProcessRunType.NonTerminating, // Expected to be a process that doesn't stop\n   workingDirectory: Environment.CurrentDirectory,\n   processPath: \"dotnet\",\n   arguments: \"./LongRunningProcess/LongRunningProcess.dll\");\n\n// attach to events\nsupervisor.StateChanged += state =\u003e { /* handle state changes */ };\nsupervisor.OutputDataReceived += s =\u003e { /* console output */ }\n\n// start the supervisor which will launch the process\nawait supervisor.Start();\n\n// ... some time later\n// attempts a co-operative shutdown with a timeout of 3\n// seconds otherwise kills the process\n\nawait supervisor.Stop(TimeSpan.FromSeconds(3));\n```\n\nWith an async extension, it is possible to await a supervisor state:\n\n```csharp\nvar exitedSuccessfully = supervisor.WhenStateIs(ProcessSupervisor.State.ExitedSuccessfully);\nawait supervisor.Start();\nawait Task.WhenAny(exitedSuccessfully).\n```\n\nYou can also leverage tasks to combine waiting for various expected states:\n\n```csharp\nvar startFailed = supervisor.WhenStateIs(ProcessSupervisor.State.StartFailed);\nvar exitedSuccessfully = supervisor.WhenStateIs(ProcessSupervisor.State.ExitedSuccessfully);\nvar exitedWithError = supervisor.WhenStateIs(ProcessSupervisor.State.ExitedWithError);\n\nsupervisor.Start();\n\nvar result = await Task.WhenAny(startFailed, exitedSuccessfully, exitedWithError);\nif(result == startFailed)\n{\n   Log.Error(supervisor.OnStartException, $\"Process start failed {supervisor.OnStartException.Message}\")\n}\n// etc.\n```\n\n### CooperativeShutdown\n\nCooperative shutdown allows a \"parent\" process to instruct a \"child\" process to\nshut down. Different to `SIGTERM` and `Process.Kill()` in that it allows a child\nto acknowledge receipt of the request and shut down cleanly (and fast!). Combined with\n`Supervisor.Stop()` a parent can send the signal and then wait for `ExitedSuccessfully`.\n\nThe inter-process communication is done via named pipes where the pipe name is\nof the format `LittleForker-{processId}`\n\nFor a \"child\" process to be able receive co-operative shut down requests it uses\n`CooperativeShutdown.Listen()` to listen on a named pipe. Handling signals should\nbe fast operations and are typically implemented by signalling to another mechanism\nto start cleanly shutting down:\n\n```csharp\nvar shutdown = new CancellationTokenSource();\nusing(await CooperativeShutdown.Listen(() =\u003e shutdown.Cancel())\n{\n   // rest of application checks shutdown token for co-operative\n   // cancellation. See MSDN for details.\n}\n```\n\nFor a \"parent\" process to be able to signal:\n\n```csharp\nawait CooperativeShutdown.SignalExit(childProcessId);\n```\n\nThis is used in `ProcessSupervisor` so if your parent process is using that, then you\ntypically won't be using this explicitly.\n\n## Building\n\nWith docker which is same as CI:\n\n- Run `build.cmd`/`build.sh` to compile, run tests and build package.\n\nLocal build which requires .NET Core SDKs 2.1, 3.1 and .NET 5.0:\n\n- Run `build-local.cmd`/`build-local.sh` to compile, run tests and build package.\n\n## Credits \u0026 Feedback\n\n[@randompunter](https://twitter.com/randompunter) for feedback.\n\nHat tip to [@markrendle](https://twitter.com/markrendle) for the project name.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdamianh%2Flittleforker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdamianh%2Flittleforker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdamianh%2Flittleforker/lists"}