Skip to content

Commit 3d94583

Browse files
committed
Add unit test on contended dbuffer not leaking
1 parent 2bb3b96 commit 3d94583

3 files changed

Lines changed: 168 additions & 0 deletions

File tree

Makefile.am

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,7 @@ if ENABLE_STATIC
13741374
test_apps += \
13751375
unittests/unittest_poller \
13761376
unittests/unittest_ypipe \
1377+
unittests/unittest_dbuffer \
13771378
unittests/unittest_mtrie \
13781379
unittests/unittest_ip_resolver \
13791380
unittests/unittest_udp_address \
@@ -1398,6 +1399,15 @@ unittests_unittest_ypipe_LDADD = \
13981399
${src_libzmq_la_LIBADD} \
13991400
$(CODE_COVERAGE_LDFLAGS)
14001401

1402+
unittests_unittest_dbuffer_SOURCES = unittests/unittest_dbuffer.cpp
1403+
unittests_unittest_dbuffer_CPPFLAGS = -I$(top_srcdir)/src ${TESTUTIL_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS)
1404+
unittests_unittest_dbuffer_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
1405+
unittests_unittest_dbuffer_LDADD = \
1406+
${TESTUTIL_LIBS} \
1407+
$(top_builddir)/src/.libs/libzmq.a \
1408+
${src_libzmq_la_LIBADD} \
1409+
$(CODE_COVERAGE_LDFLAGS)
1410+
14011411
unittests_unittest_mtrie_SOURCES = unittests/unittest_mtrie.cpp
14021412
unittests_unittest_mtrie_CPPFLAGS = -I$(top_srcdir)/src ${TESTUTIL_CPPFLAGS} $(CODE_COVERAGE_CPPFLAGS)
14031413
unittests_unittest_mtrie_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)

unittests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 2.8.1...3.31)
33

44
set(unittests
55
unittest_ypipe
6+
unittest_dbuffer
67
unittest_poller
78
unittest_mtrie
89
unittest_ip_resolver

unittests/unittest_dbuffer.cpp

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/* SPDX-License-Identifier: MPL-2.0 */
2+
3+
#include "../tests/testutil.hpp"
4+
5+
#include <atomic_counter.hpp>
6+
#include <dbuffer.hpp>
7+
#include <msg.hpp>
8+
#include <thread.hpp>
9+
10+
#include <unity.h>
11+
12+
void setUp ()
13+
{
14+
}
15+
void tearDown ()
16+
{
17+
}
18+
19+
// All test messages share one payload buffer.
20+
// Ownership is tracked per message using the hint field, which points at
21+
// a counter for how many times the content was released.
22+
static char shared_payload[129];
23+
24+
static void count_free (void *data_, void *hint_)
25+
{
26+
LIBZMQ_UNUSED (data_);
27+
++*static_cast<int *> (hint_);
28+
}
29+
30+
// Writes a message owning 'shared_payload' into the dbuffer and gives up
31+
// ownership, the way pipe writers do. 'freed_' is incremented when the
32+
// dbuffer releases the message content.
33+
static void write_counted_msg (zmq::dbuffer_t<zmq::msg_t> &buf_, int *freed_)
34+
{
35+
zmq::msg_t msg;
36+
TEST_ASSERT_EQUAL_INT (
37+
0, msg.init_data (shared_payload, sizeof shared_payload, count_free,
38+
freed_));
39+
buf_.write (msg);
40+
TEST_ASSERT_EQUAL_INT (0, msg.init ());
41+
}
42+
43+
void test_create ()
44+
{
45+
zmq::dbuffer_t<zmq::msg_t> buf;
46+
TEST_ASSERT_FALSE (buf.check_read ());
47+
}
48+
49+
void test_read_empty ()
50+
{
51+
zmq::dbuffer_t<zmq::msg_t> buf;
52+
zmq::msg_t msg;
53+
TEST_ASSERT_EQUAL_INT (0, msg.init ());
54+
TEST_ASSERT_FALSE (buf.read (&msg));
55+
TEST_ASSERT_EQUAL_INT (0, msg.close ());
56+
}
57+
58+
// Uncontended conflation.
59+
// Second write should replace the first message and release its content.
60+
// The reader must only observe the second message.
61+
void test_conflation_releases_overwritten_message ()
62+
{
63+
int freed_first = 0, freed_second = 0;
64+
65+
zmq::dbuffer_t<zmq::msg_t> buf;
66+
write_counted_msg (buf, &freed_first);
67+
write_counted_msg (buf, &freed_second);
68+
TEST_ASSERT_EQUAL_INT (1, freed_first);
69+
TEST_ASSERT_EQUAL_INT (0, freed_second);
70+
71+
zmq::msg_t msg;
72+
TEST_ASSERT_EQUAL_INT (0, msg.init ());
73+
TEST_ASSERT_TRUE (buf.read (&msg));
74+
TEST_ASSERT_EQUAL_INT (sizeof shared_payload, msg.size ());
75+
TEST_ASSERT_EQUAL_INT (0, msg.close ());
76+
TEST_ASSERT_EQUAL_INT (1, freed_second);
77+
78+
TEST_ASSERT_FALSE (buf.read (&msg));
79+
}
80+
81+
// Helpers for parking a reader inside probe (), holding the dbuffer lock.
82+
// This allows deterministically causing writes to fail try_lock.
83+
static zmq::atomic_counter_t probe_entered;
84+
static zmq::atomic_counter_t probe_release;
85+
86+
static bool blocking_probe_fn (const zmq::msg_t &)
87+
{
88+
probe_entered.add (1);
89+
while (probe_release.get () == 0)
90+
msleep (1);
91+
return true;
92+
}
93+
94+
static void probe_thread_fn (void *arg_)
95+
{
96+
static_cast<zmq::dbuffer_t<zmq::msg_t> *> (arg_)->probe (
97+
blocking_probe_fn);
98+
}
99+
100+
// A write to a dbuffer locked by a reader stores the msg in the _back slot.
101+
// Subsequent writes must release that message instead of overwriting.
102+
// Previously, this would leak one message per reader/writer collision
103+
// on sockets with ZMQ_CONFLATE set.
104+
void test_write_while_reader_busy_does_not_leak ()
105+
{
106+
int freed_first = 0, freed_second = 0, freed_third = 0;
107+
108+
{
109+
zmq::dbuffer_t<zmq::msg_t> buf;
110+
111+
probe_entered.set (0);
112+
probe_release.set (0);
113+
zmq::thread_t reader;
114+
reader.start (probe_thread_fn, &buf, "dbuffer_probe");
115+
while (probe_entered.get () == 0)
116+
msleep (1);
117+
118+
// The reader now sits inside probe () holding the lock.
119+
// Both writes fail try_lock, and the second overwrites _back.
120+
write_counted_msg (buf, &freed_first);
121+
write_counted_msg (buf, &freed_second);
122+
TEST_ASSERT_EQUAL_INT (1, freed_first);
123+
TEST_ASSERT_EQUAL_INT (0, freed_second);
124+
125+
probe_release.add (1);
126+
reader.stop ();
127+
128+
// With the reader gone this write swaps successfully.
129+
// It must still released the message stored by the failed swap.
130+
write_counted_msg (buf, &freed_third);
131+
TEST_ASSERT_EQUAL_INT (1, freed_second);
132+
133+
zmq::msg_t msg;
134+
TEST_ASSERT_EQUAL_INT (0, msg.init ());
135+
TEST_ASSERT_TRUE (buf.read (&msg));
136+
TEST_ASSERT_EQUAL_INT (0, msg.close ());
137+
TEST_ASSERT_EQUAL_INT (1, freed_third);
138+
}
139+
140+
// Destruction must not double-release anything.
141+
TEST_ASSERT_EQUAL_INT (1, freed_first);
142+
TEST_ASSERT_EQUAL_INT (1, freed_second);
143+
TEST_ASSERT_EQUAL_INT (1, freed_third);
144+
}
145+
146+
int main (void)
147+
{
148+
setup_test_environment ();
149+
150+
UNITY_BEGIN ();
151+
RUN_TEST (test_create);
152+
RUN_TEST (test_read_empty);
153+
RUN_TEST (test_conflation_releases_overwritten_message);
154+
RUN_TEST (test_write_while_reader_busy_does_not_leak);
155+
156+
return UNITY_END ();
157+
}

0 commit comments

Comments
 (0)