diff options
author | Jan Lehnardt <jan@apache.org> | 2021-03-13 18:00:12 +0100 |
---|---|---|
committer | Jan Lehnardt <jan@apache.org> | 2021-03-17 15:32:49 +0100 |
commit | bfc25ccd34f77191c0d7329a1e57f4ef6c837dff (patch) | |
tree | a01456f202b1bdade288b65277e8ec63db7b915b | |
parent | 10423740c792a9dcc97b488993b70cbf4f44927e (diff) | |
download | couchdb-bfc25ccd34f77191c0d7329a1e57f4ef6c837dff.tar.gz |
feat: work around get_stacktrace deprecation/removal
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.
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | rebar.config.script | 2 | ||||
-rw-r--r-- | src/chttpd/src/chttpd.erl | 34 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_stats.erl | 6 | ||||
-rw-r--r-- | src/couch/include/couch_db.hrl | 26 | ||||
-rw-r--r-- | src/couch/src/couch_httpd.erl | 12 | ||||
-rw-r--r-- | src/couch/src/couch_proc_manager.erl | 3 | ||||
-rw-r--r-- | src/couch/src/couch_query_servers.erl | 3 | ||||
-rw-r--r-- | src/couch_replicator/src/couch_replicator_scheduler_job.erl | 4 | ||||
-rw-r--r-- | src/ddoc_cache/src/ddoc_cache_entry.erl | 5 | ||||
-rw-r--r-- | src/fabric/src/fabric_rpc.erl | 9 | ||||
-rw-r--r-- | src/fabric/src/fabric_view_all_docs.erl | 3 | ||||
-rw-r--r-- | src/mango/src/mango_httpd.erl | 3 | ||||
-rw-r--r-- | src/mango/src/mango_util.erl | 9 | ||||
-rw-r--r-- | src/rexi/src/rexi_server.erl | 12 |
15 files changed, 75 insertions, 58 deletions
@@ -175,7 +175,7 @@ exunit: export ERL_LIBS = $(shell pwd)/src exunit: export ERL_AFLAGS = -config $(shell pwd)/rel/files/eunit.config exunit: export COUCHDB_QUERY_SERVER_JAVASCRIPT = $(shell pwd)/bin/couchjs $(shell pwd)/share/server/main.js exunit: couch elixir-init setup-eunit elixir-check-formatted elixir-credo - @mix test --cover --trace $(EXUNIT_OPTS) + @mix test --trace $(EXUNIT_OPTS) setup-eunit: export BUILDDIR = $(shell pwd) setup-eunit: export ERL_AFLAGS = -config $(shell pwd)/rel/files/eunit.config diff --git a/rebar.config.script b/rebar.config.script index 03e6fcaf5..cbb2192b7 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -195,7 +195,7 @@ ErlOpts = case os:getenv("ERL_OPTS") of end. AddConfig = [ - {require_otp_vsn, "19|20|21|22"}, + {require_otp_vsn, "19|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 16ed82e41..a677c191a 100644 --- a/src/chttpd/src/chttpd.erl +++ b/src/chttpd/src/chttpd.erl @@ -268,16 +268,15 @@ before_request(HttpReq) -> try chttpd_stats:init(), chttpd_plugin:before_request(HttpReq) - 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, @@ -310,8 +309,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) -> @@ -321,17 +320,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, @@ -343,16 +342,15 @@ 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, 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_stats.erl b/src/chttpd/src/chttpd_stats.erl index 59ec9268d..b76c5618b 100644 --- a/src/chttpd/src/chttpd_stats.erl +++ b/src/chttpd/src/chttpd_stats.erl @@ -1,3 +1,4 @@ + % Licensed under the Apache License, Version 2.0 (the "License"); you may not % use this file except in compliance with the License. You may obtain a copy of % the License at @@ -12,6 +13,8 @@ -module(chttpd_stats). +% for the stacktrace macro only so far +-include_lib("couch/include/couch_db.hrl"). -export([ init/0, @@ -50,8 +53,7 @@ report(HttpReq, 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 830b9bcf4..61e5f73d7 100644 --- a/src/couch/include/couch_db.hrl +++ b/src/couch/include/couch_db.hrl @@ -219,3 +219,29 @@ -type sec_props() :: [tuple()]. -type sec_obj() :: {sec_props()}. +%% 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, + +% for features specific to Erlang/OTP version 20.x (and later versions) +-ifdef(ERLANG_OTP_VERSION_20). +-else. +-define(ERLANG_OTP_VERSION_21_FEATURES, true). +-endif. +% Get the stacktrace in a way that is backwards compatible +-ifdef(ERLANG_OTP_VERSION_21_FEATURES). +-define(STACKTRACE(ErrorType, Error, ErrorStackTrace), + ErrorType:Error:ErrorStackTrace ->). +-else. +-define(STACKTRACE(ErrorType, Error, ErrorStackTrace), + ErrorType:Error -> + ErrorStackTrace = erlang:get_stacktrace(),). +-endif. diff --git a/src/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl index d89c74957..6c0fd8761 100644 --- a/src/couch/src/couch_httpd.erl +++ b/src/couch/src/couch_httpd.erl @@ -363,23 +363,19 @@ handle_request_int(MochiReq, DefaultFun, send_error(HttpReq, request_entity_too_large); exit:{uri_too_long, _} -> send_error(HttpReq, request_uri_too_long); - throw:Error -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(throw, Error, Stack) couch_log:debug("Minor error in HTTP request: ~p",[Error]), couch_log:debug("Stacktrace: ~p",[Stack]), send_error(HttpReq, Error); - error:badarg -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(error, badarg, Stack) couch_log:error("Badarg error in HTTP request",[]), couch_log:info("Stacktrace: ~p",[Stack]), send_error(HttpReq, badarg); - error:function_clause -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(error, function_clause, Stack) couch_log:error("function_clause error in HTTP request",[]), couch_log:info("Stacktrace: ~p",[Stack]), send_error(HttpReq, function_clause); - Tag:Error -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(Tag, Error, Stack) couch_log:error("Uncaught error in HTTP request: ~p", [{Tag, Error}]), couch_log:info("Stacktrace: ~p",[Stack]), diff --git a/src/couch/src/couch_proc_manager.erl b/src/couch/src/couch_proc_manager.erl index 0daef3ee9..1f6d0167b 100644 --- a/src/couch/src/couch_proc_manager.erl +++ b/src/couch/src/couch_proc_manager.erl @@ -308,8 +308,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/src/couch_query_servers.erl b/src/couch/src/couch_query_servers.erl index 75180a7f8..ebf82ab31 100644 --- a/src/couch/src/couch_query_servers.erl +++ b/src/couch/src/couch_query_servers.erl @@ -527,8 +527,7 @@ with_ddoc_proc(#doc{id=DDocId,revs={Start, [DiskRev|_]}}=DDoc, Fun) -> Resp -> ok = ret_os_process(Proc), Resp - catch Tag:Err -> - Stack = erlang:get_stacktrace(), + catch ?STACKTRACE(Tag, Err, Stack) catch proc_stop(Proc), erlang:raise(Tag, Err, Stack) end. diff --git a/src/couch_replicator/src/couch_replicator_scheduler_job.erl b/src/couch_replicator/src/couch_replicator_scheduler_job.erl index 238c725e4..1d328d0a6 100644 --- a/src/couch_replicator/src/couch_replicator_scheduler_job.erl +++ b/src/couch_replicator/src/couch_replicator_scheduler_job.erl @@ -319,9 +319,9 @@ handle_info(timeout, InitArgs) -> catch exit:{http_request_failed, _, _, max_backoff} -> {stop, {shutdown, max_backoff}, {error, InitArgs}}; - Class:Error -> + ?STACKTRACE(Class, Error, Stack) ShutdownReason = {error, replication_start_error(Error)}, - StackTop2 = lists:sublist(erlang:get_stacktrace(), 2), + StackTop2 = lists:sublist(Stack, 2), % Shutdown state is a hack as it is not really the state of the % gen_server (it failed to initialize, so it doesn't have one). % Shutdown state is used to pass extra info about why start failed. diff --git a/src/ddoc_cache/src/ddoc_cache_entry.erl b/src/ddoc_cache/src/ddoc_cache_entry.erl index 4cc3d7e52..32d3ec1a7 100644 --- a/src/ddoc_cache/src/ddoc_cache_entry.erl +++ b/src/ddoc_cache/src/ddoc_cache_entry.erl @@ -14,6 +14,8 @@ -behaviour(gen_server). -vsn(1). +% for the stacktrace macro only so far +-include_lib("couch/include/couch_db.hrl"). -export([ dbname/1, @@ -297,8 +299,7 @@ do_open(Key) -> try recover(Key) of Resp -> erlang:exit({open_ok, Key, Resp}) - catch T:R -> - S = erlang:get_stacktrace(), + catch ?STACKTRACE(T, R, S) erlang:exit({open_error, Key, {T, R, S}}) end. diff --git a/src/fabric/src/fabric_rpc.erl b/src/fabric/src/fabric_rpc.erl index 85da3ff12..7776bd9fe 100644 --- a/src/fabric/src/fabric_rpc.erl +++ b/src/fabric/src/fabric_rpc.erl @@ -330,9 +330,9 @@ with_db(DbName, Options, {M,F,A}) -> apply(M, F, [Db | A]) catch Exception -> Exception; - error:Reason -> + ?STACKTRACE(error, Reason, Stack) couch_log:error("rpc ~p:~p/~p ~p ~p", [M, F, length(A)+1, Reason, - clean_stack()]), + clean_stack(Stack)]), {error, Reason} end); Error -> @@ -596,9 +596,8 @@ make_att_reader({fabric_attachment_receiver, Middleman, Length}) -> make_att_reader(Else) -> Else. -clean_stack() -> - lists:map(fun({M,F,A}) when is_list(A) -> {M,F,length(A)}; (X) -> X end, - erlang:get_stacktrace()). +clean_stack(S) -> + lists:map(fun({M,F,A}) when is_list(A) -> {M,F,length(A)}; (X) -> X end, S). set_io_priority(DbName, Options) -> case lists:keyfind(io_priority, 1, Options) of diff --git a/src/fabric/src/fabric_view_all_docs.erl b/src/fabric/src/fabric_view_all_docs.erl index e4d3d4a40..1b1aa8a9c 100644 --- a/src/fabric/src/fabric_view_all_docs.erl +++ b/src/fabric/src/fabric_view_all_docs.erl @@ -296,8 +296,7 @@ open_doc(DbName, Options, Id, IncludeDocs) -> try open_doc_int(DbName, Options, Id, IncludeDocs) of #view_row{} = Row -> exit(Row) - catch Type:Reason -> - Stack = erlang:get_stacktrace(), + catch ?STACKTRACE(Type, Reason, Stack) couch_log:error("_all_docs open error: ~s ~s :: ~w ~w", [ DbName, Id, {Type, Reason}, Stack]), exit({Id, Reason}) diff --git a/src/mango/src/mango_httpd.erl b/src/mango/src/mango_httpd.erl index 379d2e127..624691bb9 100644 --- a/src/mango/src/mango_httpd.erl +++ b/src/mango/src/mango_httpd.erl @@ -37,10 +37,9 @@ handle_req(#httpd{} = Req, Db0) -> Db = set_user_ctx(Req, Db0), 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) diff --git a/src/mango/src/mango_util.erl b/src/mango/src/mango_util.erl index 0d31f15f9..8257e841f 100644 --- a/src/mango/src/mango_util.erl +++ b/src/mango/src/mango_util.erl @@ -138,16 +138,13 @@ do_defer(Mod, Fun, Args) -> Resp -> erlang:exit({mango_defer_ok, Resp}) catch - throw:Error -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(throw, Error, Stack) couch_log:error("Defered error: ~w~n ~p", [{throw, Error}, Stack]), erlang:exit({mango_defer_throw, Error}); - error:Error -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(error, Error, Stack) couch_log:error("Defered error: ~w~n ~p", [{error, Error}, Stack]), erlang:exit({mango_defer_error, Error}); - exit:Error -> - Stack = erlang:get_stacktrace(), + ?STACKTRACE(exit, Error, Stack) couch_log:error("Defered error: ~w~n ~p", [{exit, Error}, Stack]), erlang:exit({mango_defer_exit, Error}) end. diff --git a/src/rexi/src/rexi_server.erl b/src/rexi/src/rexi_server.erl index fedff69c3..4cd9ce66e 100644 --- a/src/rexi/src/rexi_server.erl +++ b/src/rexi/src/rexi_server.erl @@ -18,6 +18,9 @@ -export([start_link/1, init_p/2, init_p/3]). +% for the stacktrace macro only so far +-include_lib("couch/include/couch_db.hrl"). + -include_lib("rexi/include/rexi.hrl"). -record(job, { @@ -135,8 +138,8 @@ init_p(From, {M,F,A}, Nonce) -> put(rexi_from, From), put('$initial_call', {M,F,length(A)}), put(nonce, Nonce), - try apply(M, F, A) catch exit:normal -> ok; Class:Reason -> - Stack = clean_stack(), + try apply(M, F, A) catch exit:normal -> ok; ?STACKTRACE(Class, Reason, Stack0) + Stack = clean_stack(Stack0), {ClientPid, _ClientRef} = From, couch_log:error( "rexi_server: from: ~s(~p) mfa: ~s:~s/~p ~p:~p ~100p", [ @@ -160,9 +163,8 @@ save_error(E, #st{errors=Q, error_limit=L, error_count=C} = St) when C >= L -> save_error(E, #st{errors=Q, error_count=C} = St) -> St#st{errors = queue:in(E, Q), error_count = C+1}. -clean_stack() -> - lists:map(fun({M,F,A}) when is_list(A) -> {M,F,length(A)}; (X) -> X end, - erlang:get_stacktrace()). +clean_stack(S) -> + lists:map(fun({M,F,A}) when is_list(A) -> {M,F,length(A)}; (X) -> X end, S). add_job(Job, #st{workers = Workers, clients = Clients} = State) -> ets:insert(Workers, Job), |