From 7903914cf084ca2d64df102a2582f096cec57079 Mon Sep 17 00:00:00 2001 From: Jan Lehnardt Date: Sat, 13 Mar 2021 18:00:12 +0100 Subject: feat: work around get_stacktrace deprecation/removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduces a macro and inserts it everywhere we catch errors and then generatre a stacktrace. So far the only thing that is a little bit ugly is that in two places, I had to add a header include dependency on couch_db.erl where those modules didn’t have any ties to couchdb/* before, alas. I’d be willing to duplicate the macros in those modules, if we don’t want the include dependency. --- rebar.config.script | 8 ++--- src/chttpd/src/chttpd.erl | 40 ++++++++++------------ src/chttpd/src/chttpd_handlers.erl | 3 +- src/chttpd/src/chttpd_stats.erl | 5 +-- src/couch/include/couch_db.hrl | 24 +++++++++++++ src/couch/src/couch_proc_manager.erl | 3 +- src/couch_js/src/couch_js_proc_manager.erl | 3 +- src/couch_replicator/src/couch_replicator_job.erl | 23 ++++++------- .../src/couch_replicator_parse.erl | 13 +++---- src/couch_views/src/couch_views_indexer.erl | 3 +- src/couch_views/src/couch_views_updater.erl | 3 +- src/ctrace/src/ctrace.erl | 3 +- src/fabric/src/fabric2_db_expiration.erl | 3 +- src/fabric/src/fabric2_index.erl | 6 ++-- src/fabric/src/fabric2_util.erl | 16 +-------- src/mango/src/mango_httpd.erl | 3 +- 16 files changed, 77 insertions(+), 82 deletions(-) diff --git a/rebar.config.script b/rebar.config.script index 0f40cf05c..6dafa8f89 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -139,7 +139,7 @@ DepDescs = [ %% Independent Apps {config, "config", {tag, "2.1.8"}}, {b64url, "b64url", {tag, "1.0.2"}}, -{erlfdb, "erlfdb", {tag, "v1.3.3"}}, +{erlfdb, "erlfdb", {tag, "v1.3.4"}}, {ets_lru, "ets-lru", {tag, "1.1.0"}}, %% Non-Erlang deps @@ -151,8 +151,8 @@ DepDescs = [ {folsom, "folsom", {tag, "CouchDB-0.8.3"}}, {hyper, "hyper", {tag, "CouchDB-2.2.0-6"}}, {ibrowse, "ibrowse", {tag, "CouchDB-4.0.1-2"}}, -{jaeger_passage, "jaeger-passage", {tag, "CouchDB-0.1.14-1"}}, -{jiffy, "jiffy", {tag, "CouchDB-1.0.4-1"}}, +{jaeger_passage, "jaeger-passage", {tag, "CouchDB-0.1.14-2"}}, +{jiffy, "jiffy", {tag, "CouchDB-1.0.5-1"}}, {local, "local", {tag, "0.2.1"}}, {mochiweb, "mochiweb", {tag, "v2.20.0"}}, {meck, "meck", {tag, "0.8.8"}}, @@ -191,7 +191,7 @@ ErlOpts = case os:getenv("ERL_OPTS") of end. AddConfig = [ - {require_otp_vsn, "19|20|21|22"}, + {require_otp_vsn, "20|21|22|23"}, {deps_dir, "src"}, {deps, lists:map(MakeDep, DepDescs ++ OptionalDeps)}, {sub_dirs, SubDirs}, diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl index 8567adaeb..135c4ec0b 100644 --- a/src/chttpd/src/chttpd.erl +++ b/src/chttpd/src/chttpd.erl @@ -278,16 +278,15 @@ before_request(HttpReq) -> {ok, HttpReq1} = chttpd_plugin:before_request(HttpReq), chttpd_stats:init(HttpReq1), {ok, HttpReq1} - catch Tag:Error -> - {error, catch_error(HttpReq, Tag, Error)} + catch ?STACKTRACE(Tag, Error, Stack) + {error, catch_error(HttpReq, Tag, Error, Stack)} end. after_request(HttpReq, HttpResp0) -> {ok, HttpResp1} = try chttpd_plugin:after_request(HttpReq, HttpResp0) - catch _Tag:Error -> - Stack = erlang:get_stacktrace(), + catch ?STACKTRACE(_Tag, Error, Stack) send_error(HttpReq, {Error, nil, Stack}), {ok, HttpResp0#httpd_resp{status = aborted}} end, @@ -320,8 +319,8 @@ process_request(#httpd{mochi_req = MochiReq} = HttpReq) -> Response -> {HttpReq, Response} end - catch Tag:Error -> - {HttpReq, catch_error(HttpReq, Tag, Error)} + catch ?STACKTRACE(Tag, Error, Stack) + {HttpReq, catch_error(HttpReq, Tag, Error, Stack)} end. handle_req_after_auth(HandlerKey, HttpReq) -> @@ -333,17 +332,17 @@ handle_req_after_auth(HandlerKey, HttpReq) -> AuthorizedReq = chttpd_auth:authorize(possibly_hack(HttpReq), fun chttpd_auth_request:authorize_request/1), {AuthorizedReq, HandlerFun(AuthorizedReq)} - catch Tag:Error -> - {HttpReq, catch_error(HttpReq, Tag, Error)} + catch ?STACKTRACE(Tag, Error, Stack) + {HttpReq, catch_error(HttpReq, Tag, Error, Stack)} end. -catch_error(_HttpReq, throw, {http_head_abort, Resp}) -> +catch_error(_HttpReq, throw, {http_head_abort, Resp}, _Stack) -> {ok, Resp}; -catch_error(_HttpReq, throw, {http_abort, Resp, Reason}) -> +catch_error(_HttpReq, throw, {http_abort, Resp, Reason}, _Stack) -> {aborted, Resp, Reason}; -catch_error(HttpReq, throw, {invalid_json, _}) -> +catch_error(HttpReq, throw, {invalid_json, _}, _Stack) -> send_error(HttpReq, {bad_request, "invalid UTF-8 JSON"}); -catch_error(HttpReq, exit, {mochiweb_recv_error, E}) -> +catch_error(HttpReq, exit, {mochiweb_recv_error, E}, _Stack) -> #httpd{ mochi_req = MochiReq, peer = Peer, @@ -355,22 +354,21 @@ catch_error(HttpReq, exit, {mochiweb_recv_error, E}) -> MochiReq:get(raw_path), E]), exit(normal); -catch_error(HttpReq, exit, {uri_too_long, _}) -> +catch_error(HttpReq, exit, {uri_too_long, _}, _Stack) -> send_error(HttpReq, request_uri_too_long); -catch_error(HttpReq, exit, {body_too_large, _}) -> +catch_error(HttpReq, exit, {body_too_large, _}, _Stack) -> send_error(HttpReq, request_entity_too_large); -catch_error(HttpReq, throw, Error) -> +catch_error(HttpReq, throw, Error, _Stack) -> send_error(HttpReq, Error); -catch_error(HttpReq, error, database_does_not_exist) -> +catch_error(HttpReq, error, database_does_not_exist, _Stack) -> send_error(HttpReq, database_does_not_exist); -catch_error(HttpReq, error, decryption_failed) -> +catch_error(HttpReq, error, decryption_failed, _Stack) -> send_error(HttpReq, decryption_failed); -catch_error(HttpReq, error, not_ciphertext) -> +catch_error(HttpReq, error, not_ciphertext, _Stack) -> send_error(HttpReq, not_ciphertext); -catch_error(HttpReq, error, {erlfdb_error, _} = Error) -> +catch_error(HttpReq, error, {erlfdb_error, _} = Error, _Stack) -> send_error(HttpReq, Error); -catch_error(HttpReq, Tag, Error) -> - Stack = erlang:get_stacktrace(), +catch_error(HttpReq, Tag, Error, Stack) -> % TODO improve logging and metrics collection for client disconnects case {Tag, Error, Stack} of {exit, normal, [{mochiweb_request, send, _, _} | _]} -> diff --git a/src/chttpd/src/chttpd_handlers.erl b/src/chttpd/src/chttpd_handlers.erl index 17d2952b3..43631c058 100644 --- a/src/chttpd/src/chttpd_handlers.erl +++ b/src/chttpd/src/chttpd_handlers.erl @@ -44,8 +44,7 @@ handler_info(HttpReq) -> Default = {'unknown.unknown', #{}}, try select(collect(handler_info, [Method, PathParts, HttpReq]), Default) - catch Type:Reason -> - Stack = erlang:get_stacktrace(), + catch ?STACKTRACE(Type, Reason, Stack) couch_log:error("~s :: handler_info failure for ~p : ~p:~p :: ~p", [ ?MODULE, get(nonce), diff --git a/src/chttpd/src/chttpd_stats.erl b/src/chttpd/src/chttpd_stats.erl index 27e9c3180..ae1efa486 100644 --- a/src/chttpd/src/chttpd_stats.erl +++ b/src/chttpd/src/chttpd_stats.erl @@ -12,6 +12,8 @@ -module(chttpd_stats). +% for the stacktrace macro only so far +-include_lib("couch/include/couch_db.hrl"). -export([ init/1, @@ -61,8 +63,7 @@ report(HttpResp) -> _ -> ok end - catch T:R -> - S = erlang:get_stacktrace(), + catch ?STACKTRACE(T, R, S) Fmt = "Failed to report chttpd request stats: ~p:~p ~p", couch_log:error(Fmt, [T, R, S]) end. diff --git a/src/couch/include/couch_db.hrl b/src/couch/include/couch_db.hrl index 22890895a..01dc0f4a1 100644 --- a/src/couch/include/couch_db.hrl +++ b/src/couch/include/couch_db.hrl @@ -178,3 +178,27 @@ -define(record_to_keyval(Name, Record), lists:zip(record_info(fields, Name), tl(tuple_to_list(Record)))). + +%% Erlang/OTP 21 deprecates and 23 removes get_stacktrace(), so +%% we have to monkey around until we can drop support < 21. +%% h/t https://github.com/erlang/otp/pull/1783#issuecomment-386190970 + +%% use like so: +% try function1(Arg1) +% catch +% ?STACKTRACE(exit, badarg, ErrorStackTrace) +% % do stuff with ErrorStackTrace +% % ... +% end, + +% Get the stacktrace in a way that is backwards compatible +% OTP_RELEASE is only available in OTP 21 and later, so we don’t need +% to do any other version magic here. +-ifdef(OTP_RELEASE). +-define(STACKTRACE(ErrorType, Error, Stack), + ErrorType:Error:Stack ->). +-else. +-define(STACKTRACE(ErrorType, Error, Stack), + ErrorType:Error -> + Stack = erlang:get_stacktrace(),). +-endif. diff --git a/src/couch/src/couch_proc_manager.erl b/src/couch/src/couch_proc_manager.erl index b83d78882..6a0322c73 100644 --- a/src/couch/src/couch_proc_manager.erl +++ b/src/couch/src/couch_proc_manager.erl @@ -321,8 +321,7 @@ find_proc(#client{lang = Lang, ddoc = DDoc, ddoc_key = DDocKey} = Client) -> find_proc(Lang, Fun) -> try iter_procs(Lang, Fun) - catch error:Reason -> - StackTrace = erlang:get_stacktrace(), + catch ?STACKTRACE(error, Reason, StackTrace) couch_log:error("~p ~p ~p", [?MODULE, Reason, StackTrace]), {error, Reason} end. diff --git a/src/couch_js/src/couch_js_proc_manager.erl b/src/couch_js/src/couch_js_proc_manager.erl index db5c492f5..4b3354d6e 100644 --- a/src/couch_js/src/couch_js_proc_manager.erl +++ b/src/couch_js/src/couch_js_proc_manager.erl @@ -320,8 +320,7 @@ find_proc(#client{lang = Lang, ddoc = DDoc, ddoc_key = DDocKey} = Client) -> find_proc(Lang, Fun) -> try iter_procs(Lang, Fun) - catch error:Reason -> - StackTrace = erlang:get_stacktrace(), + catch ?STACKTRACE(error, Reason, StackTrace) couch_log:error("~p ~p ~p", [?MODULE, Reason, StackTrace]), {error, Reason} end. diff --git a/src/couch_replicator/src/couch_replicator_job.erl b/src/couch_replicator/src/couch_replicator_job.erl index c8c143a58..d11bdb738 100644 --- a/src/couch_replicator/src/couch_replicator_job.erl +++ b/src/couch_replicator/src/couch_replicator_job.erl @@ -221,12 +221,12 @@ handle_info(timeout, delayed_init) -> {ok, State} -> {noreply, State}; {stop, Reason, State} -> {stop, Reason, State} catch - exit:{shutdown, Exit} when Exit =:= finished orelse Exit =:= halt -> - Stack = erlang:get_stacktrace(), - {stop, {shutdown, Exit}, {init_error, Stack}}; - _Tag:Error -> + ?STACKTRACE(exit, {shutdown, finished}, Stack) + {stop, {shutdown, finished}, {init_error, Stack}}; + ?STACKTRACE(exit, {shtudown, halt}, Stack) + {stop, {shutdown, halt}, {init_error, Stack}}; + ?STACKTRACE(_Tag, Error, Stack) ShutdownReason = {error, replication_start_error(Error)}, - Stack = erlang:get_stacktrace(), {stop, {shutdown, ShutdownReason}, {init_error, Stack}} end; @@ -406,16 +406,15 @@ delayed_init() -> try do_init(Job, JobData) of State = #rep_state{} -> {ok, State} catch - exit:{http_request_failed, _, _, max_backoff} -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(exit, {http_request_failed, _, _, max_backoff}, Stack) reschedule_on_error(undefined, Job, JobData, max_backoff), {stop, {shutdown, max_backoff}, {init_error, Stack}}; - exit:{shutdown, Exit} when Exit =:= finished orelse Exit =:= halt -> - Stack = erlang:get_stacktrace(), - {stop, {shutdown, Exit}, {init_error, Stack}}; - _Tag:Error -> + ?STACKTRACE(exit, {shutdown, finished}, Stack) + {stop, {shutdown, finished}, {init_error, Stack}}; + ?STACKTRACE(exit, {shutdown, halt}, Stack) + {stop, {shutdown, halt}, {init_error, Stack}}; + ?STACKTRACE(_Tag, Error, Stack) Reason = {error, replication_start_error(Error)}, - Stack = erlang:get_stacktrace(), ErrMsg = "~p : job ~p failed during startup ~p stack:~p", couch_log:error(ErrMsg, [?MODULE, Job, Reason, Stack]), reschedule_on_error(undefined, Job, JobData, Reason), diff --git a/src/couch_replicator/src/couch_replicator_parse.erl b/src/couch_replicator/src/couch_replicator_parse.erl index 5996ec507..79c778d23 100644 --- a/src/couch_replicator/src/couch_replicator_parse.erl +++ b/src/couch_replicator/src/couch_replicator_parse.erl @@ -22,6 +22,7 @@ -include_lib("ibrowse/include/ibrowse.hrl"). +-include_lib("couch/include/couch_db.hrl"). -include("couch_replicator.hrl"). @@ -59,13 +60,11 @@ parse_rep_doc(RepDoc) -> {ok, Rep} = try parse_rep(RepDoc, null) catch - throw:{error, Reason} -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(throw, {error, Reason}, Stack) LogErr1 = "~p parse_rep_doc fail ~p ~p", couch_log:error(LogErr1, [?MODULE, Reason, Stack]), throw({bad_rep_doc, Reason}); - Tag:Err -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(Tag, Err, Stack) LogErr2 = "~p parse_rep_doc fail ~p:~p ~p", couch_log:error(LogErr2, [?MODULE, Tag, Err, Stack]), throw({bad_rep_doc, couch_util:to_binary({Tag, Err})}) @@ -83,13 +82,11 @@ parse_transient_rep(#{} = Body, UserName) -> {ok, Rep} = try parse_rep(Body, UserName) catch - throw:{error, Reason} -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(throw, {error, Reason}, Stack) LogErr1 = "~p parse_transient_rep fail ~p ~p", couch_log:error(LogErr1, [?MODULE, Reason, Stack]), throw({bad_request, Reason}); - Tag:Err -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(Tag, Err, Stack) LogErr2 = "~p parse_transient_rep fail ~p ~p", couch_log:error(LogErr2, [?MODULE, Tag, Err, Stack]), throw({bad_request, couch_util:to_binary({Tag, Err})}) diff --git a/src/couch_views/src/couch_views_indexer.erl b/src/couch_views/src/couch_views_indexer.erl index 88b1ff623..2164a5827 100644 --- a/src/couch_views/src/couch_views_indexer.erl +++ b/src/couch_views/src/couch_views_indexer.erl @@ -121,8 +121,7 @@ init() -> ok; error:database_does_not_exist -> fail_job(Job, Data, db_deleted, "Database was deleted"); - Error:Reason -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(Error, Reason, Stack) Fmt = "Error building view for ddoc ~s in ~s: ~p:~p ~p", couch_log:error(Fmt, [DbName, DDocId, Error, Reason, Stack]), diff --git a/src/couch_views/src/couch_views_updater.erl b/src/couch_views/src/couch_views_updater.erl index defdb6ba6..7c96bb8bb 100644 --- a/src/couch_views/src/couch_views_updater.erl +++ b/src/couch_views/src/couch_views_updater.erl @@ -31,8 +31,7 @@ index(Db, #doc{id = Id, revs = Revs} = Doc, _NewWinner, _OldWinner, NewRevId, index_int(Db, Doc, Seq) end catch - error:{erlfdb_error, ErrCode} when is_integer(ErrCode) -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(error, {erlfdb_error, ErrCode}, Stack) DbName = fabric2_db:name(Db), couch_log:error("Mango index erlfdb error Db ~s Doc ~p ~p", [DbName, Id, ErrCode]), diff --git a/src/ctrace/src/ctrace.erl b/src/ctrace/src/ctrace.erl index 5521901fd..8ae51c487 100644 --- a/src/ctrace/src/ctrace.erl +++ b/src/ctrace/src/ctrace.erl @@ -109,8 +109,7 @@ with_span(Operation, Options, Fun) -> try start_span(Operation, Options), Fun() - catch Type:Reason -> - Stack = erlang:get_stacktrace(), + catch ?STACKTRACE(Type, Reason, Stack) log(#{ ?LOG_FIELD_ERROR_KIND => Type, ?LOG_FIELD_MESSAGE => Reason, diff --git a/src/fabric/src/fabric2_db_expiration.erl b/src/fabric/src/fabric2_db_expiration.erl index 92f22e749..94cb61b82 100644 --- a/src/fabric/src/fabric2_db_expiration.erl +++ b/src/fabric/src/fabric2_db_expiration.erl @@ -131,8 +131,7 @@ cleanup(true) -> {ok, Job1, Data1} = ?MODULE:process_expirations(Job, Data), ok = resubmit_job(Job1, Data1, schedule_sec()) catch - _Tag:Error -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(_Tag, Error, Stack) couch_log:error("~p : processing error ~p ~p ~p", [?MODULE, Job, Error, Stack]), ok = resubmit_job(Job, Data, ?ERROR_RESCHEDULE_SEC), diff --git a/src/fabric/src/fabric2_index.erl b/src/fabric/src/fabric2_index.erl index 25c31a8c8..3c59cd7e2 100644 --- a/src/fabric/src/fabric2_index.erl +++ b/src/fabric/src/fabric2_index.erl @@ -67,8 +67,7 @@ cleanup(Db) -> catch error:database_does_not_exist -> ok; - Tag:Reason -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(Tag, Reason, Stack) DbName = fabric2_db:name(Db), LogMsg = "~p failed to cleanup indices for `~s` ~p:~p ~p", couch_log:error(LogMsg, [?MODULE, DbName, Tag, Reason, Stack]) @@ -168,8 +167,7 @@ process_updates_iter([Db | Rest], Cont) -> catch error:database_does_not_exist -> ok; - Tag:Reason -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(Tag, Reason, Stack) LogMsg = "~p failed to build indices for `~s` ~p:~p ~p", couch_log:error(LogMsg, [?MODULE, Db, Tag, Reason, Stack]) end, diff --git a/src/fabric/src/fabric2_util.erl b/src/fabric/src/fabric2_util.erl index 7c212521d..f95e410c6 100644 --- a/src/fabric/src/fabric2_util.erl +++ b/src/fabric/src/fabric2_util.erl @@ -383,23 +383,9 @@ pmap(Fun, Args, Opts) -> end, Refs). -% OTP_RELEASE is defined in OTP 21+ only --ifdef(OTP_RELEASE). - pmap_exec(Fun, Arg) -> try {'$res', Fun(Arg)} - catch Tag:Reason:Stack -> + catch ?STACKTRACE(Tag, Reason, Stack) {'$err', Tag, Reason, Stack} end. - --else. - -pmap_exec(Fun, Arg) -> - try - {'$res', Fun(Arg)} - catch Tag:Reason -> - {'$err', Tag, Reason, erlang:get_stacktrace()} - end. - --endif. diff --git a/src/mango/src/mango_httpd.erl b/src/mango/src/mango_httpd.erl index 8d5a2123d..9ac4dfc88 100644 --- a/src/mango/src/mango_httpd.erl +++ b/src/mango/src/mango_httpd.erl @@ -36,10 +36,9 @@ handle_req(#httpd{} = Req, Db) -> try handle_req_int(Req, Db) catch - throw:{mango_error, Module, Reason} -> + ?STACKTRACE(throw, {mango_error, Module, Reason}, Stack) case mango_error:info(Module, Reason) of {500, ErrorStr, ReasonStr} -> - Stack = erlang:get_stacktrace(), chttpd:send_error(Req, {ErrorStr, ReasonStr, Stack}); {Code, ErrorStr, ReasonStr} -> chttpd:send_error(Req, Code, ErrorStr, ReasonStr) -- cgit v1.2.1