From 7ee8080d1127b30c935dfd896a8e78b3a34ee176 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Mon, 29 Aug 2011 15:58:45 +0200 Subject: Improve performance of upgrade when many processes have old code This commit utilizes the new bif erlang:check_old_code/1 to check if a module has old code in the system or not before running erlang:check_process_code/2. This is to optimize release_handler:install_release (and release_handler:check_install_release). A new test is added which checks that after traversing all processes/modules once and purging all old code, the second run through this part of the code is "sufficiently" much faster. A note is also added in the reference manual for release_handler:install_release about how to go around the efficiency problem of this function. --- lib/sasl/doc/src/release_handler.xml | 22 +++++++- lib/sasl/src/release_handler_1.erl | 27 ++++++---- lib/sasl/test/release_handler_SUITE.erl | 89 ++++++++++++++++++++++++++++++--- 3 files changed, 119 insertions(+), 19 deletions(-) diff --git a/lib/sasl/doc/src/release_handler.xml b/lib/sasl/doc/src/release_handler.xml index 4a973bc5ed..2781412848 100644 --- a/lib/sasl/doc/src/release_handler.xml +++ b/lib/sasl/doc/src/release_handler.xml @@ -4,7 +4,7 @@
- 19962009 + 19962011 Ericsson AB. All Rights Reserved. @@ -299,6 +299,26 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). {update_paths,true}, afterwards code:lib_dir(myapp) will return /home/user/myapp-1.0.

+ +

Before a new release is installed, the + release_handler checks that no processes have + references to old code of any of the modules that are to be + loaded. This operation can be very time consuming, + especially if there are many processes in the system, since + it might cause garbage collections and copying of data.

+

If it is desirable to reduce the time spent in this + function, then check_install_release/1 + might be called first. This function does the same check for + old code, including garbage collection, and can be done in + the background before actually starting the upgrade by + calling install_release.

+

Code might even be purged (using + code:purge/1) after + check_install_release in order to completely + avoid the check for old code during + install_release.

+
diff --git a/lib/sasl/src/release_handler_1.erl b/lib/sasl/src/release_handler_1.erl index ff62f847ac..590ecdba65 100644 --- a/lib/sasl/src/release_handler_1.erl +++ b/lib/sasl/src/release_handler_1.erl @@ -122,22 +122,29 @@ split_instructions([], Before) -> %% Mod = atom() %%----------------------------------------------------------------- check_old_processes(Script) -> + Procs = erlang:processes(), lists:foreach(fun({load, {Mod, soft_purge, _PostPurgeMethod}}) -> - check_old_code(Mod); + check_old_code(Mod,Procs); ({remove, {Mod, soft_purge, _PostPurgeMethod}}) -> - check_old_code(Mod); + check_old_code(Mod,Procs); (_) -> ok end, Script). -check_old_code(Mod) -> - lists:foreach(fun(Pid) -> - case erlang:check_process_code(Pid, Mod) of - false -> ok; - true -> throw({error, Mod}) - end - end, - erlang:processes()). +check_old_code(Mod,Procs) -> + case erlang:check_old_code(Mod) of + true -> + lists:foreach(fun(Pid) -> + case erlang:check_process_code(Pid, Mod) of + false -> ok; + true -> throw({error, Mod}) + end + end, + Procs); + false -> + ok + end. + %%----------------------------------------------------------------- %% An unpurged module is a module for which there exist an old diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl index 9c7733b7ec..40bc2e66c4 100644 --- a/lib/sasl/test/release_handler_SUITE.erl +++ b/lib/sasl/test/release_handler_SUITE.erl @@ -55,7 +55,8 @@ win32_cases() -> %% Cases that can be run on all platforms cases() -> - [otp_2740, otp_2760, otp_5761, otp_9402, otp_9417, instructions, eval_appup]. + [otp_2740, otp_2760, otp_5761, otp_9402, otp_9417, + many_procs, instructions, eval_appup]. groups() -> [{release,[], @@ -556,7 +557,7 @@ otp_2760(Conf) -> LibDir = filename:join([DataDir,app1_app2,lib1]), Rel1 = create_and_install_fake_first_release(Dir,[{app1,"1.0",LibDir}]), - Rel2 = create_fake_upgrade_release(Dir,"after",[],{Rel1,Rel1,[LibDir]}), + Rel2 = create_fake_upgrade_release(Dir,"after",[],{[Rel1],[Rel1],[LibDir]}), Rel2Dir = filename:dirname(Rel2), %% Start a node with Rel1.boot and check that the app1 module is loaded @@ -597,7 +598,7 @@ otp_5761(Conf) when is_list(Conf) -> "2", [{app1,"2.0",LibDir2}, {app2,"1.0",LibDir2}], - {Rel1,Rel1,[LibDir1]}), + {[Rel1],[Rel1],[LibDir1]}), Rel1Dir = filename:dirname(Rel1), Rel2Dir = filename:dirname(Rel2), @@ -673,7 +674,7 @@ otp_9402(Conf) when is_list(Conf) -> Rel2 = create_fake_upgrade_release(Dir, "2", [{a,"1.2",LibDir}], - {Rel1,Rel1,[LibDir]}), + {[Rel1],[Rel1],[LibDir]}), Rel1Dir = filename:dirname(Rel1), Rel2Dir = filename:dirname(Rel2), @@ -737,7 +738,7 @@ otp_9417(Conf) when is_list(Conf) -> Rel2 = create_fake_upgrade_release(Dir, "2", [{b,"2.0",LibDir}], - {Rel1,Rel1,[LibDir]}), + {[Rel1],[Rel1],[LibDir]}), Rel1Dir = filename:dirname(Rel1), Rel2Dir = filename:dirname(Rel2), @@ -777,6 +778,79 @@ otp_9417(Conf) when is_list(Conf) -> {file,BServerBeam} = rpc:call(Node,code,is_loaded,[b_server]), ok. +%% OTP-9395 - performance problems when there are MANY processes +%% Test that the procedure of checking for old code before an upgrade +%% can be started is "very much faster" when there is no old code in +%% the system. +many_procs(Conf) when is_list(Conf) -> + + NProcs = 1000, + NMods = 10, + Modules = [list_to_atom("m"++integer_to_list(N)) || N <- lists:seq(1,NMods)], + Mbins = [generate_module(M) || M <- Modules], + + Pids = [spawn_link(fun() -> + Cs = [M:bar() || M <- Modules], + receive stop -> Cs end + end) || + _ <- lists:seq(1,NProcs)], + + [code:load_binary(Mod,generated_by_release_handler_SUITE,Bin) || + {Mod,Bin} <- Mbins], + + S = [point_of_no_return | + [{remove,{M,soft_purge,soft_purge}} || M <- Modules]], + + {T1,ok} = timer:tc(release_handler_1,check_script,[S,[]]), + [code:purge(M) || M <- Modules], + {T2,ok} = timer:tc(release_handler_1,check_script,[S,[]]), + + lists:foreach(fun(Pid) -> Pid ! stop end, Pids), + lists:foreach(fun(Mod) -> code:purge(Mod), + code:delete(Mod), + code:purge(Mod) + end, Modules), + + if T2 > 0 -> + X = T1/T2, + ct:log("~p procs, ~p mods -> ~n" + "\tWith old code: ~.2f sec~n" + "\tAfter purge: ~.2f sec~n" + "\tT1/T2: ~.2f", + [NProcs,NMods,T1/1000000,T2/1000000,X]), + if X < 1000 -> + ct:fail({not_enough_improvement_after_purge,round(X)}); + true -> + ok + end; + T1 > 0 -> %% Means T1/T2 = infinite + ok; + true -> + ct:fail({unexpected_values,T1,T2}) + end, + ok. + +%% Generate a module with a constant. This constant will be referenced +%% from many processes in order to force a garbage collection from +%% erlang:check_process_code. +generate_module(Mod) -> + Str = lists:concat( + ["-module(",Mod,").\n" + "-compile(export_all).\n" + "bar() -> {now(),[{1,'1',\"1\"},{2,'2',\"2\"},{3,'3',\"3\"},{4,'4',\"4\"},{5,'5',\"5\"},{6,'6',\"6\"},{7,'7',\"7\"},{8,'8',\"8\"},{9,'9',\"9\"},{10,'10',\"10\"},{11,'11',\"11\"},{12,'12',\"12\"},{13,'13',\"13\"},{14,'14',\"14\"},{15,'15',\"15\"},{16,'16',\"16\"},{17,'17',\"17\"},{18,'18',\"18\"},{19,'19',\"19\"},{20,'20',\"20\"},{21,'21',\"21\"},{22,'22',\"22\"},{23,'23',\"23\"},{24,'24',\"24\"},{25,'25',\"25\"},{26,'26',\"26\"},{27,'27',\"27\"},{28,'28',\"28\"},{29,'29',\"29\"},{30,'30',\"30\"},{31,'31',\"31\"},{32,'32',\"32\"},{33,'33',\"33\"},{34,'34',\"34\"},{35,'35',\"35\"},{36,'36',\"36\"},{37,'37',\"37\"},{38,'38',\"38\"},{39,'39',\"39\"},{40,'40',\"40\"},{41,'41',\"41\"},{42,'42',\"42\"},{43,'43',\"43\"},{44,'44',\"44\"},{45,'45',\"45\"},{46,'46',\"46\"},{47,'47',\"47\"},{48,'48',\"48\"},{49,'49',\"49\"},{50,'50',\"50\"},{51,'51',\"51\"},{52,'52',\"52\"},{53,'53',\"53\"},{54,'54',\"54\"},{55,'55',\"55\"},{56,'56',\"56\"},{57,'57',\"57\"},{58,'58',\"58\"},{59,'59',\"59\"},{60,'60',\"60\"},{61,'61',\"61\"},{62,'62',\"62\"},{63,'63',\"63\"},{64,'64',\"64\"},{65,'65',\"65\"},{66,'66',\"66\"},{67,'67',\"67\"},{68,'68',\"68\"},{69,'69',\"69\"},{70,'70',\"70\"},{71,'71',\"71\"},{72,'72',\"72\"},{73,'73',\"73\"},{74,'74',\"74\"},{75,'75',\"75\"},{76,'76',\"76\"},{77,'77',\"77\"},{78,'78',\"78\"},{79,'79',\"79\"},{80,'80',\"80\"},{81,'81',\"81\"},{82,'82',\"82\"},{83,'83',\"83\"},{84,'84',\"84\"},{85,'85',\"85\"},{86,'86',\"86\"},{87,'87',\"87\"},{88,'88',\"88\"},{89,'89',\"89\"},{90,'90',\"90\"},{91,'91',\"91\"},{92,'92',\"92\"},{93,'93',\"93\"},{94,'94',\"94\"},{95,'95',\"95\"},{96,'96',\"96\"},{97,'97',\"97\"},{98,'98',\"98\"},{99,'99',\"99\"},{100,'100',\"100\"}]}.\n"]), + TT = doscan(Str), + Forms = [ begin {ok,Y} = erl_parse:parse_form(X),Y end || X <- TT ], + {ok,Mod,Bin} = compile:forms(Forms), + code:load_binary(Mod,generated_by_release_handler_SUITE,Bin), + {Mod,Bin}. + +doscan([]) -> + []; +doscan(Str) -> + {done,{ok,T,_},C} = erl_scan:tokens([],Str,0), + [ T | doscan(C) ]. + + %% Test upgrade and downgrade of applications eval_appup(Conf) when is_list(Conf) -> @@ -1838,9 +1912,8 @@ create_fake_upgrade_release(Dir,RelVsn,AppDirs,{UpFrom,DownTo,ExtraLibs}) -> %% And a relup file so it can be upgraded to RelupPath = Paths ++ [filename:join([Lib,"*","ebin"]) || Lib <- ExtraLibs], - ok = systools:make_relup(Rel,[UpFrom],[DownTo], - [{path,RelupPath}, - {outdir,RelDir}]), + ok = systools:make_relup(Rel,UpFrom,DownTo,[{path,RelupPath}, + {outdir,RelDir}]), Rel. -- cgit v1.2.3