Old childspecs survive appup script evaluation

When trying to devise a simple example of evaluating an .appup script, I did the following:

  • In `testapp`, vsn 1, I had one child in the `testapp_sup` supervisor
  • The child, `testapp_p1` was spawn_link:ed as a plain process, to force a brutal purge
  • In vsn 2, the supervisor childspec list was empty

When letting `setup` generate an .appup script, the app upgrade crashed as the upgraded supervisor kept the old childspec and tried to restart the `testapp_p1` process, even though the module had been purged.

In `supervisor:code_change/3`, the new `StartSpec` is taken from `CB:init/1`, and the old start spec is processed to copy the child pid into the new child spec. But if the `Id` isn’t found in the new spec, the old child spec is kept as-is.

Why would that be? It seems counter-intuitive to me.

Also, due to this, it’s not obvious to me how to generate an .appup script that will work.

For reference, here’s the auto-generated script:

[{load_object_code,{testapp,"2",[testapp_app,testapp_sup]}},
 point_of_no_return,
 {suspend,[testapp_app,testapp_sup]},
 {load,{testapp_app,brutal_purge,brutal_purge}},
 {load,{testapp_sup,brutal_purge,brutal_purge}},
 {code_change,up,[{testapp_app,setup},{testapp_sup,setup}]},
 {resume,[testapp_app,testapp_sup]},
 {remove,{testapp_p1,brutal_purge,brutal_purge}},
 {purge,[testapp_p1]}]

Here is a bit of trace to illustrate what happens:

(<0.96.0>) call supervisor:code_change(253393648690427985072663628403827426949,{state,{local,testapp_sup},
       one_for_one,
       {[testapp_p1],
        #{testapp_p1 =>
              {child,<0.97.0>,testapp_p1,
                     {testapp_p1,start_link,[]},
                     permanent,false,5000,worker,
                     [testapp_p1]}}},
       undefined,5,10,[],0,never,testapp_sup,[]},setup)
(<0.96.0>) call supervisor:set_flags({one_for_one,5,10},{state,{local,testapp_sup},
       one_for_one,
       {[testapp_p1],
        #{testapp_p1 =>
              {child,<0.97.0>,testapp_p1,
                     {testapp_p1,start_link,[]},
                     permanent,false,5000,worker,
                     [testapp_p1]}}},
       undefined,5,10,[],0,never,testapp_sup,[]})
...
(<0.96.0>) call supervisor:update_childspec({state,{local,testapp_sup},
       one_for_one,
       {[testapp_p1],
        #{testapp_p1 =>
              {child,<0.97.0>,testapp_p1,
                     {testapp_p1,start_link,[]},
                     permanent,false,5000,worker,
                     [testapp_p1]}}},
       undefined,5,10,[],0,never,testapp_sup,[]},[])
(<0.96.0>) returned from supervisor:update_childspec/2 -> {ok,
                                                           {state,
                                                            {local,
                                                             testapp_sup},
                                                            one_for_one,
                                                            {[testapp_p1],
                                                             #{testapp_p1 =>
                                                                {child,
                                                                 <0.97.0>,
                                                                 testapp_p1,
                                                                 {testapp_p1,
                                                                  start_link,
                                                                  []},
                                                                 permanent,
                                                                 false,5000,
                                                                 worker,
                                                                 [testapp_p1]}}},
                                                            undefined,5,10,[],
                                                            0,never,
                                                            testapp_sup,[]}}
(<0.96.0>) returned from supervisor:code_change/3 -> {ok,
                                                      {state,
                                                       {local,testapp_sup},
                                                       one_for_one,
                                                       {[testapp_p1],
                                                        #{testapp_p1 =>
                                                           {child,<0.97.0>,
                                                            testapp_p1,
                                                            {testapp_p1,
                                                             start_link,[]},
                                                            permanent,false,
                                                            5000,worker,
                                                            [testapp_p1]}}},
                                                       undefined,5,10,[],0,
                                                       never,testapp_sup,[]}}
(<0.96.0>) call supervisor:handle_info({'EXIT',<0.97.0>,killed},{state,{local,testapp_sup},
       one_for_one,
       {[testapp_p1],
        #{testapp_p1 =>
              {child,<0.97.0>,testapp_p1,
                     {testapp_p1,start_link,[]},
                     permanent,false,5000,worker,
                     [testapp_p1]}}},
       undefined,5,10,[],0,never,testapp_sup,[]})
(<0.96.0>) call supervisor:restart_child(<0.97.0>,killed,{state,{local,testapp_sup},
       one_for_one,
       {[testapp_p1],
        #{testapp_p1 =>
              {child,<0.97.0>,testapp_p1,
                     {testapp_p1,start_link,[]},
                     permanent,false,5000,worker,
                     [testapp_p1]}}},
       undefined,5,10,[],0,never,testapp_sup,[]})
{ok,[]}

The `'EXIT’` signal arrives from the `testapp_p1` process being killed due to the brutal purge. Since the old childspec survived the code_change, the supervisor tries to restart it.

BR,
Ulf

2 Likes