blob: 6ba71f3a72c6fc0738eba29b7b5f8e4cf8af7071 [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
Roger Meier2fa9c312011-09-05 19:15:53 +000023#ifdef HAVE_CONFIG_H
24#include <config.h>
25#endif
26#ifdef HAVE_SYS_TIME_H
David Reiss709b69f2010-10-06 17:10:30 +000027#include <sys/time.h>
Roger Meier2fa9c312011-09-05 19:15:53 +000028#endif
David Reiss709b69f2010-10-06 17:10:30 +000029#include <getopt.h>
30#include <boost/test/unit_test.hpp>
31
32#include <transport/TFileTransport.h>
33
34using namespace apache::thrift::transport;
35
36/**************************************************************************
37 * Global state
38 **************************************************************************/
39
40static const char* tmp_dir = "/tmp";
41
42class FsyncLog;
43FsyncLog* fsync_log;
44
45
46/**************************************************************************
47 * Helper code
48 **************************************************************************/
49
50// Provide BOOST_CHECK_LT() and BOOST_CHECK_GT(), in case we're compiled
51// with an older version of boost
52#ifndef BOOST_CHECK_LT
David Reiss4f9efdb2010-10-06 17:10:33 +000053#define BOOST_CHECK_CMP(a, b, op, check_fn) \
54 check_fn((a) op (b), \
55 "check " BOOST_STRINGIZE(a) " " BOOST_STRINGIZE(op) " " \
56 BOOST_STRINGIZE(b) " failed: " BOOST_STRINGIZE(a) "=" << (a) << \
57 " " BOOST_STRINGIZE(b) "=" << (b))
David Reiss709b69f2010-10-06 17:10:30 +000058
David Reiss4f9efdb2010-10-06 17:10:33 +000059#define BOOST_CHECK_LT(a, b) BOOST_CHECK_CMP(a, b, <, BOOST_CHECK_MESSAGE)
60#define BOOST_CHECK_GT(a, b) BOOST_CHECK_CMP(a, b, >, BOOST_CHECK_MESSAGE)
61#define BOOST_REQUIRE_LT(a, b) BOOST_CHECK_CMP(a, b, <, BOOST_REQUIRE_MESSAGE)
David Reiss709b69f2010-10-06 17:10:30 +000062#endif // BOOST_CHECK_LT
63
64/**
65 * Class to record calls to fsync
66 */
67class FsyncLog {
68 public:
69 struct FsyncCall {
70 struct timeval time;
71 int fd;
72 };
73 typedef std::list<FsyncCall> CallList;
74
75 FsyncLog() {}
76
77 void fsync(int fd) {
Roger Meier3b771a12010-11-17 22:11:26 +000078 (void) fd;
David Reiss709b69f2010-10-06 17:10:30 +000079 FsyncCall call;
80 gettimeofday(&call.time, NULL);
81 calls_.push_back(call);
82 }
83
84 const CallList* getCalls() const {
85 return &calls_;
86 }
87
88 private:
89 CallList calls_;
90};
91
92/**
93 * Helper class to clean up temporary files
94 */
95class TempFile {
96 public:
97 TempFile(const char* directory, const char* prefix) {
98 size_t path_len = strlen(directory) + strlen(prefix) + 8;
99 path_ = new char[path_len];
100 snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix);
101
102 fd_ = mkstemp(path_);
103 if (fd_ < 0) {
104 throw apache::thrift::TException("mkstemp() failed");
105 }
106 }
107
108 ~TempFile() {
109 unlink();
110 close();
111 }
112
113 const char* getPath() const {
114 return path_;
115 }
116
117 int getFD() const {
118 return fd_;
119 }
120
121 void unlink() {
122 if (path_) {
123 ::unlink(path_);
124 delete[] path_;
125 path_ = NULL;
126 }
127 }
128
129 void close() {
130 if (fd_ < 0) {
131 return;
132 }
133
134 ::close(fd_);
135 fd_ = -1;
136 }
137
138 private:
139 char* path_;
140 int fd_;
141};
142
143// Use our own version of fsync() for testing.
144// This returns immediately, so timing in test_destructor() isn't affected by
145// waiting on the actual filesystem.
146extern "C"
147int fsync(int fd) {
148 if (fsync_log) {
149 fsync_log->fsync(fd);
150 }
151 return 0;
152}
153
David Reiss709b69f2010-10-06 17:10:30 +0000154int time_diff(const struct timeval* t1, const struct timeval* t2) {
155 return (t2->tv_usec - t1->tv_usec) + (t2->tv_sec - t1->tv_sec) * 1000000;
156}
157
158
159/**************************************************************************
160 * Test cases
161 **************************************************************************/
162
163/**
164 * Make sure the TFileTransport destructor exits "quickly".
165 *
166 * Previous versions had a bug causing the writer thread not to exit
167 * right away.
168 *
169 * It's kind of lame that we just check to see how long the destructor takes in
170 * wall-clock time. This could result in false failures on slower systems, or
171 * on heavily loaded machines.
172 */
David Reiss109693c2010-10-06 17:10:42 +0000173BOOST_AUTO_TEST_CASE(test_destructor) {
David Reiss709b69f2010-10-06 17:10:30 +0000174 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
175
176 unsigned int const NUM_ITERATIONS = 1000;
David Reiss709b69f2010-10-06 17:10:30 +0000177
Roger Meier085a3e72010-10-08 21:23:35 +0000178 unsigned int num_over = 0;
David Reiss709b69f2010-10-06 17:10:30 +0000179 for (unsigned int n = 0; n < NUM_ITERATIONS; ++n) {
180 ftruncate(f.getFD(), 0);
181
182 TFileTransport* transport = new TFileTransport(f.getPath());
183
184 // write something so that the writer thread gets started
185 transport->write(reinterpret_cast<const uint8_t*>("foo"), 3);
186
187 // Every other iteration, also call flush(), just in case that potentially
188 // has any effect on how the writer thread wakes up.
189 if (n & 0x1) {
190 transport->flush();
191 }
192
193 /*
194 * Time the call to the destructor
195 */
196 struct timeval start;
197 struct timeval end;
198
199 gettimeofday(&start, NULL);
200 delete transport;
201 gettimeofday(&end, NULL);
202
203 int delta = time_diff(&start, &end);
David Reiss41993772010-10-06 17:10:31 +0000204
Roger Meier1ebeffb2011-06-06 18:00:03 +0000205 // If any attempt takes more than 500ms, treat that as a failure.
David Reiss41993772010-10-06 17:10:31 +0000206 // Treat this as a fatal failure, so we'll return now instead of
207 // looping over a very slow operation.
Roger Meier1ebeffb2011-06-06 18:00:03 +0000208 BOOST_REQUIRE_LT(delta, 500000);
David Reiss41993772010-10-06 17:10:31 +0000209
Roger Meier1ebeffb2011-06-06 18:00:03 +0000210 // Normally, it takes less than 100ms on my dev box.
David Reiss41993772010-10-06 17:10:31 +0000211 // However, if the box is heavily loaded, some of the test runs
212 // take longer, since we're just waiting for our turn on the CPU.
Roger Meier1ebeffb2011-06-06 18:00:03 +0000213 if (delta > 100000) {
Roger Meier085a3e72010-10-08 21:23:35 +0000214 ++num_over;
David Reiss41993772010-10-06 17:10:31 +0000215 }
David Reiss709b69f2010-10-06 17:10:30 +0000216 }
David Reiss41993772010-10-06 17:10:31 +0000217
Roger Meier085a3e72010-10-08 21:23:35 +0000218 // Make sure fewer than 10% of the runs took longer than 1000us
219 BOOST_CHECK(num_over < (NUM_ITERATIONS / 10));
David Reiss709b69f2010-10-06 17:10:30 +0000220}
221
222/**
223 * Make sure setFlushMaxUs() is honored.
224 */
225void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us,
226 uint32_t test_us) {
227 // TFileTransport only calls fsync() if data has been written,
228 // so make sure the write interval is smaller than the flush interval.
229 BOOST_REQUIRE(write_us < flush_us);
230
231 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
232
233 // Record calls to fsync()
234 FsyncLog log;
235 fsync_log = &log;
236
237 TFileTransport* transport = new TFileTransport(f.getPath());
238 // Don't flush because of # of bytes written
239 transport->setFlushMaxBytes(0xffffffff);
240 uint8_t buf[] = "a";
241 uint32_t buflen = sizeof(buf);
242
David Reiss41993772010-10-06 17:10:31 +0000243 // Set the flush interval
David Reiss709b69f2010-10-06 17:10:30 +0000244 transport->setFlushMaxUs(flush_us);
245
David Reiss41993772010-10-06 17:10:31 +0000246 // Make one call to write, to start the writer thread now.
247 // (If we just let the thread get created during our test loop,
248 // the thread creation sometimes takes long enough to make the first
249 // fsync interval fail the check.)
250 transport->write(buf, buflen);
251
David Reiss709b69f2010-10-06 17:10:30 +0000252 // Add one entry to the fsync log, just to mark the start time
253 log.fsync(-1);
254
255 // Loop doing write(), sleep(), ...
256 uint32_t total_time = 0;
257 while (true) {
258 transport->write(buf, buflen);
259 if (total_time > test_us) {
260 break;
261 }
262 usleep(write_us);
263 total_time += write_us;
264 }
265
266 delete transport;
267
268 // Stop logging new fsync() calls
269 fsync_log = NULL;
270
271 // Examine the fsync() log
272 //
273 // TFileTransport uses pthread_cond_timedwait(), which only has millisecond
274 // resolution. In my testing, it normally wakes up about 1 millisecond late.
275 // However, sometimes it takes a bit longer. Allow 5ms leeway.
276 int max_allowed_delta = flush_us + 5000;
277
278 const FsyncLog::CallList* calls = log.getCalls();
279 // We added 1 fsync call above.
280 // Make sure TFileTransport called fsync at least once
Christian Lavoie01c5ceb2010-11-04 20:35:15 +0000281 BOOST_CHECK_GT(calls->size(),
282 static_cast<FsyncLog::CallList::size_type>(1));
David Reiss709b69f2010-10-06 17:10:30 +0000283
284 const struct timeval* prev_time = NULL;
285 for (FsyncLog::CallList::const_iterator it = calls->begin();
286 it != calls->end();
287 ++it) {
288 if (prev_time) {
289 int delta = time_diff(prev_time, &it->time);
290 BOOST_CHECK_LT(delta, max_allowed_delta);
291 }
292 prev_time = &it->time;
293 }
294}
295
David Reiss109693c2010-10-06 17:10:42 +0000296BOOST_AUTO_TEST_CASE(test_flush_max_us1) {
David Reiss709b69f2010-10-06 17:10:30 +0000297 // fsync every 10ms, write every 5ms, for 500ms
298 test_flush_max_us_impl(10000, 5000, 500000);
299}
300
David Reiss109693c2010-10-06 17:10:42 +0000301BOOST_AUTO_TEST_CASE(test_flush_max_us2) {
David Reiss709b69f2010-10-06 17:10:30 +0000302 // fsync every 50ms, write every 20ms, for 500ms
303 test_flush_max_us_impl(50000, 20000, 500000);
304}
305
David Reiss109693c2010-10-06 17:10:42 +0000306BOOST_AUTO_TEST_CASE(test_flush_max_us3) {
David Reiss709b69f2010-10-06 17:10:30 +0000307 // fsync every 400ms, write every 300ms, for 1s
308 test_flush_max_us_impl(400000, 300000, 1000000);
309}
310
David Reiss4f9efdb2010-10-06 17:10:33 +0000311/**
312 * Make sure flush() is fast when there is nothing to do.
313 *
314 * TFileTransport used to have a bug where flush() would wait for the fsync
315 * timeout to expire.
316 */
David Reiss109693c2010-10-06 17:10:42 +0000317BOOST_AUTO_TEST_CASE(test_noop_flush) {
David Reiss4f9efdb2010-10-06 17:10:33 +0000318 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
319 TFileTransport transport(f.getPath());
320
321 // Write something to start the writer thread.
322 uint8_t buf[] = "a";
323 transport.write(buf, 1);
324
325 struct timeval start;
326 gettimeofday(&start, NULL);
327
328 for (unsigned int n = 0; n < 10; ++n) {
329 transport.flush();
330
331 struct timeval now;
332 gettimeofday(&now, NULL);
333
334 // Fail if at any point we've been running for longer than half a second.
335 // (With the buggy code, TFileTransport used to take 3 seconds per flush())
336 //
337 // Use a fatal fail so we break out early, rather than continuing to make
338 // many more slow flush() calls.
339 int delta = time_diff(&start, &now);
Roger Meier1ebeffb2011-06-06 18:00:03 +0000340 BOOST_REQUIRE_LT(delta, 2000000);
David Reiss4f9efdb2010-10-06 17:10:33 +0000341 }
342}
343
David Reiss709b69f2010-10-06 17:10:30 +0000344/**************************************************************************
345 * General Initialization
346 **************************************************************************/
347
348void print_usage(FILE* f, const char* argv0) {
349 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
350 fprintf(f, "Options:\n");
351 fprintf(f, " --tmp-dir=DIR, -t DIR\n");
352 fprintf(f, " --help\n");
353}
354
355void parse_args(int argc, char* argv[]) {
David Reiss709b69f2010-10-06 17:10:30 +0000356 struct option long_opts[] = {
357 { "help", false, NULL, 'h' },
358 { "tmp-dir", true, NULL, 't' },
359 { NULL, 0, NULL, 0 }
360 };
361
362 while (true) {
363 optopt = 1;
364 int optchar = getopt_long(argc, argv, "ht:", long_opts, NULL);
365 if (optchar == -1) {
366 break;
367 }
368
369 switch (optchar) {
370 case 't':
371 tmp_dir = optarg;
372 break;
373 case 'h':
374 print_usage(stdout, argv[0]);
375 exit(0);
376 case '?':
377 exit(1);
378 default:
379 // Only happens if someone adds another option to the optarg string,
380 // but doesn't update the switch statement to handle it.
381 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
382 exit(1);
383 }
384 }
385}
386
387boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
David Reiss109693c2010-10-06 17:10:42 +0000388 boost::unit_test::framework::master_test_suite().p_name.value =
389 "TFileTransportTest";
390
David Reiss709b69f2010-10-06 17:10:30 +0000391 // Parse arguments
392 parse_args(argc, argv);
David Reiss109693c2010-10-06 17:10:42 +0000393 return NULL;
David Reiss709b69f2010-10-06 17:10:30 +0000394}