blob: 700a1ac3c39dca5222deeef153029f413cd11c1a [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
Antonio Di Monaco796667b2016-01-04 23:05:19 +010033#ifdef __MINGW32__
34 #include <io.h>
35 #include <unistd.h>
36 #include <sys/types.h>
37 #include <fcntl.h>
38 #include <sys\stat.h>
39#endif
40
David Reiss709b69f2010-10-06 17:10:30 +000041using namespace apache::thrift::transport;
42
43/**************************************************************************
44 * Global state
45 **************************************************************************/
46
47static const char* tmp_dir = "/tmp";
48
49class FsyncLog;
50FsyncLog* fsync_log;
51
David Reiss709b69f2010-10-06 17:10:30 +000052/**************************************************************************
53 * Helper code
54 **************************************************************************/
55
Roger Meier09cc5e72014-01-15 10:13:18 +010056// Provide BOOST_WARN_LT() and BOOST_WARN_GT(), in case we're compiled
David Reiss709b69f2010-10-06 17:10:30 +000057// with an older version of boost
Roger Meier09cc5e72014-01-15 10:13:18 +010058#ifndef BOOST_WARN_LT
Konrad Grochowski16a23a62014-11-13 15:33:38 +010059#define BOOST_WARN_CMP(a, b, op, check_fn) \
60 check_fn((a)op(b), \
61 "check " BOOST_STRINGIZE(a) " " BOOST_STRINGIZE(op) " " BOOST_STRINGIZE( \
62 b) " failed: " BOOST_STRINGIZE(a) "=" \
63 << (a) << " " BOOST_STRINGIZE(b) "=" << (b))
David Reiss709b69f2010-10-06 17:10:30 +000064
Roger Meier09cc5e72014-01-15 10:13:18 +010065#define BOOST_WARN_LT(a, b) BOOST_WARN_CMP(a, b, <, BOOST_WARN_MESSAGE)
66#define BOOST_WARN_GT(a, b) BOOST_WARN_CMP(a, b, >, BOOST_WARN_MESSAGE)
67#define BOOST_WARN_LT(a, b) BOOST_WARN_CMP(a, b, <, BOOST_WARN_MESSAGE)
68#endif // BOOST_WARN_LT
David Reiss709b69f2010-10-06 17:10:30 +000069
70/**
71 * Class to record calls to fsync
72 */
73class FsyncLog {
Konrad Grochowski16a23a62014-11-13 15:33:38 +010074public:
David Reiss709b69f2010-10-06 17:10:30 +000075 struct FsyncCall {
76 struct timeval time;
77 int fd;
78 };
79 typedef std::list<FsyncCall> CallList;
80
81 FsyncLog() {}
82
83 void fsync(int fd) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +010084 (void)fd;
David Reiss709b69f2010-10-06 17:10:30 +000085 FsyncCall call;
James E. King, III07f59972017-03-10 06:18:33 -050086 THRIFT_GETTIMEOFDAY(&call.time, NULL);
David Reiss709b69f2010-10-06 17:10:30 +000087 calls_.push_back(call);
88 }
89
Konrad Grochowski16a23a62014-11-13 15:33:38 +010090 const CallList* getCalls() const { return &calls_; }
David Reiss709b69f2010-10-06 17:10:30 +000091
Konrad Grochowski16a23a62014-11-13 15:33:38 +010092private:
David Reiss709b69f2010-10-06 17:10:30 +000093 CallList calls_;
94};
95
96/**
97 * Helper class to clean up temporary files
98 */
99class TempFile {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100100public:
David Reiss709b69f2010-10-06 17:10:30 +0000101 TempFile(const char* directory, const char* prefix) {
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100102 #ifdef __MINGW32__
James E. King, III07f59972017-03-10 06:18:33 -0500103 ((void)directory);
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100104 size_t path_len = strlen(prefix) + 8;
105 path_ = new char[path_len];
106 snprintf(path_, path_len, "%sXXXXXX", prefix);
107 if (_mktemp_s(path_,path_len) == 0) {
108 fd_ = open(path_,O_CREAT | O_RDWR | O_BINARY,S_IREAD | S_IWRITE);
109 if (fd_ < 0) {
110 throw apache::thrift::TException("_mktemp_s() failed");
111 }
112 } else {
113 throw apache::thrift::TException("_mktemp_s() failed");
114 }
115 #else
David Reiss709b69f2010-10-06 17:10:30 +0000116 size_t path_len = strlen(directory) + strlen(prefix) + 8;
117 path_ = new char[path_len];
118 snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix);
119
120 fd_ = mkstemp(path_);
121 if (fd_ < 0) {
122 throw apache::thrift::TException("mkstemp() failed");
123 }
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100124 #endif
David Reiss709b69f2010-10-06 17:10:30 +0000125 }
126
127 ~TempFile() {
128 unlink();
129 close();
130 }
131
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100132 const char* getPath() const { return path_; }
David Reiss709b69f2010-10-06 17:10:30 +0000133
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100134 int getFD() const { return fd_; }
David Reiss709b69f2010-10-06 17:10:30 +0000135
136 void unlink() {
137 if (path_) {
138 ::unlink(path_);
139 delete[] path_;
140 path_ = NULL;
141 }
142 }
143
144 void close() {
145 if (fd_ < 0) {
146 return;
147 }
148
149 ::close(fd_);
150 fd_ = -1;
151 }
152
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100153private:
David Reiss709b69f2010-10-06 17:10:30 +0000154 char* path_;
155 int fd_;
156};
157
158// Use our own version of fsync() for testing.
159// This returns immediately, so timing in test_destructor() isn't affected by
160// waiting on the actual filesystem.
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100161extern "C" int fsync(int fd) {
David Reiss709b69f2010-10-06 17:10:30 +0000162 if (fsync_log) {
163 fsync_log->fsync(fd);
164 }
165 return 0;
166}
167
David Reiss709b69f2010-10-06 17:10:30 +0000168int time_diff(const struct timeval* t1, const struct timeval* t2) {
169 return (t2->tv_usec - t1->tv_usec) + (t2->tv_sec - t1->tv_sec) * 1000000;
170}
171
David Reiss709b69f2010-10-06 17:10:30 +0000172/**************************************************************************
173 * Test cases
174 **************************************************************************/
175
176/**
177 * Make sure the TFileTransport destructor exits "quickly".
178 *
179 * Previous versions had a bug causing the writer thread not to exit
180 * right away.
181 *
182 * It's kind of lame that we just check to see how long the destructor takes in
183 * wall-clock time. This could result in false failures on slower systems, or
184 * on heavily loaded machines.
185 */
David Reiss109693c2010-10-06 17:10:42 +0000186BOOST_AUTO_TEST_CASE(test_destructor) {
David Reiss709b69f2010-10-06 17:10:30 +0000187 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
188
189 unsigned int const NUM_ITERATIONS = 1000;
David Reiss709b69f2010-10-06 17:10:30 +0000190
Roger Meier085a3e72010-10-08 21:23:35 +0000191 unsigned int num_over = 0;
David Reiss709b69f2010-10-06 17:10:30 +0000192 for (unsigned int n = 0; n < NUM_ITERATIONS; ++n) {
James E. King, III36200902016-10-05 14:47:18 -0400193 BOOST_CHECK_EQUAL(0, ftruncate(f.getFD(), 0));
David Reiss709b69f2010-10-06 17:10:30 +0000194
195 TFileTransport* transport = new TFileTransport(f.getPath());
196
197 // write something so that the writer thread gets started
198 transport->write(reinterpret_cast<const uint8_t*>("foo"), 3);
199
200 // Every other iteration, also call flush(), just in case that potentially
201 // has any effect on how the writer thread wakes up.
202 if (n & 0x1) {
203 transport->flush();
204 }
205
206 /*
207 * Time the call to the destructor
208 */
209 struct timeval start;
210 struct timeval end;
211
James E. King, III07f59972017-03-10 06:18:33 -0500212 THRIFT_GETTIMEOFDAY(&start, NULL);
David Reiss709b69f2010-10-06 17:10:30 +0000213 delete transport;
James E. King, III07f59972017-03-10 06:18:33 -0500214 THRIFT_GETTIMEOFDAY(&end, NULL);
David Reiss709b69f2010-10-06 17:10:30 +0000215
216 int delta = time_diff(&start, &end);
David Reiss41993772010-10-06 17:10:31 +0000217
Roger Meier1ebeffb2011-06-06 18:00:03 +0000218 // If any attempt takes more than 500ms, treat that as a failure.
David Reiss41993772010-10-06 17:10:31 +0000219 // Treat this as a fatal failure, so we'll return now instead of
220 // looping over a very slow operation.
Roger Meier09cc5e72014-01-15 10:13:18 +0100221 BOOST_WARN_LT(delta, 500000);
David Reiss41993772010-10-06 17:10:31 +0000222
Roger Meier1ebeffb2011-06-06 18:00:03 +0000223 // Normally, it takes less than 100ms on my dev box.
David Reiss41993772010-10-06 17:10:31 +0000224 // However, if the box is heavily loaded, some of the test runs
225 // take longer, since we're just waiting for our turn on the CPU.
Roger Meier1ebeffb2011-06-06 18:00:03 +0000226 if (delta > 100000) {
Roger Meier085a3e72010-10-08 21:23:35 +0000227 ++num_over;
David Reiss41993772010-10-06 17:10:31 +0000228 }
David Reiss709b69f2010-10-06 17:10:30 +0000229 }
David Reiss41993772010-10-06 17:10:31 +0000230
Roger Meier085a3e72010-10-08 21:23:35 +0000231 // Make sure fewer than 10% of the runs took longer than 1000us
Roger Meier09cc5e72014-01-15 10:13:18 +0100232 BOOST_WARN(num_over < (NUM_ITERATIONS / 10));
David Reiss709b69f2010-10-06 17:10:30 +0000233}
234
235/**
236 * Make sure setFlushMaxUs() is honored.
237 */
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100238void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us, uint32_t test_us) {
David Reiss709b69f2010-10-06 17:10:30 +0000239 // TFileTransport only calls fsync() if data has been written,
240 // so make sure the write interval is smaller than the flush interval.
Roger Meier09cc5e72014-01-15 10:13:18 +0100241 BOOST_WARN(write_us < flush_us);
David Reiss709b69f2010-10-06 17:10:30 +0000242
243 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
244
245 // Record calls to fsync()
246 FsyncLog log;
247 fsync_log = &log;
248
249 TFileTransport* transport = new TFileTransport(f.getPath());
250 // Don't flush because of # of bytes written
251 transport->setFlushMaxBytes(0xffffffff);
252 uint8_t buf[] = "a";
253 uint32_t buflen = sizeof(buf);
254
David Reiss41993772010-10-06 17:10:31 +0000255 // Set the flush interval
David Reiss709b69f2010-10-06 17:10:30 +0000256 transport->setFlushMaxUs(flush_us);
257
David Reiss41993772010-10-06 17:10:31 +0000258 // Make one call to write, to start the writer thread now.
259 // (If we just let the thread get created during our test loop,
260 // the thread creation sometimes takes long enough to make the first
261 // fsync interval fail the check.)
262 transport->write(buf, buflen);
263
David Reiss709b69f2010-10-06 17:10:30 +0000264 // Add one entry to the fsync log, just to mark the start time
265 log.fsync(-1);
266
267 // Loop doing write(), sleep(), ...
268 uint32_t total_time = 0;
269 while (true) {
270 transport->write(buf, buflen);
271 if (total_time > test_us) {
272 break;
273 }
274 usleep(write_us);
275 total_time += write_us;
276 }
277
278 delete transport;
279
280 // Stop logging new fsync() calls
281 fsync_log = NULL;
282
283 // Examine the fsync() log
284 //
285 // TFileTransport uses pthread_cond_timedwait(), which only has millisecond
286 // resolution. In my testing, it normally wakes up about 1 millisecond late.
287 // However, sometimes it takes a bit longer. Allow 5ms leeway.
288 int max_allowed_delta = flush_us + 5000;
289
290 const FsyncLog::CallList* calls = log.getCalls();
291 // We added 1 fsync call above.
292 // Make sure TFileTransport called fsync at least once
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100293 BOOST_WARN_GE(calls->size(), static_cast<FsyncLog::CallList::size_type>(1));
David Reiss709b69f2010-10-06 17:10:30 +0000294
295 const struct timeval* prev_time = NULL;
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100296 for (FsyncLog::CallList::const_iterator it = calls->begin(); it != calls->end(); ++it) {
David Reiss709b69f2010-10-06 17:10:30 +0000297 if (prev_time) {
298 int delta = time_diff(prev_time, &it->time);
Roger Meier09cc5e72014-01-15 10:13:18 +0100299 BOOST_WARN_LT(delta, max_allowed_delta);
David Reiss709b69f2010-10-06 17:10:30 +0000300 }
301 prev_time = &it->time;
302 }
303}
304
David Reiss109693c2010-10-06 17:10:42 +0000305BOOST_AUTO_TEST_CASE(test_flush_max_us1) {
David Reiss709b69f2010-10-06 17:10:30 +0000306 // fsync every 10ms, write every 5ms, for 500ms
307 test_flush_max_us_impl(10000, 5000, 500000);
308}
309
David Reiss109693c2010-10-06 17:10:42 +0000310BOOST_AUTO_TEST_CASE(test_flush_max_us2) {
David Reiss709b69f2010-10-06 17:10:30 +0000311 // fsync every 50ms, write every 20ms, for 500ms
312 test_flush_max_us_impl(50000, 20000, 500000);
313}
314
David Reiss109693c2010-10-06 17:10:42 +0000315BOOST_AUTO_TEST_CASE(test_flush_max_us3) {
David Reiss709b69f2010-10-06 17:10:30 +0000316 // fsync every 400ms, write every 300ms, for 1s
317 test_flush_max_us_impl(400000, 300000, 1000000);
318}
319
David Reiss4f9efdb2010-10-06 17:10:33 +0000320/**
321 * Make sure flush() is fast when there is nothing to do.
322 *
323 * TFileTransport used to have a bug where flush() would wait for the fsync
324 * timeout to expire.
325 */
David Reiss109693c2010-10-06 17:10:42 +0000326BOOST_AUTO_TEST_CASE(test_noop_flush) {
David Reiss4f9efdb2010-10-06 17:10:33 +0000327 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
328 TFileTransport transport(f.getPath());
329
330 // Write something to start the writer thread.
331 uint8_t buf[] = "a";
332 transport.write(buf, 1);
333
334 struct timeval start;
James E. King, III07f59972017-03-10 06:18:33 -0500335 THRIFT_GETTIMEOFDAY(&start, NULL);
David Reiss4f9efdb2010-10-06 17:10:33 +0000336
337 for (unsigned int n = 0; n < 10; ++n) {
338 transport.flush();
339
340 struct timeval now;
James E. King, III07f59972017-03-10 06:18:33 -0500341 THRIFT_GETTIMEOFDAY(&now, NULL);
David Reiss4f9efdb2010-10-06 17:10:33 +0000342
343 // Fail if at any point we've been running for longer than half a second.
344 // (With the buggy code, TFileTransport used to take 3 seconds per flush())
345 //
346 // Use a fatal fail so we break out early, rather than continuing to make
347 // many more slow flush() calls.
348 int delta = time_diff(&start, &now);
Roger Meier09cc5e72014-01-15 10:13:18 +0100349 BOOST_WARN_LT(delta, 2000000);
David Reiss4f9efdb2010-10-06 17:10:33 +0000350 }
351}
352
David Reiss709b69f2010-10-06 17:10:30 +0000353/**************************************************************************
354 * General Initialization
355 **************************************************************************/
356
357void print_usage(FILE* f, const char* argv0) {
358 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
359 fprintf(f, "Options:\n");
360 fprintf(f, " --tmp-dir=DIR, -t DIR\n");
361 fprintf(f, " --help\n");
362}
363
364void parse_args(int argc, char* argv[]) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100365 struct option long_opts[]
366 = {{"help", false, NULL, 'h'}, {"tmp-dir", true, NULL, 't'}, {NULL, 0, NULL, 0}};
David Reiss709b69f2010-10-06 17:10:30 +0000367
368 while (true) {
369 optopt = 1;
370 int optchar = getopt_long(argc, argv, "ht:", long_opts, NULL);
371 if (optchar == -1) {
372 break;
373 }
374
375 switch (optchar) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100376 case 't':
377 tmp_dir = optarg;
378 break;
379 case 'h':
380 print_usage(stdout, argv[0]);
381 exit(0);
382 case '?':
383 exit(1);
384 default:
385 // Only happens if someone adds another option to the optarg string,
386 // but doesn't update the switch statement to handle it.
387 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
388 exit(1);
David Reiss709b69f2010-10-06 17:10:30 +0000389 }
390 }
391}
392
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100393#ifdef BOOST_TEST_DYN_LINK
394static int myArgc = 0;
395static char **myArgv = NULL;
James E. King, III36200902016-10-05 14:47:18 -0400396
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100397bool init_unit_test_suite() {
398 boost::unit_test::framework::master_test_suite().p_name.value = "TFileTransportTest";
James E. King, III36200902016-10-05 14:47:18 -0400399
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100400 // Parse arguments
401 parse_args(myArgc,myArgv);
402 return true;
403}
James E. King, III36200902016-10-05 14:47:18 -0400404
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100405int main( int argc, char* argv[] ) {
406 myArgc = argc;
407 myArgv = argv;
408 return ::boost::unit_test::unit_test_main(&init_unit_test_suite,argc,argv);
409}
James E. King, III36200902016-10-05 14:47:18 -0400410#else
David Reiss709b69f2010-10-06 17:10:30 +0000411boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100412 boost::unit_test::framework::master_test_suite().p_name.value = "TFileTransportTest";
David Reiss109693c2010-10-06 17:10:42 +0000413
David Reiss709b69f2010-10-06 17:10:30 +0000414 // Parse arguments
415 parse_args(argc, argv);
David Reiss109693c2010-10-06 17:10:42 +0000416 return NULL;
David Reiss709b69f2010-10-06 17:10:30 +0000417}
James E. King, III36200902016-10-05 14:47:18 -0400418#endif