asterisk / asterisk

The official Asterisk Project repository.
https://www.asterisk.org
Other
2k stars 929 forks source link

loader.c: Allow dependent modules to be unloaded automatically. #475

Closed InterLinked1 closed 1 month ago

InterLinked1 commented 6 months ago

Because of the (often recursive) nature of module dependencies in Asterisk, hot swapping a module on the fly is cumbersome if a module is depended on by other modules. Currently, dependencies must be popped manually by unloading dependents, unloading the module of interest, and then loading modules again in reverse order.

To make this easier, the ability to do this automatically in certain circumstances has been added, as an optional extension to the "module refresh" command. If requested, Asterisk will check if a module that has a positive usecount could be unloaded safely if anything recursively dependent on it were unloaded. If so, it will go ahead and unload all these modules and load them back again. This makes hot swapping modules that provide dependencies much easier.

Resolves: #474

UserNote: Modules with dependency relations can have their dependents automatically unloaded and loaded again using the "module refresh" CLI command or the ModuleLoad AMI command.

InterLinked1 commented 6 months ago

cherry-pick-to: 18 cherry-pick-to: 20 cherry-pick-to: 21

asteriskteam commented 2 months ago

This PR has been marked stale because it has been in "Changes Requested" or "submitter-action-required" state for 28 days or more. Please make the requested changes within 14 days or the PR will be closed.

asteriskteam commented 1 month ago

This PR has been marked stale because it has been in "Changes Requested" or "submitter-action-required" state for 28 days or more. Please make the requested changes within 14 days or the PR will be closed.

gtjoseph commented 1 month ago

I lost track of this one, sorry. I'm reviewing today.

gtjoseph commented 1 month ago

@InterLinked1 Do you have an example of a module that actually can be refreshed recursively? Every one I've tried gets Soft unload failed ...

InterLinked1 commented 1 month ago

@InterLinked1 Do you have an example of a module that actually can be refreshed recursively? Every one I've tried gets Soft unload failed ...

Most of my use cases are my own proprietary modules with layered dependencies, but I found one that should work in a stock install - this appears to be two layers since app_mixmonitor depends on func_periodic_hook and func_periodic_hook depends on func_cut:

Here it is originally:

debian*CLI> module refresh func_cut automatically
Unloaded and loaded func_cut
[2024-04-27 10:24:37.446]  Unloading app_mixmonitor.so
[2024-04-27 10:24:37.447]   == Unregistered application 'StopMixMonitor'
[2024-04-27 10:24:37.447]   == Unregistered application 'MixMonitor'
[2024-04-27 10:24:37.447]   == Manager unregistered action MixMonitorMute
[2024-04-27 10:24:37.447]   == Manager unregistered action MixMonitor
[2024-04-27 10:24:37.447]   == Manager unregistered action StopMixMonitor
[2024-04-27 10:24:37.447]   == Unregistered custom function MIXMONITOR
[2024-04-27 10:24:37.447]  Unloading func_periodic_hook.so
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/1, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/2, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/3, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/4, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/5, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/6, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/beep/1, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/beep/2, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]   == Unregistered custom function PERIODIC_HOOK
[2024-04-27 10:24:37.447]  Unloading func_cut.so
[2024-04-27 10:24:37.447]   == Unregistered custom function CUT
[2024-04-27 10:24:37.447]   == Unregistered custom function SORT
[2024-04-27 10:24:37.447]   == Registered custom function 'CUT'
[2024-04-27 10:24:37.447]   == Registered custom function 'SORT'
[2024-04-27 10:24:37.447]  Loaded func_cut.so => (Cut out information from a string)
[2024-04-27 10:24:37.447]   == Registered custom function 'PERIODIC_HOOK'
[2024-04-27 10:24:37.447]  Loaded func_periodic_hook.so => (Periodic dialplan hooks.)
[2024-04-27 10:24:37.447]   == Registered application 'MixMonitor'
[2024-04-27 10:24:37.447]   == Registered application 'StopMixMonitor'
[2024-04-27 10:24:37.447]   == Manager registered action MixMonitorMute
[2024-04-27 10:24:37.447]   == Manager registered action MixMonitor
[2024-04-27 10:24:37.447]   == Manager registered action StopMixMonitor
[2024-04-27 10:24:37.447]   == Registered custom function 'MIXMONITOR'
[2024-04-27 10:24:37.447]  Loaded app_mixmonitor.so => (Mixed Audio Monitoring Application)
debian*CLI>

And now:

proliant*CLI> module refresh func_cut recursively
Unloaded and loaded func_cut
[Apr 27 10:43:43]        > Unloading app_mixmonitor.so
[Apr 27 10:43:43]        > Unregistered application 'StopMixMonitor'
[Apr 27 10:43:43]        > Unregistered application 'MixMonitor'
[Apr 27 10:43:43]        > Manager unregistered action MixMonitorMute
[Apr 27 10:43:43]        > Manager unregistered action MixMonitor
[Apr 27 10:43:43]        > Manager unregistered action StopMixMonitor
[Apr 27 10:43:43]        > Unregistered custom function MIXMONITOR
[Apr 27 10:43:43]        > Unloading func_periodic_hook.so
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/beep/3, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/beep/2, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/beep/1, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/6, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/5, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/4, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/3, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/2, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/1, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Unregistered custom function PERIODIC_HOOK
[Apr 27 10:43:43]        > Unloading func_cut.so
[Apr 27 10:43:43]        > Unregistered custom function CUT
[Apr 27 10:43:43]        > Unregistered custom function SORT
[Apr 27 10:43:43]        > Registered custom function 'CUT'
[Apr 27 10:43:43]        > Registered custom function 'SORT'
[Apr 27 10:43:43]        > Loaded func_cut.so => (Cut out information from a string)
[Apr 27 10:43:43]        > Registered custom function 'PERIODIC_HOOK'
[Apr 27 10:43:43]        > Loaded func_periodic_hook.so => (Periodic dialplan hooks.)
[Apr 27 10:43:43]        > Registered application 'MixMonitor'
[Apr 27 10:43:43]        > Registered application 'StopMixMonitor'
[Apr 27 10:43:43]        > Manager registered action MixMonitorMute
[Apr 27 10:43:43]        > Manager registered action MixMonitor
[Apr 27 10:43:43]        > Manager registered action StopMixMonitor
[Apr 27 10:43:43]        > Registered custom function 'MIXMONITOR'
[Apr 27 10:43:43]        > Loaded app_mixmonitor.so => (Mixed Audio Monitoring Application)
InterLinked1 commented 1 month ago

@InterLinked1 Do you have an example of a module that actually can be refreshed recursively? Every one I've tried gets Soft unload failed ...

Most of my use cases are my own proprietary modules with layered dependencies, but I found one that should work in a stock install - this appears to be two layers since app_mixmonitor depends on func_periodic_hook and func_periodic_hook depends on func_cut:

Here it is originally:

debian*CLI> module refresh func_cut automatically
Unloaded and loaded func_cut
[2024-04-27 10:24:37.446]  Unloading app_mixmonitor.so
[2024-04-27 10:24:37.447]   == Unregistered application 'StopMixMonitor'
[2024-04-27 10:24:37.447]   == Unregistered application 'MixMonitor'
[2024-04-27 10:24:37.447]   == Manager unregistered action MixMonitorMute
[2024-04-27 10:24:37.447]   == Manager unregistered action MixMonitor
[2024-04-27 10:24:37.447]   == Manager unregistered action StopMixMonitor
[2024-04-27 10:24:37.447]   == Unregistered custom function MIXMONITOR
[2024-04-27 10:24:37.447]  Unloading func_periodic_hook.so
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/1, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/2, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/3, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/4, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/5, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/hook/6, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/beep/1, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]     -- Remove __func_periodic_hook_context__/beep/2, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[2024-04-27 10:24:37.447]   == Unregistered custom function PERIODIC_HOOK
[2024-04-27 10:24:37.447]  Unloading func_cut.so
[2024-04-27 10:24:37.447]   == Unregistered custom function CUT
[2024-04-27 10:24:37.447]   == Unregistered custom function SORT
[2024-04-27 10:24:37.447]   == Registered custom function 'CUT'
[2024-04-27 10:24:37.447]   == Registered custom function 'SORT'
[2024-04-27 10:24:37.447]  Loaded func_cut.so => (Cut out information from a string)
[2024-04-27 10:24:37.447]   == Registered custom function 'PERIODIC_HOOK'
[2024-04-27 10:24:37.447]  Loaded func_periodic_hook.so => (Periodic dialplan hooks.)
[2024-04-27 10:24:37.447]   == Registered application 'MixMonitor'
[2024-04-27 10:24:37.447]   == Registered application 'StopMixMonitor'
[2024-04-27 10:24:37.447]   == Manager registered action MixMonitorMute
[2024-04-27 10:24:37.447]   == Manager registered action MixMonitor
[2024-04-27 10:24:37.447]   == Manager registered action StopMixMonitor
[2024-04-27 10:24:37.447]   == Registered custom function 'MIXMONITOR'
[2024-04-27 10:24:37.447]  Loaded app_mixmonitor.so => (Mixed Audio Monitoring Application)
debian*CLI>

And now:

proliant*CLI> module refresh func_cut recursively
Unloaded and loaded func_cut
[Apr 27 10:43:43]        > Unloading app_mixmonitor.so
[Apr 27 10:43:43]        > Unregistered application 'StopMixMonitor'
[Apr 27 10:43:43]        > Unregistered application 'MixMonitor'
[Apr 27 10:43:43]        > Manager unregistered action MixMonitorMute
[Apr 27 10:43:43]        > Manager unregistered action MixMonitor
[Apr 27 10:43:43]        > Manager unregistered action StopMixMonitor
[Apr 27 10:43:43]        > Unregistered custom function MIXMONITOR
[Apr 27 10:43:43]        > Unloading func_periodic_hook.so
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/beep/3, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/beep/2, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/beep/1, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/6, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/5, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/4, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/3, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/2, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Remove __func_periodic_hook_context__/hook/1, registrar=func_periodic_hook; con=<nil>((nil)); con->root=(nil)
[Apr 27 10:43:43]        > Unregistered custom function PERIODIC_HOOK
[Apr 27 10:43:43]        > Unloading func_cut.so
[Apr 27 10:43:43]        > Unregistered custom function CUT
[Apr 27 10:43:43]        > Unregistered custom function SORT
[Apr 27 10:43:43]        > Registered custom function 'CUT'
[Apr 27 10:43:43]        > Registered custom function 'SORT'
[Apr 27 10:43:43]        > Loaded func_cut.so => (Cut out information from a string)
[Apr 27 10:43:43]        > Registered custom function 'PERIODIC_HOOK'
[Apr 27 10:43:43]        > Loaded func_periodic_hook.so => (Periodic dialplan hooks.)
[Apr 27 10:43:43]        > Registered application 'MixMonitor'
[Apr 27 10:43:43]        > Registered application 'StopMixMonitor'
[Apr 27 10:43:43]        > Manager registered action MixMonitorMute
[Apr 27 10:43:43]        > Manager registered action MixMonitor
[Apr 27 10:43:43]        > Manager registered action StopMixMonitor
[Apr 27 10:43:43]        > Registered custom function 'MIXMONITOR'
[Apr 27 10:43:43]        > Loaded app_mixmonitor.so => (Mixed Audio Monitoring Application)

By the way, I really don't like how all the above log messages are "flat" hierarchy. I brought this up during your PR adjusting those, but I'm realizing now that I think at verbose levels 5 and above, there is no visual distinction between log messages.

I'm going to propose differentiating them somehow, to address this, though that's a separate issue.

github-actions[bot] commented 1 month ago

Successfully merged to branch master and cherry-picked to ["18","20","21"]