{"id":15056355,"url":"https://github.com/erlangsters/spawn-mode","last_synced_at":"2026-02-02T08:33:18.447Z","repository":{"id":224581726,"uuid":"763241764","full_name":"erlangsters/spawn-mode","owner":"erlangsters","description":"An Erlang process spawner API that solves a fundamental design issue.","archived":false,"fork":false,"pushed_at":"2025-06-27T11:20:11.000Z","size":915,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-02T13:13:22.709Z","etag":null,"topics":["beam","erlang","orchestrator","process","reactor","spawn","worker"],"latest_commit_sha":null,"homepage":"https://about.erlangsters.org","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erlangsters.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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,"zenodo":null}},"created_at":"2024-02-25T22:43:14.000Z","updated_at":"2025-06-27T10:40:59.000Z","dependencies_parsed_at":"2024-04-04T18:30:29.505Z","dependency_job_id":"9fcc2320-1dd8-4aa2-8728-ce7f46c6aabd","html_url":"https://github.com/erlangsters/spawn-mode","commit_stats":null,"previous_names":["erlangsters/process-spawner","erlangsters/spawn-mode"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/erlangsters/spawn-mode","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlangsters%2Fspawn-mode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlangsters%2Fspawn-mode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlangsters%2Fspawn-mode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlangsters%2Fspawn-mode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erlangsters","download_url":"https://codeload.github.com/erlangsters/spawn-mode/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlangsters%2Fspawn-mode/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29008020,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-02T08:20:25.892Z","status":"ssl_error","status_checked_at":"2026-02-02T08:20:04.345Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["beam","erlang","orchestrator","process","reactor","spawn","worker"],"created_at":"2024-09-24T21:50:05.750Z","updated_at":"2026-02-02T08:33:18.429Z","avatar_url":"https://github.com/erlangsters.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spawn Mode\n\n[![Erlangsters Repository](https://img.shields.io/badge/erlangsters-spawn--mode-%23a90432)](https://github.com/erlangsters/spawn-mode)\n![Supported Erlang/OTP Versions](https://img.shields.io/badge/erlang%2Fotp-27%7C28-%23a90432)\n![Current Version](https://img.shields.io/badge/version-0.0.1-%23354052)\n![License](https://img.shields.io/github/license/erlangsters/spawn-mode)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/erlangsters/spawn-mode/workflow.yml)](https://github.com/erlangsters/spawn-mode/actions/workflows/workflow.yml)\n[![Documentation Link](https://img.shields.io/badge/documentation-available-yellow)](http://erlangsters.github.io/spawn-mode/)\n\nThe spawn mode solves a fundamental recurrent issue in the design of your\nAPIs - the start functions.\n\nThere are many versions of the `spawn` functions (as of OTP 27, up to 21\nvariants) and exposing this variety to the end user is difficult. It solves the\nproblem by introducing a spawn mode which makes it easy to not tie a start\nfunction to a single spawn variant.\n\n\u003e The re-written OTP behaviors ([worker](https://github.com/erlangsters/worker),\n[orchestrator](https://github.com/erlangsters/orchestrator),\n[reactor](https://github.com/erlangsters/reactor)) for Erlang distributions\nwithout the OTP framework make use of this spawn mode. They are great example that\nshows how it was designed to be used.\n\nXXX: OTP 23 has introduced the `spawn_request/x` variation. It's not supported\n     yet.\n\nWritten by the Erlangsters [community](https://about.erlangsters.org/) and\nreleased under the MIT [license](https://opensource.org/license/mit).\n\n## Getting started\n\nWhen you implement a process behavior in a module, you will (almost always)\nprovide a start function which will invariably spawn a process at some point.\n\n```erlang\nstart(foo, bar) -\u003e\n  % Do a few things here...\n  Pid = spawn_link(fun loop/x).\n  % ...and perhaps some more things here.\n  {ok, Pid}.\n```\n\nHowever, by doing so, you restrict your process behavior to be started with\nonly one spawn method.\n\nBecause of that, some people will either leave it this way (which is fine if\nthe module is not shared, but then you have an incomplete API that will perhaps\nneed an update later) or resort to adding more start functions.\n\n```erlang\n-export([\n    start/2,\n    start_link/2,\n    start_monitor/2\n]).\n```\n\nThis is how OTP behaviors solve this design issue (see the API of the\n`gen_server` behavior for an example).\n\nSo, how is that bad? Well, those functions are not smart, they just wrap a\ndifferent spawning method, and can be very inconvenient to write (especially if\nthere are some initialization steps inside). Therefore they can be error-prone\nand will pollute the API of your modules. But most annoyingly, it's a\nunsatisfying solution that involves duplication, and which has to be replicated\nfor every single process behavior modules you will ever write. It's also\nvulnerable to more potential spawn methods later introduced to the language.\n\nThis is when the spawn mode comes into play. Instead of tying your usual start\nfunction to a given version of the spawn methods, you add a \"spawn mode\"\nparameter to the start function.\n\n```erlang\nstart(Mode, foo, bar) -\u003e\n  % Do the same few things here...\n  Result = spawner:spawn(Mode, fun loop/x).\n  % ...and the same some more things here.\n  {ok, Result}.\n```\n\nWith this revised version of your start function, you let the user choose how\nthe process implementing your behavior must actually be spawned.\n\n```erlang\n% Same as if the 'spawn' function is used.\n{ok, Pid} = my_module:start(no_link, foo, bar),\n% Same as if the 'spawn_link' function is used.\n{ok, Pid} = my_module:start(link, foo, bar),\n% Same as if the 'spawn_monitor' function is used.\n{ok, {Pid, Monitor}} = my_module:start(monitor, foo, bar),\n```\n\n(Note that I've hidden the fact that spawning with a monitor will return both,\nthe pid and the monitor reference. It must be handled by your code, indeed.)\n\nAnd the cherry on top of the cake is that it also lets the user also exploit\nthe more sophisticated spawn method.\n\n```erlang\nPid = my_module:start({no_link, [{fullsweep_after, 2}]}, foo, bar).\n```\n\nThe previous example is equivalent to spawning with the `spawn_opt` function\nand passing `{fullsweep_after, 2}` as option while asking for no link nor\nmonitor.\n\nOf course, all the spawn modes you would expect are available, see\nthe `spawner:spawn/x` functions. And last but not least, the spawn mode is\n`spawner:mode/0` data type.\n\n## Dealing with initializations\n\nNext to the `spawner:spawn/x` functions, you will notice their\n`spawner:setup_spawn/x` counterparts. They allow to safely initialize the\nprocess **before** it can be returned.\n\nUltimately, this library should only be about the spawn mode and wrappers\naround the built-in spawn functions that honor them. So why are we talking\nabout initialization ?\n\nWell, it's frequent that right after spawning a new process, it will follow an\ninitialization sequence, and only once completed, the start function can\nreturn.\n\n```erlang\nstart(foo, bar) -\u003e\n  Pid = spawn(fun loop/x),\n  % Do the initialization steps before returning.\n  Pid ! {do_init, self()},\n  receive\n    {Pid, init_done} -\u003e\n      ok\n  end,\n  {ok, Pid}.\n```\n\nHowever, because the early sequence can go wrong, the implementation\nof the start function is forced to spawn the process with a monitor.\n\n```erlang\nstart(foo, bar) -\u003e\n  % We start the process with a monitor instead, then we do the initialization\n  % steps.\n  {Pid, Monitor} = spawn_monitor(fun loop/x),\n  Pid ! {do_init, self()},\n  receive\n    {Pid, init_done} -\u003e\n      % Initialization was successful, the monitor can be removed.\n      demonitor(Monitor),\n      {ok, Pid};\n    {'DOWN', Monitor, process, Pid, Reason} -\u003e\n      % Initialization went wrong, we return an error instead.\n      {error, Reason}\n  end.\n```\n\nHow can we apply the same concept using the spawn mode then? With the\n`spawner:setup_spawn/x` functions! You encapsulate the initialization part of\nyour code in a setup function, then let `setup_spawn/x` function do the rest.\n\n```erlang\nstart(Mode, foo, bar) -\u003e\n  % The same initialization steps but in a function.\n  Setup = fun(Pid, Monitor) -\u003e\n    Pid ! {do_init, self()},\n    receive\n      {Pid, init_done} -\u003e\n        ok;\n      {'DOWN', Monitor, process, Pid, Reason} -\u003e\n        {not_ok, Reason}\n    end\n  end,\n  % Spawning the process and initializing it.\n  case spawner:spawn_setup(Mode, fun loop/x, Setup) of\n    {setup, ok, Return} -\u003e\n      {ok, Return};\n    {setup, {not_ok, Reason}, undefined} -\u003e\n      {error, Reason}\n  end.\n```\n\nThe provided setup function is passed the monitor which allows you to safely\ninitialize the process. Then by the time the start function returns, the spawn\nmode is guaranteed to be honored.\n\n## Bits of history\n\nWhile I'm usually not a big fan of the API design decisions made for the OTP\n(see my work on the [OTPless distribution](https://github.com/otpless-erlang)\nErlang with its [re-written behaviors](https://github.com/erlangsters/otpless-behaviors)),\nthere are often historical reasons for things being the way they are.\n\nAs of Erlang/OTP version 27, there are 5 types of the `spawn` function.\n\n- `spawn/x` (4)\n- `spawn_link/x` (4)\n- `spawn_monitor/x` (4)\n- `spawn_opt/x` (4)\n- `spawn_request/x` (5)\n\nWith all their variations, that is no more than 21 functions! Even if they\nshare similarities, that feels heavy.\n\nThe `spawn_opt` functions were added in Erlang/OTP R6B (in 2000). The\n`spawn_monitor` functions (and monitor functionalities in general) were added\nin Erlang/OTP R10B (in 2004). And the `spawn_request` functions were added in\nErlang/OTP 23 (in 2020).\n\nAt the time when only `spawn/x` and `spawn_link/x` existed, adding a `start/x`\nfunction for the former and a `start_link/x` function for the later may have\nfelt like an acceptable solution. But as the number of variants grows, it\nbecomes a tricky choice to make... either breaking backward compatibility for\nan elegant solution, or keep adding a variant of the `start/x` function.\n\nPerhaps that OTP team did not know the number of spawn functions would keep\ngrowing.\n\nAnyhow, they eventually decided to mitigate this by re-using the existing\n\"options\" parameter (of the existing `start/x` functions) to pass the\nspawn-related options.\n\n```\nstart_opt() =\n    {timeout, Timeout :: timeout()} |\n    {spawn_opt, SpawnOptions :: [proc_lib:spawn_option()]} |\n    enter_loop_opt()\n```\n\nIt's an excerpt of the OTP documentation (the `gen_server` behavior).\n\nXXX: Check factuality of the above infos.\n\n## Using it in your project\n\nWith the **Rebar3** build system, add the following to the `rebar.config` file\nof your project.\n\n```\n{deps, [\n  {spawn_mode, {git, \"https://github.com/erlangsters/spawn-mode.git\", {tag, \"master\"}}}\n]}.\n```\n\nIf you happen to use the **Erlang.mk** build system, then add the following to\nyour Makefile.\n\n```\nBUILD_DEPS = spawn_mode\ndep_spawn_mode = git https://github.com/erlangsters/spawn-mode master\n```\n\nIn practice, you want to replace the branch \"master\" with a specific \"tag\" to\navoid breaking your project if incompatible changes are made.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferlangsters%2Fspawn-mode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferlangsters%2Fspawn-mode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferlangsters%2Fspawn-mode/lists"}