Преглед изворни кода

perf: avoid calling zero arity Module:module_info

zmstone пре 1 година
родитељ
комит
f953bfb736

+ 3 - 3
apps/emqx/src/emqx_config_handler.erl

@@ -761,7 +761,7 @@ merge_to_override_config(RawConf, Opts) ->
 upgrade_conf(Conf) ->
     ConfigLoader = emqx_app:get_config_loader(),
     %% ensure module loaded
-    _ = ConfigLoader:module_info(),
+    ok = emqx_utils:interactive_load(ConfigLoader),
     case erlang:function_exported(ConfigLoader, schema_module, 0) of
         true ->
             try_upgrade_conf(apply(ConfigLoader, schema_module, []), Conf);
@@ -849,7 +849,7 @@ remove_empty_leaf(KeyPath, Handlers) ->
     end.
 
 assert_callback_function(Mod) ->
-    _ = apply(Mod, module_info, []),
+    emqx_utils:interactive_load(Mod),
     case
         erlang:function_exported(Mod, pre_config_update, 3) orelse
             erlang:function_exported(Mod, post_config_update, 5) orelse
@@ -902,7 +902,7 @@ get_function_arity(_Module, _Callback, []) ->
     false;
 get_function_arity(Module, Callback, [Arity | Opts]) ->
     %% ensure module is loaded
-    Module = Module:module_info(module),
+    ok = emqx_utils:interactive_load(Module),
     case erlang:function_exported(Module, Callback, Arity) of
         true -> Arity;
         false -> get_function_arity(Module, Callback, Opts)

+ 1 - 2
apps/emqx_auth/src/emqx_authz/emqx_authz.erl

@@ -713,8 +713,7 @@ format_for_api(Source) ->
 
 maybe_write_source_files(Source) ->
     Module = authz_module(type(Source)),
-    %% ensure module loaded
-    _ = catch Module:module_info(module),
+    ok = emqx_utils:interactive_load(Module),
     case erlang:function_exported(Module, write_files, 1) of
         true ->
             Module:write_files(Source);

+ 1 - 1
apps/emqx_bridge/src/emqx_bridge_app.erl

@@ -49,7 +49,7 @@ stop(_State) ->
 
 -if(?EMQX_RELEASE_EDITION == ee).
 ensure_enterprise_schema_loaded() ->
-    _ = emqx_bridge_enterprise:module_info(),
+    emqx_utils:interactive_load(emqx_bridge_enterprise),
     ok.
 -else.
 ensure_enterprise_schema_loaded() ->

+ 2 - 2
apps/emqx_bridge/src/schema/emqx_bridge_schema.erl

@@ -93,7 +93,7 @@ bridge_api_union(Refs) ->
 enterprise_api_schemas(Method) ->
     %% We *must* do this to ensure the module is really loaded, especially when we use
     %% `call_hocon' from `nodetool' to generate initial configurations.
-    _ = emqx_bridge_enterprise:module_info(),
+    ok = emqx_utils:interactive_load(emqx_bridge_enterprise),
     case erlang:function_exported(emqx_bridge_enterprise, api_schemas, 1) of
         true -> emqx_bridge_enterprise:api_schemas(Method);
         false -> []
@@ -102,7 +102,7 @@ enterprise_api_schemas(Method) ->
 enterprise_fields_bridges() ->
     %% We *must* do this to ensure the module is really loaded, especially when we use
     %% `call_hocon' from `nodetool' to generate initial configurations.
-    _ = emqx_bridge_enterprise:module_info(),
+    ok = emqx_utils:interactive_load(emqx_bridge_enterprise),
     case erlang:function_exported(emqx_bridge_enterprise, fields, 1) of
         true -> emqx_bridge_enterprise:fields(bridges);
         false -> []

+ 1 - 1
apps/emqx_management/src/emqx_mgmt_data_backup.erl

@@ -746,7 +746,7 @@ import_cluster_hocon(BackupDir, Opts) ->
     end.
 
 upgrade_raw_conf(SchemaMod, RawConf) ->
-    _ = SchemaMod:module_info(),
+    ok = emqx_utils:interactive_load(SchemaMod),
     case erlang:function_exported(SchemaMod, upgrade_raw_conf, 1) of
         true ->
             %% TODO make it a schema module behaviour in hocon_schema

+ 13 - 2
apps/emqx_utils/src/emqx_utils.erl

@@ -71,7 +71,8 @@
     call_first_defined/3,
     ntoa/1,
     foldl_while/3,
-    is_restricted_str/1
+    is_restricted_str/1,
+    interactive_load/1
 ]).
 
 -export([
@@ -604,7 +605,7 @@ call_first_defined(Module, Function, []) ->
     error({not_exported, Module, Function});
 call_first_defined(Module, Function, [Args | Rest]) ->
     %% ensure module is loaded
-    _ = apply(Module, module_info, []),
+    ok = interactive_load(Module),
     case erlang:function_exported(Module, Function, length(Args)) of
         true ->
             apply(Module, Function, Args);
@@ -919,6 +920,16 @@ base62(I) when I < 26 -> $A + I;
 base62(I) when I < 52 -> $a + I - 26;
 base62(I) -> $0 + I - 52.
 
+%% In production code, EMQX is always booted in embedded mode.
+%% so making a dynamic call to Module:module_info(module) is cheap.
+%% In interactive mode (test, and emqx config check CLI), the first attempt
+%% to make the dynamic call to Module:module_info(module) will cost,
+%% once loaded, it's cheap for subsequent calls.
+%% NOTE: For non-existing modules, this call is not as effective!
+interactive_load(Module) ->
+    _ = catch apply(Module, module_info, [module]),
+    ok.
+
 -ifdef(TEST).
 -include_lib("eunit/include/eunit.hrl").
 

+ 3 - 13
apps/emqx_utils/src/emqx_variform.erl

@@ -301,7 +301,9 @@ assert_func_exported(?BIF_MOD, iif, _Arity) ->
 assert_func_exported(?BIF_MOD, is_empty, _Arity) ->
     ok;
 assert_func_exported(Mod, Fun, Arity) ->
-    ok = try_load(Mod),
+    %% call emqx_utils:interactive_load to make sure it will work in tests.
+    %% in production, the module has to be pre-loaded by plugin management code
+    ok = emqx_utils:interactive_load(Mod),
     case erlang:function_exported(Mod, Fun, Arity) of
         true ->
             ok;
@@ -314,18 +316,6 @@ assert_func_exported(Mod, Fun, Arity) ->
             })
     end.
 
-%% best effort to load the module because it might not be loaded as a part of the release modules
-%% e.g. from a plugin.
-%% do not call code server, just try to call a function in the module.
-try_load(Mod) ->
-    try
-        _ = erlang:apply(Mod, module_info, [md5]),
-        ok
-    catch
-        _:_ ->
-            ok
-    end.
-
 assert_module_allowed(Mod) when ?IS_ALLOWED_MOD(Mod) ->
     ok;
 assert_module_allowed(Mod) ->