chewbranca opened a new issue, #4890:
URL: https://github.com/apache/couchdb/issues/4890

   [NOTE]: # ( ^^ Provide a general summary of the issue in the title above. ^^ 
)
   
   ## Description
   
   There are two subsets of `config:get*` accessors: 1) baseline `get` in 
functions `config:get/{1,2,3}`; 2) three wrapper functions around the baseline 
`get` for type coercion: `get_integer/3`, `get_boolean/3`, and `get_float/3`.
   
   The bug here is that the baseline `get` accessor fails with an ets exception 
in the event the ets table does not exist, crashing the caller and _not_ 
providing any config value, whereas using the secondary category of type 
coercion wrappers _catches_ the exception out of the baseline `get` call and 
then returns the default value, rather than allowing the exception to bubble up.
   
   The implication of this is that live config lookups can suddenly revert to 
default values in the event the proper set of values stored in the ets table 
becomes unavailable. This is obviously not good.
   
   In practice, `config` is pretty reliable at this point, and I only stumbled 
upon it while debugging eunit tests where we're constantly spinning up and 
tearing down subsets of CouchDB applications, resulting in numerous instances 
of config lookup crashes during teardown when there were still live requests 
but the ets table did not exist. So I don't think this is commonly occurring 
today, but the type coercion functions landed a decade ago [1], and I've 
personally run `exit(whereis(config))` on a number of occasions, so I know this 
happened a few times at least.
   
   This issue is further complicated by the fact that the `try ... catch 
error:badarg ... end` logic in the typed accessors is at least triple 
overloaded. The core pattern for all three functions is the following, with 
`s/integer/float/g` and `s/integer/boolean/g` for `get_integer/3` and 
`get_boolean/3`, respectively:
   
   ```erlang
   get_integer(Section, Key, Default) when is_integer(Default) ->
       try
           to_integer(get(Section, Key, Default))
       catch
           error:badarg ->
               Default
       end.
   ```
   
   The three scenarios I'm aware of that can trigger the catch statement 
resulting in the Default value flowing through are:
   
     1) it's not the appropriate data type, so conversion functions like 
`list_to_integer` throw `{error, badarg}`.
     2) baseline `get/{1,2,3}` throws `{error, badarg}` when the ets table 
doesn't exist
     3) `get/3` throws `{error, badarg}` itself when the ets:lookup succeeds 
with zero results and default is a bad data type [2]
   
   
   ## Steps to Reproduce
   
   This can be done independently of CouchDB with the raw `config.beam` file:
   
   ```erlang
   chewbranca@Russells-MacBook-Pro-2 db % erl -pa src/config/ebin
   Erlang/OTP 25 [erts-13.2.2.3] [source] [64-bit] [smp:10:10] [ds:10:10:10] 
[async-threads:1]
   
   Eshell V13.2.2.3  (abort with ^G)
   1> config:get_boolean("foo", "bar", false).
   false
   2> config:get("foo", "bar", "true").
   ** exception error: bad argument
        in function  ets:lookup/2
           called as ets:lookup(config,{"foo","bar"})
           *** argument 1: the table identifier does not refer to an existing 
ETS table
        in call from config:get/3 (src/config.erl, line 161)
   3>
   ```
   
   ## Expected Behaviour
   
   I don't think there's ever a scenario where we want the user provided 
settings in default.ini and local.ini to be ignored because the cache 
disappeared resulting in reverting to unexpected default values. I think we 
should always crash when the ets table doesn't exist for normal database 
operations.
   
   I do however think we should wire up the eunit test suite such that we can 
toggle off use of the ini provide config values and force use of all default 
values in the code base. We could achieve this by way of something like 
meck'ing out `config:get/3` as `fun(_Key, _Section, Default) -> Default end.`
   
   On a positive note, it's fairly easy to induce that behavior by way of 
moving the call to `get/3` in each of the three typed accessors outside of the 
try-catch block such that we allow lookup exceptions but catch data type 
exceptions.
   
   The following diff:
   
   ```diff
   diff --git a/src/config/src/config.erl b/src/config/src/config.erl
   index 3ece5326b..30c666bb2 100644
   --- a/src/config/src/config.erl
   +++ b/src/config/src/config.erl
   @@ -68,8 +68,9 @@ all() ->
        lists:sort(gen_server:call(?MODULE, all, infinity)).
    
    get_integer(Section, Key, Default) when is_integer(Default) ->
   +    Val = get(Section, Key, Default),
        try
   -        to_integer(get(Section, Key, Default))
   +        to_integer(Val)
        catch
            error:badarg ->
                Default
   @@ -91,8 +92,9 @@ to_integer(Bin) when is_binary(Bin) ->
        list_to_integer(binary_to_list(Bin)).
    
    get_float(Section, Key, Default) when is_float(Default) ->
   +    Val = get(Section, Key, Default),
        try
   -        to_float(get(Section, Key, Default))
   +        to_float(Val)
        catch
            error:badarg ->
                Default
   @@ -116,8 +118,9 @@ to_float(Bin) when is_binary(Bin) ->
        list_to_float(binary_to_list(Bin)).
    
    get_boolean(Section, Key, Default) when is_boolean(Default) ->
   +    Val = get(Section, Key, Default),
        try
   -        to_boolean(get(Section, Key, Default))
   +        to_boolean(Val)
        catch
            error:badarg ->
                Default
   ```
   
   gives the desired behavior:
   
   ```erlang
   chewbranca@Russells-MBP-2 couchdb % erl -pa src/config/ebin      
   Erlang/OTP 25 [erts-13.2.2.3] [source] [64-bit] [smp:10:10] [ds:10:10:10] 
[async-threads:1]
   
   Eshell V13.2.2.3  (abort with ^G)
   1> config:get_boolean("foo", "bar", false).
   ** exception error: bad argument
        in function  ets:lookup/2
           called as ets:lookup(config,{"foo","bar"})
           *** argument 1: the table identifier does not refer to an existing 
ETS table
        in call from config:get/3 (src/config.erl, line 163)
        in call from config:get_boolean/3 (src/config.erl, line 121)
   2> config:get("foo", "bar", "true").       
   ** exception error: bad argument
        in function  ets:lookup/2
           called as ets:lookup(config,{"foo","bar"})
           *** argument 1: the table identifier does not refer to an existing 
ETS table
        in call from config:get/3 (src/config.erl, line 163)
   3> %                                                                         
                                                                                
                                                                                
        
   ```
   
   If folks think that's a reasonable approach I can move that over to a PR.
   
   
   ## Additional Context
   
   [1] 
https://github.com/apache/couchdb-config/commit/6859d11114af0911b91d6e97ac2d87d00c2a3bc3
   [2] https://github.com/apache/couchdb-config/blob/master/src/config.erl#L167
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to