You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2020/03/13 22:43:03 UTC
[couchdb] branch refactor-tx-too-old-tests created (now b6b5586)
This is an automated email from the ASF dual-hosted git repository.
vatamane pushed a change to branch refactor-tx-too-old-tests
in repository https://gitbox.apache.org/repos/asf/couchdb.git.
at b6b5586 Refactor some of the "tx_too_old" tests
This branch includes the following new commits:
new b6b5586 Refactor some of the "tx_too_old" tests
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[couchdb] 01/01: Refactor some of the "tx_too_old" tests
Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
vatamane pushed a commit to branch refactor-tx-too-old-tests
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit b6b5586a625dd5dc2df7d36dcece59612a380d41
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Fri Mar 13 18:28:51 2020 -0400
Refactor some of the "tx_too_old" tests
* There was a good amount of duplication between `_db_crud_tests` and
`_changes_fold_tests`, so make a common test utility module so both suites can
use.
* Clean up test names. Previously some were named `tx_too_long` but since the
official FDB error is `transaction_too_old` rename them to match a bit
better.
* `list_dbs_info` implementation queue of 100 futures to parallelize fetching.
So its test was update to create more than 100 dbs. Creating 100 dbs took
about 3 seconds so add a small parallel map (pmap) utility function to help
with that.
---
src/fabric/test/fabric2_changes_fold_tests.erl | 103 +++++++--------------
src/fabric/test/fabric2_db_crud_tests.erl | 118 +++++++++++--------------
src/fabric/test/fabric2_tutil.erl | 99 +++++++++++++++++++++
3 files changed, 186 insertions(+), 134 deletions(-)
diff --git a/src/fabric/test/fabric2_changes_fold_tests.erl b/src/fabric/test/fabric2_changes_fold_tests.erl
index fddf180..8fedae1 100644
--- a/src/fabric/test/fabric2_changes_fold_tests.erl
+++ b/src/fabric/test/fabric2_changes_fold_tests.erl
@@ -21,9 +21,6 @@
-define(DOC_COUNT, 25).
--define(PDICT_ERROR_IN_FOLD_RANGE, '$fabric2_error_in_fold_range').
--define(PDICT_ERROR_IN_USER_FUN, '$fabric2_error_throw_in_user_fun').
-
changes_fold_test_() ->
{
@@ -43,10 +40,10 @@ changes_fold_test_() ->
?TDEF_FE(fold_changes_basic_rev),
?TDEF_FE(fold_changes_since_now_rev),
?TDEF_FE(fold_changes_since_seq_rev),
- ?TDEF_FE(fold_changes_basic_tx_too_long),
- ?TDEF_FE(fold_changes_reverse_tx_too_long),
- ?TDEF_FE(fold_changes_tx_too_long_with_single_row_emits),
- ?TDEF_FE(fold_changes_since_seq_tx_too_long),
+ ?TDEF_FE(fold_changes_basic_tx_too_old),
+ ?TDEF_FE(fold_changes_reverse_tx_too_old),
+ ?TDEF_FE(fold_changes_tx_too_old_with_single_row_emits),
+ ?TDEF_FE(fold_changes_since_seq_tx_too_old),
?TDEF_FE(fold_changes_not_progressing)
]
}
@@ -66,10 +63,7 @@ teardown_all(Ctx) ->
setup() ->
- meck:expect(erlfdb, fold_range, fun(Tx, Start, End, Callback, Acc, Opts) ->
- maybe_tx_too_long(?PDICT_ERROR_IN_FOLD_RANGE),
- meck:passthrough([Tx, Start, End, Callback, Acc, Opts])
- end),
+ fabric2_tutil:tx_too_old_mock_erlfdb(),
{ok, Db} = fabric2_db:create(?tempdb(), [{user_ctx, ?ADMIN_USER}]),
Rows = lists:map(fun(Val) ->
DocId = fabric2_util:uuid(),
@@ -90,7 +84,7 @@ setup() ->
cleanup({Db, _DocIdRevs}) ->
- reset_error_counts(),
+ fabric2_tutil:tx_too_old_reset_errors(),
ok = fabric2_db:delete(fabric2_db:name(Db), []).
@@ -130,131 +124,114 @@ fold_changes_since_seq_rev({Db, DocRows}) ->
fold_changes_since_seq_rev({Db, RestRows}).
-fold_changes_basic_tx_too_long({Db, DocRows0}) ->
+fold_changes_basic_tx_too_old({Db, DocRows0}) ->
DocRows = lists:reverse(DocRows0),
- tx_too_long_errors(0, 1),
+ fabric2_tutil:tx_too_old_setup_errors(0, 1),
?assertEqual(DocRows, changes(Db)),
- tx_too_long_errors(1, 0),
+ fabric2_tutil:tx_too_old_setup_errors(1, 0),
?assertEqual(DocRows, changes(Db)),
% Blow up in user fun but after emitting one row successfully.
- tx_too_long_errors({1, 1}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({1, 1}, 0),
?assertEqual(DocRows, changes(Db)),
% Blow up before last document
- tx_too_long_errors({?DOC_COUNT - 1, 1}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({?DOC_COUNT - 1, 1}, 0),
?assertEqual(DocRows, changes(Db)),
% Emit one value, then blow up in user function and then blow up twice in
% fold_range. But it is not enough to stop the iteration.
- tx_too_long_errors({1, 1}, {1, 2}),
+ fabric2_tutil:tx_too_old_setup_errors({1, 1}, {1, 2}),
?assertEqual(DocRows, changes(Db)).
-fold_changes_reverse_tx_too_long({Db, DocRows}) ->
+fold_changes_reverse_tx_too_old({Db, DocRows}) ->
Opts = [{dir, rev}],
- tx_too_long_errors(0, 1),
+ fabric2_tutil:tx_too_old_setup_errors(0, 1),
?assertEqual([], changes(Db, 0, Opts)),
- tx_too_long_errors(1, 0),
+ fabric2_tutil:tx_too_old_setup_errors(1, 0),
?assertEqual([], changes(Db, 0, Opts)),
- tx_too_long_errors(1, 0),
+ fabric2_tutil:tx_too_old_setup_errors(1, 0),
?assertEqual(DocRows, changes(Db, now, Opts)),
- tx_too_long_errors(1, 0),
+ fabric2_tutil:tx_too_old_setup_errors(1, 0),
?assertEqual(DocRows, changes(Db, now, Opts)),
% Blow up in user fun but after emitting one row successfully.
- tx_too_long_errors({1, 1}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({1, 1}, 0),
?assertEqual(DocRows, changes(Db, now, Opts)),
% Blow up before last document
- tx_too_long_errors({?DOC_COUNT - 1, 1}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({?DOC_COUNT - 1, 1}, 0),
?assertEqual(DocRows, changes(Db, now, Opts)),
% Emit value, blow up in user function, and twice in fold_range
- tx_too_long_errors({1, 1}, {1, 2}),
+ fabric2_tutil:tx_too_old_setup_errors({1, 1}, {1, 2}),
?assertEqual(DocRows, changes(Db, now, Opts)).
-fold_changes_tx_too_long_with_single_row_emits({Db, DocRows0}) ->
+fold_changes_tx_too_old_with_single_row_emits({Db, DocRows0}) ->
% This test does a few basic operations while forcing erlfdb range fold to
% emit a single row at a time, thus forcing it to use continuations while
% also inducing tx errors
Opts = [{target_bytes, 1}],
DocRows = lists:reverse(DocRows0),
- tx_too_long_errors(0, 1),
+ fabric2_tutil:tx_too_old_setup_errors(0, 1),
?assertEqual(DocRows, changes(Db, 0, Opts)),
- tx_too_long_errors(1, 0),
+ fabric2_tutil:tx_too_old_setup_errors(1, 0),
?assertEqual(DocRows, changes(Db, 0, Opts)),
% Blow up in user fun but after emitting one row successfully.
- tx_too_long_errors({1, 1}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({1, 1}, 0),
?assertEqual(DocRows, changes(Db, 0, Opts)),
% Blow up before last document
- tx_too_long_errors({?DOC_COUNT - 1, 1}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({?DOC_COUNT - 1, 1}, 0),
?assertEqual(DocRows, changes(Db, 0, Opts)).
-fold_changes_since_seq_tx_too_long({Db, Rows}) ->
+fold_changes_since_seq_tx_too_old({Db, Rows}) ->
% Blow up after after a successful emit, then twice
% in range fold call. Also re-use already existing basic
% fold_changes_since_seq test function.
- tx_too_long_errors({1, 1}, {1, 2}),
+ fabric2_tutil:tx_too_old_setup_errors({1, 1}, {1, 2}),
fold_changes_since_seq({Db, Rows}).
fold_changes_not_progressing({Db, _}) ->
% Fail in first fold range call.
- tx_too_long_errors(5, 0),
+ fabric2_tutil:tx_too_old_setup_errors(5, 0),
?assertError(fold_range_not_progressing, changes(Db)),
% Fail in first user fun call.
- tx_too_long_errors(0, 5),
+ fabric2_tutil:tx_too_old_setup_errors(0, 5),
?assertError(fold_range_not_progressing, changes(Db)),
% Blow up in last user fun call
- tx_too_long_errors({?DOC_COUNT - 1, 5}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({?DOC_COUNT - 1, 5}, 0),
?assertError(fold_range_not_progressing, changes(Db)),
% Blow up in user function after one success.
- tx_too_long_errors({1, 5}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({1, 5}, 0),
?assertError(fold_range_not_progressing, changes(Db)),
% Emit value, blow up in user function, then keep blowing up in fold_range.
- tx_too_long_errors({1, 1}, {1, 4}),
+ fabric2_tutil:tx_too_old_setup_errors({1, 1}, {1, 4}),
?assertError(fold_range_not_progressing, changes(Db)).
fold_fun(#{} = Change, Acc) ->
- maybe_tx_too_long(?PDICT_ERROR_IN_USER_FUN),
+ fabric2_tutil:tx_too_old_raise_in_user_fun(),
{ok, [Change | Acc]}.
-tx_too_long_errors(UserFunCount, FoldErrors) when is_integer(UserFunCount) ->
- tx_too_long_errors({0, UserFunCount}, FoldErrors);
-
-tx_too_long_errors(UserFunErrors, FoldCount) when is_integer(FoldCount) ->
- tx_too_long_errors(UserFunErrors, {0, FoldCount});
-
-tx_too_long_errors({UserFunSkip, UserFunCount}, {FoldSkip, FoldCount}) ->
- reset_error_counts(),
- put(?PDICT_ERROR_IN_USER_FUN, {UserFunSkip, UserFunCount}),
- put(?PDICT_ERROR_IN_FOLD_RANGE, {FoldSkip, FoldCount}).
-
-
-reset_error_counts() ->
- erase(?PDICT_ERROR_IN_FOLD_RANGE),
- erase(?PDICT_ERROR_IN_USER_FUN).
-
-
changes(Db) ->
changes(Db, 0, []).
@@ -262,17 +239,3 @@ changes(Db) ->
changes(Db, Since, Opts) ->
{ok, Rows} = fabric2_db:fold_changes(Db, Since, fun fold_fun/2, [], Opts),
Rows.
-
-
-maybe_tx_too_long(Key) ->
- case get(Key) of
- {Skip, Count} when is_integer(Skip), Skip > 0 ->
- put(Key, {Skip - 1, Count});
- {0, Count} when is_integer(Count), Count > 0 ->
- put(Key, {0, Count - 1}),
- error({erlfdb_error, 1007});
- {0, 0} ->
- ok;
- undefined ->
- ok
- end.
diff --git a/src/fabric/test/fabric2_db_crud_tests.erl b/src/fabric/test/fabric2_db_crud_tests.erl
index 6323a22..d6ede3a 100644
--- a/src/fabric/test/fabric2_db_crud_tests.erl
+++ b/src/fabric/test/fabric2_db_crud_tests.erl
@@ -18,10 +18,6 @@
-include("fabric2_test.hrl").
--define(PDICT_ERROR_IN_FOLD_RANGE, '$fabric2_error_in_fold_range').
--define(PDICT_ERROR_IN_USER_FUN, '$fabric2_error_throw_in_user_fun').
-
-
crud_test_() ->
{
"Test database CRUD operations",
@@ -42,8 +38,8 @@ crud_test_() ->
?TDEF_FE(list_dbs_user_fun_partial),
?TDEF_FE(list_dbs_info),
?TDEF_FE(list_dbs_info_partial),
- ?TDEF_FE(list_dbs_tx_too_long),
- ?TDEF_FE(list_dbs_info_tx_too_long)
+ ?TDEF_FE(list_dbs_tx_too_old),
+ ?TDEF_FE(list_dbs_info_tx_too_old)
]
}
}
@@ -62,15 +58,11 @@ teardown_all(Ctx) ->
setup() ->
- meck:expect(erlfdb, fold_range, fun(Tx, Start, End, Callback, Acc, Opts) ->
- maybe_tx_too_long(?PDICT_ERROR_IN_FOLD_RANGE),
- meck:passthrough([Tx, Start, End, Callback, Acc, Opts])
- end),
- ok.
+ fabric2_tutil:tx_too_old_mock_erlfdb().
cleanup(_) ->
- reset_error_counts().
+ fabric2_tutil:tx_too_old_reset_errors().
create_db(_) ->
@@ -166,14 +158,14 @@ list_dbs_info_partial(_) ->
?assertEqual([{meta, []}], UserAcc).
-list_dbs_tx_too_long(_) ->
+list_dbs_tx_too_old(_) ->
DbName1 = ?tempdb(),
DbName2 = ?tempdb(),
?assertMatch({ok, _}, fabric2_db:create(DbName1, [])),
?assertMatch({ok, _}, fabric2_db:create(DbName2, [])),
UserFun = fun(Row, Acc) ->
- maybe_tx_too_long(?PDICT_ERROR_IN_USER_FUN),
+ fabric2_tutil:tx_too_old_raise_in_user_fun(),
{ok, [Row, Acc]}
end,
@@ -181,64 +173,93 @@ list_dbs_tx_too_long(_) ->
Dbs = fabric2_db:list_dbs(UserFun, [], []),
% Blow up in fold range
- tx_too_long_errors(0, 1),
+ fabric2_tutil:tx_too_old_setup_errors(0, 1),
?assertEqual(Dbs, fabric2_db:list_dbs(UserFun, [], [])),
% Blow up in fold_range after emitting one row
- tx_too_long_errors(0, {1, 1}),
+ fabric2_tutil:tx_too_old_setup_errors(0, {1, 1}),
?assertEqual(Dbs, fabric2_db:list_dbs(UserFun, [], [])),
% Blow up in user fun
- tx_too_long_errors(1, 0),
+ fabric2_tutil:tx_too_old_setup_errors(2, 2),
?assertEqual(Dbs, fabric2_db:list_dbs(UserFun, [], [])),
% Blow up in user fun after emitting one row
- tx_too_long_errors({1, 1}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({1, 1}, 0),
?assertEqual(Dbs, fabric2_db:list_dbs(UserFun, [], [])),
% Blow up in in user fun and fold range
- tx_too_long_errors(1, {1, 1}),
+ fabric2_tutil:tx_too_old_setup_errors(1, {1, 1}),
?assertEqual(Dbs, fabric2_db:list_dbs(UserFun, [], [])),
ok = fabric2_db:delete(DbName1, []),
ok = fabric2_db:delete(DbName2, []).
-list_dbs_info_tx_too_long(_) ->
- DbName1 = ?tempdb(),
- DbName2 = ?tempdb(),
- ?assertMatch({ok, _}, fabric2_db:create(DbName1, [])),
- ?assertMatch({ok, _}, fabric2_db:create(DbName2, [])),
+list_dbs_info_tx_too_old(_) ->
+ % list_dbs_info uses a queue of 100 futures to fetch db infos in parallel
+ % so create more than 100 dbs so make sure we have 100+ dbs in our test
+
+ DbCount = 101,
+ DbNames = fabric2_tutil:pmap(fun(_) ->
+ DbName = ?tempdb(),
+ {ok, _} = fabric2_db:create(DbName, []),
+ DbName
+ end, lists:seq(1, DbCount)),
+
+ % Sanity check the pmap results
+ ?assertEqual(DbCount, length(DbNames)),
+ ?assert(lists:all(fun(DbName) -> is_binary(DbName) end, DbNames)),
UserFun = fun(Row, Acc) ->
- maybe_tx_too_long(?PDICT_ERROR_IN_USER_FUN),
+ fabric2_tutil:tx_too_old_raise_in_user_fun(),
{ok, [Row, Acc]}
end,
+ % This is the expected return with no tx timeouts
{ok, DbInfos} = fabric2_db:list_dbs_info(UserFun, [], []),
- % Blow up in fold range
- tx_too_long_errors(0, 1),
+ % Blow up in fold range on the first call
+ fabric2_tutil:tx_too_old_setup_errors(0, 1),
?assertEqual({ok, DbInfos}, fabric2_db:list_dbs_info(UserFun, [], [])),
% Blow up in fold_range after emitting one row
- tx_too_long_errors(0, {1, 1}),
+ fabric2_tutil:tx_too_old_setup_errors(0, {1, 1}),
+ ?assertEqual({ok, DbInfos}, fabric2_db:list_dbs_info(UserFun, [], [])),
+
+ % Blow up in fold_range after emitting 99 rows
+ fabric2_tutil:tx_too_old_setup_errors(0, {DbCount - 2, 1}),
+ ?assertEqual({ok, DbInfos}, fabric2_db:list_dbs_info(UserFun, [], [])),
+
+ % Blow up in fold_range after emitting 100 rows
+ fabric2_tutil:tx_too_old_setup_errors(0, {DbCount - 1, 1}),
?assertEqual({ok, DbInfos}, fabric2_db:list_dbs_info(UserFun, [], [])),
% Blow up in user fun
- tx_too_long_errors(1, 0),
+ fabric2_tutil:tx_too_old_setup_errors(1, 0),
?assertEqual({ok, DbInfos}, fabric2_db:list_dbs_info(UserFun, [], [])),
% Blow up in user fun after emitting one row
- tx_too_long_errors({1, 1}, 0),
+ fabric2_tutil:tx_too_old_setup_errors({1, 1}, 0),
+ ?assertEqual({ok, DbInfos}, fabric2_db:list_dbs_info(UserFun, [], [])),
+
+ % Blow up in user fun after emitting 99 rows
+ fabric2_tutil:tx_too_old_setup_errors({DbCount - 2, 1}, 0),
+ ?assertEqual({ok, DbInfos}, fabric2_db:list_dbs_info(UserFun, [], [])),
+
+ % Blow up in user fun after emitting 100 rows
+ fabric2_tutil:tx_too_old_setup_errors({DbCount - 1, 1}, 0),
?assertEqual({ok, DbInfos}, fabric2_db:list_dbs_info(UserFun, [], [])),
% Blow up in in user fun and fold range
- tx_too_long_errors(1, {1, 1}),
+ fabric2_tutil:tx_too_old_setup_errors(1, {1, 1}),
?assertEqual({ok, DbInfos}, fabric2_db:list_dbs_info(UserFun, [], [])),
- ok = fabric2_db:delete(DbName1, []),
- ok = fabric2_db:delete(DbName2, []).
+ DeleteResults = fabric2_tutil:pmap(fun(DbName) ->
+ fabric2_db:delete(DbName, [])
+ end, DbNames),
+
+ ?assert(lists:all(fun(Res) -> Res =:= ok end, DeleteResults)).
is_db_info_member(_, []) ->
@@ -251,34 +272,3 @@ is_db_info_member(DbName, [DbInfo | RestInfos]) ->
_E ->
is_db_info_member(DbName, RestInfos)
end.
-
-
-tx_too_long_errors(UserFunCount, FoldErrors) when is_integer(UserFunCount) ->
- tx_too_long_errors({0, UserFunCount}, FoldErrors);
-
-tx_too_long_errors(UserFunErrors, FoldCount) when is_integer(FoldCount) ->
- tx_too_long_errors(UserFunErrors, {0, FoldCount});
-
-tx_too_long_errors({UserFunSkip, UserFunCount}, {FoldSkip, FoldCount}) ->
- reset_error_counts(),
- put(?PDICT_ERROR_IN_USER_FUN, {UserFunSkip, UserFunCount}),
- put(?PDICT_ERROR_IN_FOLD_RANGE, {FoldSkip, FoldCount}).
-
-
-reset_error_counts() ->
- erase(?PDICT_ERROR_IN_FOLD_RANGE),
- erase(?PDICT_ERROR_IN_USER_FUN).
-
-
-maybe_tx_too_long(Key) ->
- case get(Key) of
- {Skip, Count} when is_integer(Skip), Skip > 0 ->
- put(Key, {Skip - 1, Count});
- {0, Count} when is_integer(Count), Count > 0 ->
- put(Key, {0, Count - 1}),
- error({erlfdb_error, 1007});
- {0, 0} ->
- ok;
- undefined ->
- ok
- end.
diff --git a/src/fabric/test/fabric2_tutil.erl b/src/fabric/test/fabric2_tutil.erl
new file mode 100644
index 0000000..5b817ba
--- /dev/null
+++ b/src/fabric/test/fabric2_tutil.erl
@@ -0,0 +1,99 @@
+% 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
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(fabric2_tutil).
+
+
+-export([
+ pmap/2,
+ pmap/3,
+
+ tx_too_old_mock_erlfdb/0,
+ tx_too_old_setup_errors/2,
+ tx_too_old_reset_errors/0,
+ tx_too_old_raise_in_user_fun/0
+]).
+
+
+-define(PDICT_ERROR_IN_FOLD_RANGE, '$fabric2_error_in_fold_range').
+-define(PDICT_ERROR_IN_USER_FUN, '$fabric2_error_throw_in_user_fun').
+
+
+pmap(Fun, Args) ->
+ pmap(Fun, Args, []).
+
+
+pmap(Fun, Args, Opts) ->
+ Refs = lists:map(fun(Arg) ->
+ {_, Ref} = spawn_monitor(fun() -> exit({'$pmap_res', Fun(Arg)}) end),
+ Ref
+ end, Args),
+ Timeout = fabric2_util:get_value(timeout, Opts, 5000),
+ lists:map(fun(Ref) ->
+ receive
+ {'DOWN', Ref, _, _, {'$pmap_res', Res}} -> Res;
+ {'DOWN', Ref, _, _, Error} -> {pmap_error, Error}
+ after Timeout ->
+ {pmap_error, timeout}
+ end
+ end, Refs).
+
+
+% Set of function to test scenarios where the FDB throws transaction_too_long
+% (1007) errors. The general pattern is to call tx_too_old_mock_erlfdb() in
+% setup. Then, before tests call tx_too_old_setup_errors(UserErrs, FoldErrs)
+% which will set how and when the error will be thrown.
+
+tx_too_old_mock_erlfdb() ->
+ meck:expect(erlfdb, fold_range, fun(Tx, Start, End, Callback, Acc, Opts) ->
+ MockFun = fun(Row, InnerAcc) ->
+ maybe_tx_too_old(?PDICT_ERROR_IN_FOLD_RANGE),
+ Callback(Row, InnerAcc)
+ end,
+ meck:passthrough([Tx, Start, End, MockFun, Acc, Opts])
+ end).
+
+
+tx_too_old_setup_errors(UserCnt, FoldErrs) when is_integer(UserCnt) ->
+ tx_too_old_setup_errors({0, UserCnt}, FoldErrs);
+
+tx_too_old_setup_errors(UserErrs, FoldCnt) when is_integer(FoldCnt) ->
+ tx_too_old_setup_errors(UserErrs, {0, FoldCnt});
+
+tx_too_old_setup_errors({UserSkip, UserCnt}, {FoldSkip, FoldCnt}) ->
+ put(?PDICT_ERROR_IN_USER_FUN, {UserSkip, UserCnt}),
+ put(?PDICT_ERROR_IN_FOLD_RANGE, {FoldSkip, FoldCnt}).
+
+
+tx_too_old_reset_errors() ->
+ erase(?PDICT_ERROR_IN_FOLD_RANGE),
+ erase(?PDICT_ERROR_IN_USER_FUN).
+
+
+tx_too_old_raise_in_user_fun() ->
+ maybe_tx_too_old(?PDICT_ERROR_IN_USER_FUN).
+
+
+% Private functions
+
+maybe_tx_too_old(Key) ->
+ case get(Key) of
+ {Skip, Count} when is_integer(Skip), Skip > 0 ->
+ put(Key, {Skip - 1, Count});
+ {0, Count} when is_integer(Count), Count > 0 ->
+ put(Key, {0, Count - 1}),
+ error({erlfdb_error, 1007});
+ {0, 0} ->
+ ok;
+ undefined ->
+ ok
+ end.