blob: 6260094a4947133b7a118291e8523e40dad706c4 [file] [log] [blame]
David Reiss709b69f2010-10-06 17:10:30 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19#ifndef _GNU_SOURCE
20#define _GNU_SOURCE // needed for getopt_long
21#endif
22
23#include <sys/time.h>
24#include <getopt.h>
25#include <boost/test/unit_test.hpp>
26
27#include <transport/TFileTransport.h>
28
29using namespace apache::thrift::transport;
30
31/**************************************************************************
32 * Global state
33 **************************************************************************/
34
35static const char* tmp_dir = "/tmp";
36
37class FsyncLog;
38FsyncLog* fsync_log;
39
40
41/**************************************************************************
42 * Helper code
43 **************************************************************************/
44
45// Provide BOOST_CHECK_LT() and BOOST_CHECK_GT(), in case we're compiled
46// with an older version of boost
47#ifndef BOOST_CHECK_LT
David Reiss4f9efdb2010-10-06 17:10:33 +000048#define BOOST_CHECK_CMP(a, b, op, check_fn) \
49 check_fn((a) op (b), \
50 "check " BOOST_STRINGIZE(a) " " BOOST_STRINGIZE(op) " " \
51 BOOST_STRINGIZE(b) " failed: " BOOST_STRINGIZE(a) "=" << (a) << \
52 " " BOOST_STRINGIZE(b) "=" << (b))
David Reiss709b69f2010-10-06 17:10:30 +000053
David Reiss4f9efdb2010-10-06 17:10:33 +000054#define BOOST_CHECK_LT(a, b) BOOST_CHECK_CMP(a, b, <, BOOST_CHECK_MESSAGE)
55#define BOOST_CHECK_GT(a, b) BOOST_CHECK_CMP(a, b, >, BOOST_CHECK_MESSAGE)
56#define BOOST_REQUIRE_LT(a, b) BOOST_CHECK_CMP(a, b, <, BOOST_REQUIRE_MESSAGE)
David Reiss709b69f2010-10-06 17:10:30 +000057#endif // BOOST_CHECK_LT
58
59/**
60 * Class to record calls to fsync
61 */
62class FsyncLog {
63 public:
64 struct FsyncCall {
65 struct timeval time;
66 int fd;
67 };
68 typedef std::list<FsyncCall> CallList;
69
70 FsyncLog() {}
71
72 void fsync(int fd) {
Roger Meier3b771a12010-11-17 22:11:26 +000073 (void) fd;
David Reiss709b69f2010-10-06 17:10:30 +000074 FsyncCall call;
75 gettimeofday(&call.time, NULL);
76 calls_.push_back(call);
77 }
78
79 const CallList* getCalls() const {
80 return &calls_;
81 }
82
83 private:
84 CallList calls_;
85};
86
87/**
88 * Helper class to clean up temporary files
89 */
90class TempFile {
91 public:
92 TempFile(const char* directory, const char* prefix) {
93 size_t path_len = strlen(directory) + strlen(prefix) + 8;
94 path_ = new char[path_len];
95 snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix);
96
97 fd_ = mkstemp(path_);
98 if (fd_ < 0) {
99 throw apache::thrift::TException("mkstemp() failed");
100 }
101 }
102
103 ~TempFile() {
104 unlink();
105 close();
106 }
107
108 const char* getPath() const {
109 return path_;
110 }
111
112 int getFD() const {
113 return fd_;
114 }
115
116 void unlink() {
117 if (path_) {
118 ::unlink(path_);
119 delete[] path_;
120 path_ = NULL;
121 }
122 }
123
124 void close() {
125 if (fd_ < 0) {
126 return;
127 }
128
129 ::close(fd_);
130 fd_ = -1;
131 }
132
133 private:
134 char* path_;
135 int fd_;
136};
137
138// Use our own version of fsync() for testing.
139// This returns immediately, so timing in test_destructor() isn't affected by
140// waiting on the actual filesystem.
141extern "C"
142int fsync(int fd) {
143 if (fsync_log) {
144 fsync_log->fsync(fd);
145 }
146 return 0;
147}
148
David Reiss709b69f2010-10-06 17:10:30 +0000149int time_diff(const struct timeval* t1, const struct timeval* t2) {
150 return (t2->tv_usec - t1->tv_usec) + (t2->tv_sec - t1->tv_sec) * 1000000;
151}
152
153
154/**************************************************************************
155 * Test cases
156 **************************************************************************/
157
158/**
159 * Make sure the TFileTransport destructor exits "quickly".
160 *
161 * Previous versions had a bug causing the writer thread not to exit
162 * right away.
163 *
164 * It's kind of lame that we just check to see how long the destructor takes in
165 * wall-clock time. This could result in false failures on slower systems, or
166 * on heavily loaded machines.
167 */
David Reiss109693c2010-10-06 17:10:42 +0000168BOOST_AUTO_TEST_CASE(test_destructor) {
David Reiss709b69f2010-10-06 17:10:30 +0000169 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
170
171 unsigned int const NUM_ITERATIONS = 1000;
David Reiss709b69f2010-10-06 17:10:30 +0000172
Roger Meier085a3e72010-10-08 21:23:35 +0000173 unsigned int num_over = 0;
David Reiss709b69f2010-10-06 17:10:30 +0000174 for (unsigned int n = 0; n < NUM_ITERATIONS; ++n) {
175 ftruncate(f.getFD(), 0);
176
177 TFileTransport* transport = new TFileTransport(f.getPath());
178
179 // write something so that the writer thread gets started
180 transport->write(reinterpret_cast<const uint8_t*>("foo"), 3);
181
182 // Every other iteration, also call flush(), just in case that potentially
183 // has any effect on how the writer thread wakes up.
184 if (n & 0x1) {
185 transport->flush();
186 }
187
188 /*
189 * Time the call to the destructor
190 */
191 struct timeval start;
192 struct timeval end;
193
194 gettimeofday(&start, NULL);
195 delete transport;
196 gettimeofday(&end, NULL);
197
198 int delta = time_diff(&start, &end);
David Reiss41993772010-10-06 17:10:31 +0000199
200 // If any attempt takes more than 100ms, treat that as a failure.
201 // Treat this as a fatal failure, so we'll return now instead of
202 // looping over a very slow operation.
203 BOOST_REQUIRE_LT(delta, 100000);
204
Roger Meier085a3e72010-10-08 21:23:35 +0000205 // Normally, it takes less than 1000us on my dev box.
David Reiss41993772010-10-06 17:10:31 +0000206 // However, if the box is heavily loaded, some of the test runs
207 // take longer, since we're just waiting for our turn on the CPU.
Roger Meier085a3e72010-10-08 21:23:35 +0000208 if (delta > 1000) {
209 ++num_over;
David Reiss41993772010-10-06 17:10:31 +0000210 }
David Reiss709b69f2010-10-06 17:10:30 +0000211 }
David Reiss41993772010-10-06 17:10:31 +0000212
Roger Meier085a3e72010-10-08 21:23:35 +0000213 // Make sure fewer than 10% of the runs took longer than 1000us
214 BOOST_CHECK(num_over < (NUM_ITERATIONS / 10));
David Reiss709b69f2010-10-06 17:10:30 +0000215}
216
217/**
218 * Make sure setFlushMaxUs() is honored.
219 */
220void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us,
221 uint32_t test_us) {
222 // TFileTransport only calls fsync() if data has been written,
223 // so make sure the write interval is smaller than the flush interval.
224 BOOST_REQUIRE(write_us < flush_us);
225
226 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
227
228 // Record calls to fsync()
229 FsyncLog log;
230 fsync_log = &log;
231
232 TFileTransport* transport = new TFileTransport(f.getPath());
233 // Don't flush because of # of bytes written
234 transport->setFlushMaxBytes(0xffffffff);
235 uint8_t buf[] = "a";
236 uint32_t buflen = sizeof(buf);
237
David Reiss41993772010-10-06 17:10:31 +0000238 // Set the flush interval
David Reiss709b69f2010-10-06 17:10:30 +0000239 transport->setFlushMaxUs(flush_us);
240
David Reiss41993772010-10-06 17:10:31 +0000241 // Make one call to write, to start the writer thread now.
242 // (If we just let the thread get created during our test loop,
243 // the thread creation sometimes takes long enough to make the first
244 // fsync interval fail the check.)
245 transport->write(buf, buflen);
246
David Reiss709b69f2010-10-06 17:10:30 +0000247 // Add one entry to the fsync log, just to mark the start time
248 log.fsync(-1);
249
250 // Loop doing write(), sleep(), ...
251 uint32_t total_time = 0;
252 while (true) {
253 transport->write(buf, buflen);
254 if (total_time > test_us) {
255 break;
256 }
257 usleep(write_us);
258 total_time += write_us;
259 }
260
261 delete transport;
262
263 // Stop logging new fsync() calls
264 fsync_log = NULL;
265
266 // Examine the fsync() log
267 //
268 // TFileTransport uses pthread_cond_timedwait(), which only has millisecond
269 // resolution. In my testing, it normally wakes up about 1 millisecond late.
270 // However, sometimes it takes a bit longer. Allow 5ms leeway.
271 int max_allowed_delta = flush_us + 5000;
272
273 const FsyncLog::CallList* calls = log.getCalls();
274 // We added 1 fsync call above.
275 // Make sure TFileTransport called fsync at least once
Christian Lavoie01c5ceb2010-11-04 20:35:15 +0000276 BOOST_CHECK_GT(calls->size(),
277 static_cast<FsyncLog::CallList::size_type>(1));
David Reiss709b69f2010-10-06 17:10:30 +0000278
279 const struct timeval* prev_time = NULL;
280 for (FsyncLog::CallList::const_iterator it = calls->begin();
281 it != calls->end();
282 ++it) {
283 if (prev_time) {
284 int delta = time_diff(prev_time, &it->time);
285 BOOST_CHECK_LT(delta, max_allowed_delta);
286 }
287 prev_time = &it->time;
288 }
289}
290
David Reiss109693c2010-10-06 17:10:42 +0000291BOOST_AUTO_TEST_CASE(test_flush_max_us1) {
David Reiss709b69f2010-10-06 17:10:30 +0000292 // fsync every 10ms, write every 5ms, for 500ms
293 test_flush_max_us_impl(10000, 5000, 500000);
294}
295
David Reiss109693c2010-10-06 17:10:42 +0000296BOOST_AUTO_TEST_CASE(test_flush_max_us2) {
David Reiss709b69f2010-10-06 17:10:30 +0000297 // fsync every 50ms, write every 20ms, for 500ms
298 test_flush_max_us_impl(50000, 20000, 500000);
299}
300
David Reiss109693c2010-10-06 17:10:42 +0000301BOOST_AUTO_TEST_CASE(test_flush_max_us3) {
David Reiss709b69f2010-10-06 17:10:30 +0000302 // fsync every 400ms, write every 300ms, for 1s
303 test_flush_max_us_impl(400000, 300000, 1000000);
304}
305
David Reiss4f9efdb2010-10-06 17:10:33 +0000306/**
307 * Make sure flush() is fast when there is nothing to do.
308 *
309 * TFileTransport used to have a bug where flush() would wait for the fsync
310 * timeout to expire.
311 */
David Reiss109693c2010-10-06 17:10:42 +0000312BOOST_AUTO_TEST_CASE(test_noop_flush) {
David Reiss4f9efdb2010-10-06 17:10:33 +0000313 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
314 TFileTransport transport(f.getPath());
315
316 // Write something to start the writer thread.
317 uint8_t buf[] = "a";
318 transport.write(buf, 1);
319
320 struct timeval start;
321 gettimeofday(&start, NULL);
322
323 for (unsigned int n = 0; n < 10; ++n) {
324 transport.flush();
325
326 struct timeval now;
327 gettimeofday(&now, NULL);
328
329 // Fail if at any point we've been running for longer than half a second.
330 // (With the buggy code, TFileTransport used to take 3 seconds per flush())
331 //
332 // Use a fatal fail so we break out early, rather than continuing to make
333 // many more slow flush() calls.
334 int delta = time_diff(&start, &now);
335 BOOST_REQUIRE_LT(delta, 500000);
336 }
337}
338
David Reiss709b69f2010-10-06 17:10:30 +0000339/**************************************************************************
340 * General Initialization
341 **************************************************************************/
342
343void print_usage(FILE* f, const char* argv0) {
344 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
345 fprintf(f, "Options:\n");
346 fprintf(f, " --tmp-dir=DIR, -t DIR\n");
347 fprintf(f, " --help\n");
348}
349
350void parse_args(int argc, char* argv[]) {
David Reiss709b69f2010-10-06 17:10:30 +0000351 struct option long_opts[] = {
352 { "help", false, NULL, 'h' },
353 { "tmp-dir", true, NULL, 't' },
354 { NULL, 0, NULL, 0 }
355 };
356
357 while (true) {
358 optopt = 1;
359 int optchar = getopt_long(argc, argv, "ht:", long_opts, NULL);
360 if (optchar == -1) {
361 break;
362 }
363
364 switch (optchar) {
365 case 't':
366 tmp_dir = optarg;
367 break;
368 case 'h':
369 print_usage(stdout, argv[0]);
370 exit(0);
371 case '?':
372 exit(1);
373 default:
374 // Only happens if someone adds another option to the optarg string,
375 // but doesn't update the switch statement to handle it.
376 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
377 exit(1);
378 }
379 }
380}
381
382boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
David Reiss109693c2010-10-06 17:10:42 +0000383 boost::unit_test::framework::master_test_suite().p_name.value =
384 "TFileTransportTest";
385
David Reiss709b69f2010-10-06 17:10:30 +0000386 // Parse arguments
387 parse_args(argc, argv);
David Reiss109693c2010-10-06 17:10:42 +0000388 return NULL;
David Reiss709b69f2010-10-06 17:10:30 +0000389}