Aye, that's true. An alternative could be `supervision_spec/1`? Fishcakez just pointed out to me that Ecto, Phoenix and Plug already use `child_spec` in this way, probably best to stick to that convention.
Cheers, Louis On Tue, 21 Feb 2017 at 19:52 José Valim <[email protected]> wrote: > > 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 > > -- 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/CABu8xFCOOxH4b2YwaEMKD-vTojPxw41e6RrYE%2BChHjxCfAWynA%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.
