> I find the name child_spec/1 a little confusing I read child_spec/1 as: the specification to run this as a child in a supervisor. Does that help?
I think calling it only "spec" can be even more confusing, as we also have typespecs. *José Valim* www.plataformatec.com.br Skype: jv.ptec Founder and Director of R&D On Tue, Feb 21, 2017 at 8:08 PM, Louis Pilfold <[email protected]> wrote: > Hello! > > Very interesting. It does seem like this would be easier to learn :) > > Little nitpick: I find the name child_spec/1 a little confusing. I would > expect it to be the spec of the children of a supervisor defined by the > module, due to the word "child". > > MySup.child_spec([]) > #=> spec of child of MySup > > MySup.spec([]) > #=> spec of MySup > > Perhaps I just feel this way because of familiarity with the current > system. > > Cheers, > Louis > > > On Tue, 21 Feb 2017 at 18:16 José Valim <[email protected]> > wrote: > >> Note: this proposal is also available in a gist - >> https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6 >> >> Streamlining child specs >> >> Hello everyone, >> >> This is a proposal for improving how supervisors are defined and used in >> Elixir. Before we go to the improvements, let's discuss the pain points of >> the current API. >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#the-current-state-of-the-art>The >> current state of the art >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#module-based-supervisors>Module-based >> supervisors >> >> Supervisors in Elixir can be defined using modules: >> >> defmodule MySupervisor do >> use Supervisor >> >> def start_link do >> Supervisor.start_link(__MODULE__, []) >> end >> >> def init([]) do >> children = [ >> worker(Child, []) >> ] >> >> supervise(children, strategy: :one_for_one) >> endend >> >> Notice we introduced the functions worker and supervise, which are part >> of the Supervisor.Spec module, to take care of defining the tuples used >> behind the scenes. >> >> However, the above is still verbose for the cases we want to simply start >> a supervisor. That's when we introduced callback-less supervisors. >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#callback-less-supervisors>Callback-less >> supervisors >> >> The idea behind callback-less supervisors is that you can start them >> directly without needing callbacks: >> >> import Supervisor.Spec >> >> children = [ >> worker(Child, []) >> ] >> Supervisor.start_link(children, strategy: :one_for_one) >> >> While the example above is an improvement compared to previous versions, >> especially the early ones that required tuples to be passed around instead >> of using the worker/supervisor functions, it has the following issues: >> >> - >> >> Importing Supervisor.Spec which exists in a separate module from >> Supervisor is confusing in terms of learning and exploring the API >> and documentation >> - >> >> Children inside callback-less supervisors cannot be hot code >> upgraded, since the definition is provided before the supervisor even >> starts >> >> * The worker(Child, []) API is confusing in regards to which >> function it will invoke and with which arguments. The fact worker(Child, >> [1, 2, 3]) invokes Child.start_link(1, 2, 3) which is then packed >> into an argument given to GenServer.start_link/3 start is not helpful. >> >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#application-integration>Application >> integration >> >> Perhaps the most troubling aspect of supervisors is the amount of code >> necessary to integrate any supervised process into a new application. For >> example, imagine you have created a new application with mix new my_app and >> after a while you decide it needs an agent. In order to correct supervise >> your agent implenentation, you will need to: >> >> 1. >> >> Define a new module that use Application and implements the start/2 >> callback >> which starts a supervisor with the agent as a worker >> >> defmodule MyApp do >> use Application >> >> def start(_type, _args) do >> import Supervisor.Spec >> >> children = [ >> worker(MyAgent, [], restart: :transient) >> ] >> >> Supervisor.start_link(children, strategy: :one_for_one) >> endend >> >> 2. Make sure to define the `:mod key in the mix.exs: >> >> elixir def application do [mod: {MyApp, []}] end >> >> The amount of code to go from unsupervised to supervised often lead >> people down the wrong road of not adding supervision at all. Another sign >> the code above is a common boilerplate is that it has been automatized >> under the mix new my_app --sup flag. The --sup flag has been helpful but >> it is useless in projects you have already created. >> >> The other problem with the code above is that the agent configuration >> ends-up spread through multiple files. Properties such as :shutdown and >> its type (worker or supervisor) is most times better to be co-located >> with its implementation rather than specified separately in the supervision >> tree. Specifying it at the supervision tree is useful only in cases the >> same implementation is started multiple times under the same or different >> trees, which is not the most common scenario. >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#a-streamlined-solution>A >> streamlined solution >> >> In the previous section we have explored the current state of application >> and supervisors in Elixir. In this section, we propose a streamlined >> solution that is going to touch all of the problems outlined above, >> starting with the application one. >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#default-application-callbacks>Default >> application callbacks >> >> Ideally, we want to make it as straight-forward as possible to go from >> non-supervised to supervised code. If most applications callbacks end-up >> having the exact same structure, such boilerplate can likely be addressed >> by Elixir. >> >> Therefore, instead of forcing users to define an application callback >> with a boilerplate supervision tree, Elixir could ship with the application >> definition where we only need to tell Elixir which processes to supervise: >> >> def application do >> [extra_applications: [:logger], >> supervise: [MyAgent, {Registry, name: MyRegistry}]]end >> >> The :supervise option allows us to specify a list of module names or >> tuples with two elements, where the first element is the module name and >> second element is any argument. When the application starts, Elixir will >> traverse the list invoking the child_spec/1 function on each module, >> passing the arguments. >> >> In other words, we will push developers to co-locate their supervision >> specification with the module definition. >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#the-child_spec1-function>The >> child_spec/1 function >> >> The heart of the proposal is in allowing modules to specify the >> supervision specification for the processes they implement. For example, >> our agent definition may look like this now: >> >> defmodule MyAgent do >> def child_spec(_) do >> %{id: MyAgent, start: {__MODULE__, :start_link, []}, restart: :transient} >> end >> # ...end >> >> With the definition above, we are now able to use MyAgent as a >> supervised process in our application, as shown in the previous section. >> >> If your application requires multiple supervisors, Supervisors can also >> be added to the application's :superviseoption, as long as they also >> implement the child_spec/1 function. Furthermore, notice the Supervisor API, >> such as start_link/2 will also be updated to allow a list of modules, in >> the same format as the application's :supervise: >> >> defmodule MySup do >> def child_spec(_) do >> %{id: MySup, start: {__MODULE__, :start_link, []}, type: :supervisor} >> end >> >> def start_link do >> Supervisor.start_link([OtherAgent], strategy: :simple_one_for_one, name: >> MySup) >> endend >> >> Besides keeping the agent configuration in the same module it is defined, >> the module based child_spec/1 has a big advantage of also being hot code >> swap friendly, because by changing the child_spec/1 function in the >> MyAgentmodule, we can patch how the agent runs in production. >> >> Not only that, by colocating child_spec/1 with the code, we make it >> simpler to start abstractions such as Phoenix.Endpoint or Ecto.Repo under >> a supervision tree, as those abstractions can automatically define the >> child_spec/1 function, no longer forcing developers to know if those >> entries have the type worker or supervisor. >> >> Today developers likely have the following code in their Phoenix >> applications: >> >> children = [ >> supervisor(MyApp.Endpoint, []), >> supervisor(MyApp.Repo, []) >> ] >> >> which constraints both Ecto and Phoenix because now they are forced to >> always run a supervisor at the top level in order to maintain backwards >> compatibility. Even worse, if a developer add those entries by hand with >> the wrong type, their applications will be misconfigured. >> >> By storing the child_spec directly in the repository and endpoint, >> developers only need to write: >> >> supervise: [MyApp.Endpoint, MyApp.Repo] >> >> which is cleaner and less error prone. >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#catching-up-with-otp>Catching >> up with OTP >> >> In the previous section we have mentioned that Supervisor.start_link/2 will >> also accept a list of modules or a list of tuples with two elements on >> Supervisor.start_link/2. In such cases, the supervisor will invoke >> child_spec/1appropriately on the list elements. >> >> You may have also noticed we have used maps to build the child_spec/1 >> functions: >> >> def child_spec(_) do >> %{id: MySup, start: {__MODULE__, :start_link, []}, type: :supervisor}end >> >> The reason we chose a map with the keys above is to mirror the API >> introduced in Erlang/OTP 18. Overall, Supervisor.start_link/2 expect a >> list of children where each child must be: >> >> - an atom - such as MyApp. In such cases, MyApp.child_spec([]) will >> be invoked when the supervisor starts >> - a tuple with two elements - such as {MyApp, arg}. In such cases, >> MyApp.child_spec(arg) will be invoked when the supervisor starts >> - a map - containing the keys :id (required), :start (required), >> :shutdown, :restart, :type or :modules >> >> Introducing maps will allow a more natural and data-centric approach for >> configuring supervisors, no longer needing the helper functions defined in >> Supervised.Spec today. >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#a-practical-example>A >> practical example >> >> In order to show how the changes above will affect existing applications, >> we checked how applications generated by Phoenix would leverage them. The >> answer is quite positive. The whole lib/demo.ex file will no longer >> exist: >> >> defmodule Demo do >> use Application >> # See http://elixir-lang.org/docs/stable/elixir/Application.html # for >> more information on OTP Applications >> def start(_type, _args) do >> import Supervisor.Spec >> # Define workers and child supervisors to be supervised >> children = [ # Start the Ecto repository >> supervisor(Demo.Repo, []), # Start the endpoint when the >> application starts >> supervisor(Demo.Endpoint, []), # Start your own worker by >> calling: Demo.Worker.start_link(arg1, arg2, arg3) # worker(Demo.Worker, >> [arg1, arg2, arg3]), >> ] >> # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # for >> other strategies and supported options >> opts = [strategy: :one_for_one, name: Demo.Supervisor] >> Supervisor.start_link(children, opts) >> endend >> >> instead it will be replaced by: >> >> def application do >> [extra_applications: [:logger], >> supervise: [Demo.Repo, Demo.Endpoint]]end >> >> which Mix will translate behind the scenes to: >> >> def application do >> [extra_applications: [:logger], >> mod: {Application.Default, %{supervise: [Demo.Repo, Demo.Endpoint]}}]end >> >> Overall we propose the following additions to def application: >> >> - :supervise - expects a list of module names to supervise when the >> application starts (maps as childspecs are not supported as that would >> make >> them compile time) >> - :max_restarts - the amount of restarts supported by application >> supervision tree in :max_seconds (defaults to 1) >> - :max_seconds - the amount of time for :max_restarts (defaults to 5) >> >> Those options will be handled by the default application module >> implemented in Application.Default. Mix will error building the >> application file if any of the options above are given alongside :mod. >> >> The application tree supervisor will have strategy of :one_for_one and >> such is not configurable. If you need other supervision strategies, then >> all you need to do is define your own supervisor with a custom strategy and >> list it under :supervise in def application. >> >> At the end of the day, the changes above will render both mix new --sup >> and Supervisor.Spec obsolete, as we now co-locate the child_spec/1 with >> the module definition, leading to better code and hot code upgrades, also >> making it easier to move from non-supervised to supervised applications. >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#automating-child_spec1> >> Automating child_spec/1 >> >> We have explicitly defined the child_spec/1 function in all of the >> examples above. This poses a problem: what if someone defines a >> Supervisor module and forget to mark its type as :supervisor in the >> specification? >> >> Given the Supervisor module already knows sensible defaults, we should >> rely on them for building specs. Therefore, we also propose use Agent, use >> Task, use Supervisor and use GenServer automatically define a >> child_spec/1function that is overridable: >> >> defmodule MyAgent do >> use Agent, restart: :transient >> # Generated on: use Agent # def child_spec(opts) do # %{id: MyAgent, >> start: {__MODULE__, :start_link, [opts]}, restart: :transient} # end >> >> def start_link(opts) do >> Agent.start_link(fn -> %{} end, opts) >> end >> >> ...end >> >> The default child_spec/1 will pass the argument given to child_spec/1 >> directly >> to start_link/1. This means MyAgentcould be started with no name in a >> supervision tree as follows: >> >> supervise: [MyAgent] >> >> and with a given name as: >> >> supervise: [{MyAgent, name: FooBar}] >> >> In other words, the changes in this proposal will push developers towards >> a single start_link/1 function that will receive options as a single >> argument. This mirrors the API exposed in GenServer, Supervisor and >> friends, removing the common confusion with passing more than one argument >> to start_link. >> >> Of course, any of the defaults above, as well as the rare cases >> developers wants to dynamically customize child_spec/1 can be achieved >> by overriding the default implementation of child_spec/1. >> >> <https://gist.github.com/josevalim/82fb1d20c9738bb3cc96449e67407df6#summing-up>Summing >> up >> >> This proposal aims to streamline the move from non-supervised >> applications to supervised applications. To do so, we introduced the >> ability to colocate the child_spec/1 definition with the module >> implementation, which leads to more self-contained abstractions, such as >> Ecto.Repo and Phoenix.Endpoint. Those changes also allowed us to >> overhaul the supervision system, getting rid of Supervisor.Spec and >> making it more data-centric. >> >> While those changes remove some of the importance given to application >> callbacks today, we believe this is a positive change. There is no need to >> talk about application callbacks if the majority of developers are using >> application callbacks simply to start supervision trees. The application >> callback will still remain import for scenarios where custom start, >> config_change or stop are necessary, such as those relying on included >> applications. >> >> Since Elixir is a stable language, all of the code available today will >> continue to work, although this proposal opens up the possibility to >> deprecate Supervisor.Spec and mix new --sup in a far ahead future, >> preparing for an eventual removal in Elixir 2.0. >> >> *Thanks to Dave Thomas for initiating the proposal and the Elixir team >> for further discussions and reviews. Also thanks to Chris McCord and Saša >> Jurić for feedback.* >> >> >> *José Valim* >> www.plataformatec.com.br >> Skype: jv.ptec >> Founder and Director of R&D >> >> -- >> You received this message because you are subscribed to the Google Groups >> "elixir-lang-core" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to [email protected]. >> To view this discussion on the web visit https://groups.google.com/d/ >> msgid/elixir-lang-core/CAGnRm4J1mmJQ_QUML5VeSnT81kprkTWBtjekD3hpHMU >> hVDJBZg%40mail.gmail.com >> <https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4J1mmJQ_QUML5VeSnT81kprkTWBtjekD3hpHMUhVDJBZg%40mail.gmail.com?utm_medium=email&utm_source=footer> >> . >> For more options, visit https://groups.google.com/d/optout. >> > -- > You received this message because you are subscribed to the Google Groups > "elixir-lang-core" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To view this discussion on the web visit https://groups.google.com/d/ > msgid/elixir-lang-core/CABu8xFBTW6GcaCFSJ_z6KYzRN% > 3Dxt9tdRpxq6woXBcapz4st%2B5w%40mail.gmail.com > <https://groups.google.com/d/msgid/elixir-lang-core/CABu8xFBTW6GcaCFSJ_z6KYzRN%3Dxt9tdRpxq6woXBcapz4st%2B5w%40mail.gmail.com?utm_medium=email&utm_source=footer> > . > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "elixir-lang-core" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4KApFNcYHoMmbx9jo32x-zZdyNJAXbwZFBvMs_X2Q_UGw%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.
