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