Skip to content

Commit fc86f4c

Browse files
committed
router: forbid using cdata as bucket_id
Using cdata (e.g. 1ULL) as bucket id was allowed in all requests of the router. But using it caused the growth of the `route_map` table on every request and full scan of the cluster in order to find, where the bucket is. This happened due to the fact, that cdata is compared by its pointer, not the value, and consequently, as there was no such pointer key in the `route_map`, it was added and scanned every time. It was decided to prohibit such usage, from now on we expect users to do `tonumber(bucket_id)`, if it's needed. The motivation for such solution is the fact, that all functions except calls already prohibit using cdata as bucket_id, and calls should be consistent with it. Another point is the fact, that the behavior of the `map_callrw` with cdata as bucket ids is very untrivial. The alternative solution of allowing usage of cdata in calls and manually converting values to number in `bucket_set`, `bucket_reset` and `bucket_resolved` was rejected due to the above mentioned reasons. Unfortunately, the behavior of `map_callrw` with array of cdata is still untrivial, as {10ULL, 20ULL} is interpreted as {1 = 10, 2 = 20}, and the request is executed on the replicaset with 1st and 2nd bucket. Closes #594 NO_DOC=bugfix
1 parent f72bb21 commit fc86f4c

File tree

3 files changed

+85
-2
lines changed

3 files changed

+85
-2
lines changed

test/router-luatest/map_callrw_test.lua

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,3 +647,41 @@ g.test_map_callrw_array_encoded_as_map = function(cg)
647647
end
648648
end, nil, bids)
649649
end
650+
651+
--
652+
-- gh-594: using cdata as bucket_id should be prohibited, as it causes fullscan
653+
-- of the cluster and subsequent memory leak in the route_map.
654+
--
655+
g.test_map_callrw_with_cdata_bucket_id = function(cg)
656+
cg.router:exec(function()
657+
-- vshard.router._buckets_group is checked for the sake of unit
658+
-- testing of the map_callrw() protection.
659+
local msg = 'is not a number'
660+
local _, err = ivshard.router._buckets_group({1ULL, 2ULL}, 1)
661+
ilt.assert_str_contains(err.message, msg)
662+
_, err = ivshard.router._buckets_group({1, 2ULL}, 1)
663+
ilt.assert_str_contains(err.message, msg)
664+
_, err = ivshard.router._buckets_group({1ULL, 2}, 1)
665+
ilt.assert_str_contains(err.message, msg)
666+
667+
-- Map_callrw in general forbids using cdata.
668+
_, err = ivshard.router.map_callrw('assert', {'a'},
669+
{bucket_ids = {1, 2ULL}})
670+
ilt.assert_str_contains(err.message, msg)
671+
_, err = ivshard.router.map_callrw('assert', {'a'},
672+
{bucket_ids = {[1] = 'a', [2ULL] = 'b'}})
673+
ilt.assert_str_contains(err.message, msg)
674+
_, err = ivshard.router.map_callrw('assert', {'a'},
675+
{bucket_ids = {[1ULL] = 'a', [2ULL] = 'b'}})
676+
ilt.assert_str_contains(err.message, msg)
677+
678+
local res
679+
-- But there's one case, where it's not possible to forbid that,
680+
-- since it's interpreted as {1 = 5ULL, 2 = 6ULL}. If there's a way,
681+
-- which will allow to forbid that, it'd be great.
682+
res, err = ivshard.router.map_callrw('assert', {'a'},
683+
{bucket_ids = {5ULL, 6ULL}})
684+
ilt.assert(res)
685+
ilt.assert_not(err)
686+
end)
687+
end

test/router-luatest/router_2_2_test.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,3 +1165,39 @@ g.test_discovery_can_be_restarted_fast = function(g)
11651165
t.assert_not(g.router:grep_log('assertion failed'))
11661166
vtest.router_cfg(g.router, global_cfg)
11671167
end
1168+
1169+
--
1170+
-- gh-594: using cdata as bucket_id should be prohibited, as it causes fullscan
1171+
-- of the cluster and subsequent memory leak in the route_map.
1172+
--
1173+
g.test_cdata_as_bucket_id_is_prohibited = function(g)
1174+
g.router:exec(function()
1175+
local msg = 'Usage: '
1176+
ilt.assert_error_msg_contains(msg, function()
1177+
ivshard.router.buckets_info(1ULL, 2ULL)
1178+
end)
1179+
ilt.assert_error_msg_contains(msg, function()
1180+
ivshard.router.call(1ULL, 'read', 'assert', {'a'})
1181+
end)
1182+
ilt.assert_error_msg_contains(msg, function()
1183+
ivshard.router.callro(1ULL, 'assert', {'a'})
1184+
end)
1185+
ilt.assert_error_msg_contains(msg, function()
1186+
ivshard.router.callbro(1ULL, 'assert', {'a'})
1187+
end)
1188+
ilt.assert_error_msg_contains(msg, function()
1189+
ivshard.router.callrw(1ULL, 'assert', {'a'})
1190+
end)
1191+
ilt.assert_error_msg_contains(msg, function()
1192+
ivshard.router.callre(1ULL, 'assert', {'a'})
1193+
end)
1194+
ilt.assert_error_msg_contains(msg, function()
1195+
ivshard.router.callbre(1ULL, 'assert', {'a'})
1196+
end)
1197+
ilt.assert_error_msg_contains(msg, function()
1198+
ivshard.router.route(1ULL)
1199+
end)
1200+
-- vshard.router._bucket_reset() is not public and doesn't require
1201+
-- protection from the misusage.
1202+
end)
1203+
end

vshard/router/init.lua

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ local function buckets_group(router, bucket_ids, timeout)
206206
if bucket_map[bucket_id] then
207207
goto continue
208208
end
209+
if type(bucket_id) ~= 'number' then
210+
local msg = string.format("Bucket '%s' is not a number", bucket_id)
211+
return nil, lerror.make(msg)
212+
end
209213
-- Avoid duplicates.
210214
bucket_map[bucket_id] = true
211215
if fiber_clock() > deadline then
@@ -609,6 +613,9 @@ local function router_call_impl(router, bucket_id, mode, prefer_replica,
609613
end
610614
local replicaset, err
611615
local tend = fiber_clock() + timeout
616+
if type(bucket_id) ~= 'number' then
617+
error('Usage: call(bucket_id, mode, func, args, opts)')
618+
end
612619
if bucket_id > router.total_bucket_count or bucket_id <= 0 then
613620
error('Bucket is unreachable: bucket id is out of range')
614621
end
@@ -1140,8 +1147,10 @@ local function router_map_callrw(router, func, args, opts)
11401147
timeout, err, err_id, rid, replicasets_to_map =
11411148
router_ref_storage_by_buckets(router, plain_bucket_ids, timeout)
11421149
-- Grouped arguments are only possible with partial Map-Reduce.
1143-
grouped_args =
1144-
router_group_map_callrw_args(router, plain_bucket_ids, bucket_ids)
1150+
if timeout then
1151+
grouped_args = router_group_map_callrw_args(
1152+
router, plain_bucket_ids, bucket_ids)
1153+
end
11451154
else
11461155
timeout, err, err_id, rid, replicasets_to_map =
11471156
router_ref_storage_all(router, timeout)

0 commit comments

Comments
 (0)