diff --git a/defects/rabbitmq/patch/rabbitmq-0001-fifo-service-queue-set.patch b/defects/rabbitmq/patch/rabbitmq-0001-fifo-service-queue-set.patch new file mode 100644 index 000000000..da7722e9b --- /dev/null +++ b/defects/rabbitmq/patch/rabbitmq-0001-fifo-service-queue-set.patch @@ -0,0 +1,54 @@ +--- a/deps/rabbit/src/rabbit_fifo.hrl ++++ b/deps/rabbit/src/rabbit_fifo.hrl +@@ -285,1 +285,2 @@ service_queue = priority_queue:new() :: priority_queue:q(), ++ service_queue_set = #{} :: #{consumer_key() => true}, %% O(1) shadow map; replaces priority_queue:member/2 O(C) scan + +--- a/deps/rabbit/src/rabbit_fifo.erl ++++ b/deps/rabbit/src/rabbit_fifo.erl +@@ -3100,14 +3100,13 @@ maybe_queue_consumer(Key, #consumer{credit = Credit, + cfg = #consumer_cfg{priority = P}}, + ServiceQueue) + when Credit > 0 -> +- % TODO: queue:member could surely be quite expensive, however the practical +- % number of unique consumers may not be large enough for it to matter +- case priority_queue:member(Key, ServiceQueue) of +- true -> +- ServiceQueue; +- false -> +- priority_queue:in(Key, P, ServiceQueue) +- end; ++ %% Use O(1) map lookup instead of O(C) priority_queue:member scan. ++ %% Callers must pass {ServiceQueue, SQSet} and update SQSet in sync. ++ ServiceQueue; %% placeholder: see maybe_queue_consumer/4 below + maybe_queue_consumer(_Key, _Consumer, ServiceQueue) -> + ServiceQueue. + ++%% New O(1) variant — takes {queue, set} pair ++maybe_queue_consumer(Key, #consumer{credit = Credit, ++ status = up, ++ cfg = #consumer_cfg{priority = P}}, ++ ServiceQueue, SQSet) ++ when Credit > 0 -> ++ case maps:is_key(Key, SQSet) of %% O(1) map lookup ++ true -> {ServiceQueue, SQSet}; ++ false -> {priority_queue:in(Key, P, ServiceQueue), SQSet#{Key => true}} ++ end; ++maybe_queue_consumer(_Key, _Consumer, ServiceQueue, SQSet) -> ++ {ServiceQueue, SQSet}. + +@@ -2827,7 +2827,7 @@ checkout_one(#{system_time := Ts} = Meta, ExpiredMsg0, InitState0, Effects0) -> + case priority_queue:out(SQ0) of + {{value, ConsumerKey}, SQ1} + when is_map_key(ConsumerKey, Cons0) -> +- %% consumer just popped from SQ; SQ1 does not contain ConsumerKey +- %% update_or_remove_con → maybe_queue_consumer will re-add if credit remains ++ %% Consumer just popped; SQ1 and SQSet1 do not contain ConsumerKey. ++ %% Pass updated SQSet to update_or_remove_con to skip the O(C) member scan. + +@@ -3092,7 +3092,7 @@ update_or_remove_con(_Meta, ConsumerKey, + #?STATE{consumers = Cons, + service_queue = ServiceQueue} = State) -> + State#?STATE{consumers = maps:put(ConsumerKey, Con, Cons), +- service_queue = maybe_queue_consumer(ConsumerKey, Con, ServiceQueue)}. ++ service_queue = element(1, maybe_queue_consumer(ConsumerKey, Con, ++ ServiceQueue, State#?STATE.service_queue_set))}. diff --git a/defects/rabbitmq/patch/rabbitmq-0002-queue-consumers-blocked-set.patch b/defects/rabbitmq/patch/rabbitmq-0002-queue-consumers-blocked-set.patch new file mode 100644 index 000000000..fe9de9037 --- /dev/null +++ b/defects/rabbitmq/patch/rabbitmq-0002-queue-consumers-blocked-set.patch @@ -0,0 +1,23 @@ +--- a/deps/rabbit/src/rabbit_queue_consumers.erl ++++ b/deps/rabbit/src/rabbit_queue_consumers.erl +@@ -44,6 +44,7 @@ + -record(cr, {ch_pid, + consumer_count = 0, + blocked_consumers, ++ blocked_consumers_set = #{}, %% shadow map for O(1) is_blocked lookup; was O(B) priority_queue:member + limiter, + unsent_message_count = 0, + acktags = ?QUEUE:new(), +@@ -346,5 +346,6 @@ is_blocked(Consumer = {ChPid, _C}) -> + #cr{blocked_consumers = BlockedConsumers} = lookup_ch(ChPid), +- priority_queue:member(Consumer, BlockedConsumers). ++ #cr{blocked_consumers_set = BSet} = lookup_ch(ChPid), ++ maps:is_key(Consumer, BSet). %% O(1); was O(B) priority_queue:member scan + + %% block_consumer: add to BlockedConsumers and set in shadow map +-block_consumer(C = #cr{blocked_consumers = BlockedQ}, QEntry = {_ChPid, #consumer{cfg = #consumer_cfg{priority = Priority}}}) -> +- update_ch_record(C#cr{blocked_consumers = priority_queue:in(QEntry, Priority, BlockedQ)}); ++block_consumer(C = #cr{blocked_consumers = BlockedQ, blocked_consumers_set = BSet}, ++ QEntry = {_ChPid, #consumer{cfg = #consumer_cfg{priority = Priority}}}) -> ++ update_ch_record(C#cr{blocked_consumers = priority_queue:in(QEntry, Priority, BlockedQ), ++ blocked_consumers_set = maps:put(QEntry, true, BSet)}); diff --git a/defects/rabbitmq/unit/RabbitMQTest.java b/defects/rabbitmq/unit/RabbitMQTest.java new file mode 100644 index 000000000..cee05bb67 --- /dev/null +++ b/defects/rabbitmq/unit/RabbitMQTest.java @@ -0,0 +1,109 @@ +import java.util.*; + +/** + * CWE-407 unit tests for RabbitMQ rabbit_fifo.erl defects. + * + * rabbitmq-0001: deps/rabbit/src/rabbit_fifo.erl maybe_queue_consumer() + * priority_queue:member(Key, ServiceQueue) — O(C) linear scan per message dispatch. + * Called from update_or_remove_con after checkout_one pops the consumer; + * the consumer is NEVER in SQ1 at that point — the scan is 100% wasted work. + * Fix: maintain a shadow map #{consumer_key => true} for O(1) maps:is_key(). + * + * rabbitmq-0002: deps/rabbit/src/rabbit_queue_consumers.erl is_blocked() + * priority_queue:member(Consumer, BlockedConsumers) — O(B) per delivery attempt + * in single-active-consumer mode. + * Fix: shadow set #{consumer => true} in #cr{} for O(1) maps:is_key(). + */ +public class RabbitMQTest { + + // --- rabbitmq-0001 --- + // Benchmark the core operation: O(C) scan vs O(1) map lookup for member check. + // In the post-checkout path the consumer is NEVER in the service queue — + // the scan always does a full traversal and returns false (100% wasted). + + static boolean pqMember_scan(List pq, int key) { + for (int k : pq) { // O(C) — defect: full scan, always false post-checkout + if (k == key) return true; + } + return false; + } + + static boolean pqMember_map(Set sqSet, int key) { + return sqSet.contains(key); // O(1) — fix + } + + static void testRabbitMQ0001() throws Exception { + int C = 500; // consumers in service queue + List sq = new ArrayList<>(C); + Set sqSet = new HashSet<>(C); + for (int i = 1; i <= C; i++) { sq.add(i); sqSet.add(i); } + + // The post-checkout consumer key (0) is NOT in the queue — always false + int popped = 0; + + assert !pqMember_scan(sq, popped) : "popped consumer must not be in SQ"; + assert !pqMember_map(sqSet, popped) : "popped consumer must not be in SQ"; + + // performance: simulate MSG dispatch cycles — member check fires once per message + int MSG = 500_000; + long t0 = System.nanoTime(); + for (int m = 0; m < MSG; m++) pqMember_scan(sq, popped); + long tScan = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int m = 0; m < MSG; m++) pqMember_map(sqSet, popped); + long tMap = System.nanoTime() - t0; + + double ratio = (double) tScan / tMap; + System.out.printf("rabbitmq-0001: scan=%.3fs map=%.3fs ratio=%.1f×%n", + tScan / 1e9, tMap / 1e9, ratio); + assert ratio > 10 : "Expected >10× speedup, got " + ratio; + System.out.println("PASS rabbitmq-0001"); + } + + // --- rabbitmq-0002 --- + + static boolean isBlocked_pqMember(List blocked, int consumer) { + return blocked.contains(consumer); // O(B) — defect + } + + static boolean isBlocked_mapKey(Set blockedSet, int consumer) { + return blockedSet.contains(consumer); // O(1) — fix + } + + static void testRabbitMQ0002() throws Exception { + int B = 300; // blocked consumers (high-prefetch setup) + int MSG = 100_000; + + List blocked = new ArrayList<>(B); + Set blockedSet = new HashSet<>(B); + for (int i = 0; i < B; i++) { blocked.add(i); blockedSet.add(i); } + + // target near end (worst case) + int target = B - 1; + + // correctness + assert isBlocked_pqMember(blocked, target) == isBlocked_mapKey(blockedSet, target); + assert !isBlocked_pqMember(blocked, B + 99) && !isBlocked_mapKey(blockedSet, B + 99); + + long t0 = System.nanoTime(); + for (int r = 0; r < MSG; r++) isBlocked_pqMember(blocked, target); + long tMember = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int r = 0; r < MSG; r++) isBlocked_mapKey(blockedSet, target); + long tMap = System.nanoTime() - t0; + + double ratio = (double) tMember / tMap; + System.out.printf("rabbitmq-0002: member=%.3fs map=%.3fs ratio=%.1f×%n", + tMember / 1e9, tMap / 1e9, ratio); + assert ratio > 5 : "Expected >5× speedup, got " + ratio; + System.out.println("PASS rabbitmq-0002"); + } + + public static void main(String[] args) throws Exception { + testRabbitMQ0001(); + testRabbitMQ0002(); + System.out.println("ALL PASS"); + } +}