blob: 294c9a685a097cf80e156e20cd3481d0fbb0b446 [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
David Reiss709b69f2010-10-06 17:10:30 +000044/**************************************************************************
45 * Helper code
46 **************************************************************************/
47
Roger Meier09cc5e72014-01-15 10:13:18 +010048// Provide BOOST_WARN_LT() and BOOST_WARN_GT(), in case we're compiled
David Reiss709b69f2010-10-06 17:10:30 +000049// with an older version of boost
Roger Meier09cc5e72014-01-15 10:13:18 +010050#ifndef BOOST_WARN_LT
Konrad Grochowski16a23a62014-11-13 15:33:38 +010051#define BOOST_WARN_CMP(a, b, op, check_fn) \
52 check_fn((a)op(b), \
53 "check " BOOST_STRINGIZE(a) " " BOOST_STRINGIZE(op) " " BOOST_STRINGIZE( \
54 b) " failed: " BOOST_STRINGIZE(a) "=" \
55 << (a) << " " BOOST_STRINGIZE(b) "=" << (b))
David Reiss709b69f2010-10-06 17:10:30 +000056
Roger Meier09cc5e72014-01-15 10:13:18 +010057#define BOOST_WARN_LT(a, b) BOOST_WARN_CMP(a, b, <, BOOST_WARN_MESSAGE)
58#define BOOST_WARN_GT(a, b) BOOST_WARN_CMP(a, b, >, BOOST_WARN_MESSAGE)
59#define BOOST_WARN_LT(a, b) BOOST_WARN_CMP(a, b, <, BOOST_WARN_MESSAGE)
60#endif // BOOST_WARN_LT
David Reiss709b69f2010-10-06 17:10:30 +000061
62/**
63 * Class to record calls to fsync
64 */
65class FsyncLog {
Konrad Grochowski16a23a62014-11-13 15:33:38 +010066public:
David Reiss709b69f2010-10-06 17:10:30 +000067 struct FsyncCall {
68 struct timeval time;
69 int fd;
70 };
71 typedef std::list<FsyncCall> CallList;
72
73 FsyncLog() {}
74
75 void fsync(int fd) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +010076 (void)fd;
David Reiss709b69f2010-10-06 17:10:30 +000077 FsyncCall call;
78 gettimeofday(&call.time, NULL);
79 calls_.push_back(call);
80 }
81
Konrad Grochowski16a23a62014-11-13 15:33:38 +010082 const CallList* getCalls() const { return &calls_; }
David Reiss709b69f2010-10-06 17:10:30 +000083
Konrad Grochowski16a23a62014-11-13 15:33:38 +010084private:
David Reiss709b69f2010-10-06 17:10:30 +000085 CallList calls_;
86};
87
88/**
89 * Helper class to clean up temporary files
90 */
91class TempFile {
Konrad Grochowski16a23a62014-11-13 15:33:38 +010092public:
David Reiss709b69f2010-10-06 17:10:30 +000093 TempFile(const char* directory, const char* prefix) {
94 size_t path_len = strlen(directory) + strlen(prefix) + 8;
95 path_ = new char[path_len];
96 snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix);
97
98 fd_ = mkstemp(path_);
99 if (fd_ < 0) {
100 throw apache::thrift::TException("mkstemp() failed");
101 }
102 }
103
104 ~TempFile() {
105 unlink();
106 close();
107 }
108
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100109 const char* getPath() const { return path_; }
David Reiss709b69f2010-10-06 17:10:30 +0000110
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100111 int getFD() const { return fd_; }
David Reiss709b69f2010-10-06 17:10:30 +0000112
113 void unlink() {
114 if (path_) {
115 ::unlink(path_);
116 delete[] path_;
117 path_ = NULL;
118 }
119 }
120
121 void close() {
122 if (fd_ < 0) {
123 return;
124 }
125
126 ::close(fd_);
127 fd_ = -1;
128 }
129
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100130private:
David Reiss709b69f2010-10-06 17:10:30 +0000131 char* path_;
132 int fd_;
133};
134
135// Use our own version of fsync() for testing.
136// This returns immediately, so timing in test_destructor() isn't affected by
137// waiting on the actual filesystem.
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100138extern "C" int fsync(int fd) {
David Reiss709b69f2010-10-06 17:10:30 +0000139 if (fsync_log) {
140 fsync_log->fsync(fd);
141 }
142 return 0;
143}
144
David Reiss709b69f2010-10-06 17:10:30 +0000145int time_diff(const struct timeval* t1, const struct timeval* t2) {
146 return (t2->tv_usec - t1->tv_usec) + (t2->tv_sec - t1->tv_sec) * 1000000;
147}
148
David Reiss709b69f2010-10-06 17:10:30 +0000149/**************************************************************************
150 * Test cases
151 **************************************************************************/
152
153/**
154 * Make sure the TFileTransport destructor exits "quickly".
155 *
156 * Previous versions had a bug causing the writer thread not to exit
157 * right away.
158 *
159 * It's kind of lame that we just check to see how long the destructor takes in
160 * wall-clock time. This could result in false failures on slower systems, or
161 * on heavily loaded machines.
162 */
David Reiss109693c2010-10-06 17:10:42 +0000163BOOST_AUTO_TEST_CASE(test_destructor) {
David Reiss709b69f2010-10-06 17:10:30 +0000164 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
165
166 unsigned int const NUM_ITERATIONS = 1000;
David Reiss709b69f2010-10-06 17:10:30 +0000167
Roger Meier085a3e72010-10-08 21:23:35 +0000168 unsigned int num_over = 0;
David Reiss709b69f2010-10-06 17:10:30 +0000169 for (unsigned int n = 0; n < NUM_ITERATIONS; ++n) {
170 ftruncate(f.getFD(), 0);
171
172 TFileTransport* transport = new TFileTransport(f.getPath());
173
174 // write something so that the writer thread gets started
175 transport->write(reinterpret_cast<const uint8_t*>("foo"), 3);
176
177 // Every other iteration, also call flush(), just in case that potentially
178 // has any effect on how the writer thread wakes up.
179 if (n & 0x1) {
180 transport->flush();
181 }
182
183 /*
184 * Time the call to the destructor
185 */
186 struct timeval start;
187 struct timeval end;
188
189 gettimeofday(&start, NULL);
190 delete transport;
191 gettimeofday(&end, NULL);
192
193 int delta = time_diff(&start, &end);
David Reiss41993772010-10-06 17:10:31 +0000194
Roger Meier1ebeffb2011-06-06 18:00:03 +0000195 // If any attempt takes more than 500ms, treat that as a failure.
David Reiss41993772010-10-06 17:10:31 +0000196 // Treat this as a fatal failure, so we'll return now instead of
197 // looping over a very slow operation.
Roger Meier09cc5e72014-01-15 10:13:18 +0100198 BOOST_WARN_LT(delta, 500000);
David Reiss41993772010-10-06 17:10:31 +0000199
Roger Meier1ebeffb2011-06-06 18:00:03 +0000200 // Normally, it takes less than 100ms on my dev box.
David Reiss41993772010-10-06 17:10:31 +0000201 // However, if the box is heavily loaded, some of the test runs
202 // take longer, since we're just waiting for our turn on the CPU.
Roger Meier1ebeffb2011-06-06 18:00:03 +0000203 if (delta > 100000) {
Roger Meier085a3e72010-10-08 21:23:35 +0000204 ++num_over;
David Reiss41993772010-10-06 17:10:31 +0000205 }
David Reiss709b69f2010-10-06 17:10:30 +0000206 }
David Reiss41993772010-10-06 17:10:31 +0000207
Roger Meier085a3e72010-10-08 21:23:35 +0000208 // Make sure fewer than 10% of the runs took longer than 1000us
Roger Meier09cc5e72014-01-15 10:13:18 +0100209 BOOST_WARN(num_over < (NUM_ITERATIONS / 10));
David Reiss709b69f2010-10-06 17:10:30 +0000210}
211
212/**
213 * Make sure setFlushMaxUs() is honored.
214 */
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100215void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us, uint32_t test_us) {
David Reiss709b69f2010-10-06 17:10:30 +0000216 // TFileTransport only calls fsync() if data has been written,
217 // so make sure the write interval is smaller than the flush interval.
Roger Meier09cc5e72014-01-15 10:13:18 +0100218 BOOST_WARN(write_us < flush_us);
David Reiss709b69f2010-10-06 17:10:30 +0000219
220 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
221
222 // Record calls to fsync()
223 FsyncLog log;
224 fsync_log = &log;
225
226 TFileTransport* transport = new TFileTransport(f.getPath());
227 // Don't flush because of # of bytes written
228 transport->setFlushMaxBytes(0xffffffff);
229 uint8_t buf[] = "a";
230 uint32_t buflen = sizeof(buf);
231
David Reiss41993772010-10-06 17:10:31 +0000232 // Set the flush interval
David Reiss709b69f2010-10-06 17:10:30 +0000233 transport->setFlushMaxUs(flush_us);
234
David Reiss41993772010-10-06 17:10:31 +0000235 // Make one call to write, to start the writer thread now.
236 // (If we just let the thread get created during our test loop,
237 // the thread creation sometimes takes long enough to make the first
238 // fsync interval fail the check.)
239 transport->write(buf, buflen);
240
David Reiss709b69f2010-10-06 17:10:30 +0000241 // Add one entry to the fsync log, just to mark the start time
242 log.fsync(-1);
243
244 // Loop doing write(), sleep(), ...
245 uint32_t total_time = 0;
246 while (true) {
247 transport->write(buf, buflen);
248 if (total_time > test_us) {
249 break;
250 }
251 usleep(write_us);
252 total_time += write_us;
253 }
254
255 delete transport;
256
257 // Stop logging new fsync() calls
258 fsync_log = NULL;
259
260 // Examine the fsync() log
261 //
262 // TFileTransport uses pthread_cond_timedwait(), which only has millisecond
263 // resolution. In my testing, it normally wakes up about 1 millisecond late.
264 // However, sometimes it takes a bit longer. Allow 5ms leeway.
265 int max_allowed_delta = flush_us + 5000;
266
267 const FsyncLog::CallList* calls = log.getCalls();
268 // We added 1 fsync call above.
269 // Make sure TFileTransport called fsync at least once
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100270 BOOST_WARN_GE(calls->size(), static_cast<FsyncLog::CallList::size_type>(1));
David Reiss709b69f2010-10-06 17:10:30 +0000271
272 const struct timeval* prev_time = NULL;
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100273 for (FsyncLog::CallList::const_iterator it = calls->begin(); it != calls->end(); ++it) {
David Reiss709b69f2010-10-06 17:10:30 +0000274 if (prev_time) {
275 int delta = time_diff(prev_time, &it->time);
Roger Meier09cc5e72014-01-15 10:13:18 +0100276 BOOST_WARN_LT(delta, max_allowed_delta);
David Reiss709b69f2010-10-06 17:10:30 +0000277 }
278 prev_time = &it->time;
279 }
280}
281
David Reiss109693c2010-10-06 17:10:42 +0000282BOOST_AUTO_TEST_CASE(test_flush_max_us1) {
David Reiss709b69f2010-10-06 17:10:30 +0000283 // fsync every 10ms, write every 5ms, for 500ms
284 test_flush_max_us_impl(10000, 5000, 500000);
285}
286
David Reiss109693c2010-10-06 17:10:42 +0000287BOOST_AUTO_TEST_CASE(test_flush_max_us2) {
David Reiss709b69f2010-10-06 17:10:30 +0000288 // fsync every 50ms, write every 20ms, for 500ms
289 test_flush_max_us_impl(50000, 20000, 500000);
290}
291
David Reiss109693c2010-10-06 17:10:42 +0000292BOOST_AUTO_TEST_CASE(test_flush_max_us3) {
David Reiss709b69f2010-10-06 17:10:30 +0000293 // fsync every 400ms, write every 300ms, for 1s
294 test_flush_max_us_impl(400000, 300000, 1000000);
295}
296
David Reiss4f9efdb2010-10-06 17:10:33 +0000297/**
298 * Make sure flush() is fast when there is nothing to do.
299 *
300 * TFileTransport used to have a bug where flush() would wait for the fsync
301 * timeout to expire.
302 */
David Reiss109693c2010-10-06 17:10:42 +0000303BOOST_AUTO_TEST_CASE(test_noop_flush) {
David Reiss4f9efdb2010-10-06 17:10:33 +0000304 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
305 TFileTransport transport(f.getPath());
306
307 // Write something to start the writer thread.
308 uint8_t buf[] = "a";
309 transport.write(buf, 1);
310
311 struct timeval start;
312 gettimeofday(&start, NULL);
313
314 for (unsigned int n = 0; n < 10; ++n) {
315 transport.flush();
316
317 struct timeval now;
318 gettimeofday(&now, NULL);
319
320 // Fail if at any point we've been running for longer than half a second.
321 // (With the buggy code, TFileTransport used to take 3 seconds per flush())
322 //
323 // Use a fatal fail so we break out early, rather than continuing to make
324 // many more slow flush() calls.
325 int delta = time_diff(&start, &now);
Roger Meier09cc5e72014-01-15 10:13:18 +0100326 BOOST_WARN_LT(delta, 2000000);
David Reiss4f9efdb2010-10-06 17:10:33 +0000327 }
328}
329
David Reiss709b69f2010-10-06 17:10:30 +0000330/**************************************************************************
331 * General Initialization
332 **************************************************************************/
333
334void print_usage(FILE* f, const char* argv0) {
335 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
336 fprintf(f, "Options:\n");
337 fprintf(f, " --tmp-dir=DIR, -t DIR\n");
338 fprintf(f, " --help\n");
339}
340
341void parse_args(int argc, char* argv[]) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100342 struct option long_opts[]
343 = {{"help", false, NULL, 'h'}, {"tmp-dir", true, NULL, 't'}, {NULL, 0, NULL, 0}};
David Reiss709b69f2010-10-06 17:10:30 +0000344
345 while (true) {
346 optopt = 1;
347 int optchar = getopt_long(argc, argv, "ht:", long_opts, NULL);
348 if (optchar == -1) {
349 break;
350 }
351
352 switch (optchar) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100353 case 't':
354 tmp_dir = optarg;
355 break;
356 case 'h':
357 print_usage(stdout, argv[0]);
358 exit(0);
359 case '?':
360 exit(1);
361 default:
362 // Only happens if someone adds another option to the optarg string,
363 // but doesn't update the switch statement to handle it.
364 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
365 exit(1);
David Reiss709b69f2010-10-06 17:10:30 +0000366 }
367 }
368}
369
370boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100371 boost::unit_test::framework::master_test_suite().p_name.value = "TFileTransportTest";
David Reiss109693c2010-10-06 17:10:42 +0000372
David Reiss709b69f2010-10-06 17:10:30 +0000373 // Parse arguments
374 parse_args(argc, argv);
David Reiss109693c2010-10-06 17:10:42 +0000375 return NULL;
David Reiss709b69f2010-10-06 17:10:30 +0000376}