summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2020-03-13 18:28:51 -0400
committerNick Vatamaniuc <nickva@users.noreply.github.com>2020-03-16 10:48:34 -0400
commit0cd9c26b201ff626f794058b94ed06e496f5baa3 (patch)
tree1d5e968a0fe0b7b03369b88cd6e31f93b56f96eb
parent0db143a7a86927cd503baf14e8d56ac922590b46 (diff)
downloadcouchdb-0cd9c26b201ff626f794058b94ed06e496f5baa3.tar.gz
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.
-rw-r--r--src/fabric/src/fabric2_util.erl49
-rw-r--r--src/fabric/test/fabric2_changes_fold_tests.erl103
-rw-r--r--src/fabric/test/fabric2_db_crud_tests.erl112
-rw-r--r--src/fabric/test/fabric2_test_util.erl76
4 files changed, 205 insertions, 135 deletions
diff --git a/src/fabric/src/fabric2_util.erl b/src/fabric/src/fabric2_util.erl
index a4faf3987..46f9abeef 100644
--- a/src/fabric/src/fabric2_util.erl
+++ b/src/fabric/src/fabric2_util.erl
@@ -37,7 +37,10 @@
from_hex/1,
uuid/0,
- encode_all_doc_key/1
+ encode_all_doc_key/1,
+
+ pmap/2,
+ pmap/3
]).
@@ -298,3 +301,47 @@ encode_all_doc_key(N) when is_number(N) -> <<>>;
encode_all_doc_key(B) when is_binary(B) -> B;
encode_all_doc_key(L) when is_list(L) -> <<255>>;
encode_all_doc_key({O}) when is_list(O) -> <<255>>.
+
+
+pmap(Fun, Args) ->
+ pmap(Fun, Args, []).
+
+
+pmap(Fun, Args, Opts) ->
+ Refs = lists:map(fun(Arg) ->
+ {_, Ref} = spawn_monitor(fun() -> exit(pmap_exec(Fun, Arg)) end),
+ Ref
+ end, Args),
+ Timeout = fabric2_util:get_value(timeout, Opts, 5000),
+ lists:map(fun(Ref) ->
+ receive
+ {'DOWN', Ref, _, _, {'$res', Res}} ->
+ Res;
+ {'DOWN', Ref, _, _, {'$err', Tag, Reason, Stack}} ->
+ erlang:raise(Tag, Reason, Stack)
+ after Timeout ->
+ error({pmap_timeout, Timeout})
+ end
+ 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 ->
+ {'$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/fabric/test/fabric2_changes_fold_tests.erl b/src/fabric/test/fabric2_changes_fold_tests.erl
index fddf1802b..8541d973c 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_test_util: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_test_util: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_test_util:tx_too_old_setup_errors(0, 1),
?assertEqual(DocRows, changes(Db)),
- tx_too_long_errors(1, 0),
+ fabric2_test_util: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_test_util: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_test_util: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_test_util: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_test_util:tx_too_old_setup_errors(0, 1),
?assertEqual([], changes(Db, 0, Opts)),
- tx_too_long_errors(1, 0),
+ fabric2_test_util:tx_too_old_setup_errors(1, 0),
?assertEqual([], changes(Db, 0, Opts)),
- tx_too_long_errors(1, 0),
+ fabric2_test_util:tx_too_old_setup_errors(1, 0),
?assertEqual(DocRows, changes(Db, now, Opts)),
- tx_too_long_errors(1, 0),
+ fabric2_test_util: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_test_util: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_test_util: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_test_util: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_test_util:tx_too_old_setup_errors(0, 1),
?assertEqual(DocRows, changes(Db, 0, Opts)),
- tx_too_long_errors(1, 0),
+ fabric2_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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 6323a22bd..c0a65ebd8 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_test_util:tx_too_old_mock_erlfdb().
cleanup(_) ->
- reset_error_counts().
+ fabric2_test_util: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_test_util:tx_too_old_raise_in_user_fun(),
{ok, [Row, Acc]}
end,
@@ -181,64 +173,87 @@ list_dbs_tx_too_long(_) ->
Dbs = fabric2_db:list_dbs(UserFun, [], []),
% Blow up in fold range
- tx_too_long_errors(0, 1),
+ fabric2_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_util:pmap(fun(_) ->
+ DbName = ?tempdb(),
+ ?assertMatch({ok, _}, fabric2_db:create(DbName, [])),
+ DbName
+ end, lists:seq(1, DbCount)),
UserFun = fun(Row, Acc) ->
- maybe_tx_too_long(?PDICT_ERROR_IN_USER_FUN),
+ fabric2_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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_test_util: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, []).
+ fabric2_util:pmap(fun(DbName) ->
+ ?assertEqual(ok, fabric2_db:delete(DbName, []))
+ end, DbNames).
is_db_info_member(_, []) ->
@@ -251,34 +266,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_test_util.erl b/src/fabric/test/fabric2_test_util.erl
new file mode 100644
index 000000000..acbe252b1
--- /dev/null
+++ b/src/fabric/test/fabric2_test_util.erl
@@ -0,0 +1,76 @@
+% 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_test_util).
+
+
+-export([
+ 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').
+
+
+% 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.