blob: c116fdc1b79dd7692471f5fa66febd3826f289b7 [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
David Reiss709b69f2010-10-06 17:10:30 +000056/**
57 * Class to record calls to fsync
58 */
59class FsyncLog {
Konrad Grochowski16a23a62014-11-13 15:33:38 +010060public:
David Reiss709b69f2010-10-06 17:10:30 +000061 struct FsyncCall {
62 struct timeval time;
63 int fd;
64 };
65 typedef std::list<FsyncCall> CallList;
66
Sebastian Zenker042580f2019-01-29 15:48:12 +010067 FsyncLog() = default;
David Reiss709b69f2010-10-06 17:10:30 +000068
69 void fsync(int fd) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +010070 (void)fd;
David Reiss709b69f2010-10-06 17:10:30 +000071 FsyncCall call;
Sebastian Zenker042580f2019-01-29 15:48:12 +010072 THRIFT_GETTIMEOFDAY(&call.time, nullptr);
David Reiss709b69f2010-10-06 17:10:30 +000073 calls_.push_back(call);
74 }
75
Konrad Grochowski16a23a62014-11-13 15:33:38 +010076 const CallList* getCalls() const { return &calls_; }
David Reiss709b69f2010-10-06 17:10:30 +000077
Konrad Grochowski16a23a62014-11-13 15:33:38 +010078private:
David Reiss709b69f2010-10-06 17:10:30 +000079 CallList calls_;
80};
81
82/**
83 * Helper class to clean up temporary files
84 */
85class TempFile {
Konrad Grochowski16a23a62014-11-13 15:33:38 +010086public:
David Reiss709b69f2010-10-06 17:10:30 +000087 TempFile(const char* directory, const char* prefix) {
Antonio Di Monaco796667b2016-01-04 23:05:19 +010088 #ifdef __MINGW32__
James E. King, III07f59972017-03-10 06:18:33 -050089 ((void)directory);
Antonio Di Monaco796667b2016-01-04 23:05:19 +010090 size_t path_len = strlen(prefix) + 8;
91 path_ = new char[path_len];
92 snprintf(path_, path_len, "%sXXXXXX", prefix);
93 if (_mktemp_s(path_,path_len) == 0) {
94 fd_ = open(path_,O_CREAT | O_RDWR | O_BINARY,S_IREAD | S_IWRITE);
95 if (fd_ < 0) {
96 throw apache::thrift::TException("_mktemp_s() failed");
97 }
98 } else {
99 throw apache::thrift::TException("_mktemp_s() failed");
100 }
101 #else
David Reiss709b69f2010-10-06 17:10:30 +0000102 size_t path_len = strlen(directory) + strlen(prefix) + 8;
103 path_ = new char[path_len];
104 snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix);
105
106 fd_ = mkstemp(path_);
107 if (fd_ < 0) {
108 throw apache::thrift::TException("mkstemp() failed");
109 }
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100110 #endif
David Reiss709b69f2010-10-06 17:10:30 +0000111 }
112
113 ~TempFile() {
114 unlink();
115 close();
116 }
117
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100118 const char* getPath() const { return path_; }
David Reiss709b69f2010-10-06 17:10:30 +0000119
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100120 int getFD() const { return fd_; }
David Reiss709b69f2010-10-06 17:10:30 +0000121
122 void unlink() {
123 if (path_) {
124 ::unlink(path_);
125 delete[] path_;
Sebastian Zenker042580f2019-01-29 15:48:12 +0100126 path_ = nullptr;
David Reiss709b69f2010-10-06 17:10:30 +0000127 }
128 }
129
130 void close() {
131 if (fd_ < 0) {
132 return;
133 }
134
135 ::close(fd_);
136 fd_ = -1;
137 }
138
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100139private:
David Reiss709b69f2010-10-06 17:10:30 +0000140 char* path_;
141 int fd_;
142};
143
144// Use our own version of fsync() for testing.
145// This returns immediately, so timing in test_destructor() isn't affected by
146// waiting on the actual filesystem.
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100147extern "C" int fsync(int fd) {
David Reiss709b69f2010-10-06 17:10:30 +0000148 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
David Reiss709b69f2010-10-06 17:10:30 +0000158/**************************************************************************
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) {
James E. King, III36200902016-10-05 14:47:18 -0400179 BOOST_CHECK_EQUAL(0, ftruncate(f.getFD(), 0));
David Reiss709b69f2010-10-06 17:10:30 +0000180
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
Sebastian Zenker042580f2019-01-29 15:48:12 +0100198 THRIFT_GETTIMEOFDAY(&start, nullptr);
David Reiss709b69f2010-10-06 17:10:30 +0000199 delete transport;
Sebastian Zenker042580f2019-01-29 15:48:12 +0100200 THRIFT_GETTIMEOFDAY(&end, nullptr);
David Reiss709b69f2010-10-06 17:10:30 +0000201
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.
James E. King IIIf5f430d2018-06-08 03:37:55 +0000207 BOOST_WARN( 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 */
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100224void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us, uint32_t test_us) {
David Reiss709b69f2010-10-06 17:10:30 +0000225 // TFileTransport only calls fsync() if data has been written,
226 // so make sure the write interval is smaller than the flush interval.
Roger Meier09cc5e72014-01-15 10:13:18 +0100227 BOOST_WARN(write_us < flush_us);
David Reiss709b69f2010-10-06 17:10:30 +0000228
229 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
230
231 // Record calls to fsync()
232 FsyncLog log;
233 fsync_log = &log;
234
235 TFileTransport* transport = new TFileTransport(f.getPath());
236 // Don't flush because of # of bytes written
237 transport->setFlushMaxBytes(0xffffffff);
238 uint8_t buf[] = "a";
239 uint32_t buflen = sizeof(buf);
240
David Reiss41993772010-10-06 17:10:31 +0000241 // Set the flush interval
David Reiss709b69f2010-10-06 17:10:30 +0000242 transport->setFlushMaxUs(flush_us);
243
David Reiss41993772010-10-06 17:10:31 +0000244 // Make one call to write, to start the writer thread now.
245 // (If we just let the thread get created during our test loop,
246 // the thread creation sometimes takes long enough to make the first
247 // fsync interval fail the check.)
248 transport->write(buf, buflen);
249
David Reiss709b69f2010-10-06 17:10:30 +0000250 // Add one entry to the fsync log, just to mark the start time
251 log.fsync(-1);
252
253 // Loop doing write(), sleep(), ...
254 uint32_t total_time = 0;
255 while (true) {
256 transport->write(buf, buflen);
257 if (total_time > test_us) {
258 break;
259 }
260 usleep(write_us);
261 total_time += write_us;
262 }
263
264 delete transport;
265
266 // Stop logging new fsync() calls
Sebastian Zenker042580f2019-01-29 15:48:12 +0100267 fsync_log = nullptr;
David Reiss709b69f2010-10-06 17:10:30 +0000268
269 // Examine the fsync() log
270 //
271 // TFileTransport uses pthread_cond_timedwait(), which only has millisecond
272 // resolution. In my testing, it normally wakes up about 1 millisecond late.
273 // However, sometimes it takes a bit longer. Allow 5ms leeway.
274 int max_allowed_delta = flush_us + 5000;
275
276 const FsyncLog::CallList* calls = log.getCalls();
277 // We added 1 fsync call above.
278 // Make sure TFileTransport called fsync at least once
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100279 BOOST_WARN_GE(calls->size(), static_cast<FsyncLog::CallList::size_type>(1));
David Reiss709b69f2010-10-06 17:10:30 +0000280
Sebastian Zenker042580f2019-01-29 15:48:12 +0100281 const struct timeval* prev_time = nullptr;
cyy64750162019-02-08 13:40:59 +0800282 for (const auto & call : *calls) {
David Reiss709b69f2010-10-06 17:10:30 +0000283 if (prev_time) {
cyy64750162019-02-08 13:40:59 +0800284 int delta = time_diff(prev_time, &call.time);
James E. King IIIf5f430d2018-06-08 03:37:55 +0000285 BOOST_WARN( delta < max_allowed_delta );
David Reiss709b69f2010-10-06 17:10:30 +0000286 }
cyy64750162019-02-08 13:40:59 +0800287 prev_time = &call.time;
David Reiss709b69f2010-10-06 17:10:30 +0000288 }
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;
Sebastian Zenker042580f2019-01-29 15:48:12 +0100321 THRIFT_GETTIMEOFDAY(&start, nullptr);
David Reiss4f9efdb2010-10-06 17:10:33 +0000322
323 for (unsigned int n = 0; n < 10; ++n) {
324 transport.flush();
325
326 struct timeval now;
Sebastian Zenker042580f2019-01-29 15:48:12 +0100327 THRIFT_GETTIMEOFDAY(&now, nullptr);
David Reiss4f9efdb2010-10-06 17:10:33 +0000328
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);
James E. King IIIf5f430d2018-06-08 03:37:55 +0000335 BOOST_WARN( delta < 2000000 );
David Reiss4f9efdb2010-10-06 17:10:33 +0000336 }
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[]) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100351 struct option long_opts[]
Sebastian Zenker042580f2019-01-29 15:48:12 +0100352 = {{"help", false, nullptr, 'h'}, {"tmp-dir", true, nullptr, 't'}, {nullptr, 0, nullptr, 0}};
David Reiss709b69f2010-10-06 17:10:30 +0000353
354 while (true) {
355 optopt = 1;
Sebastian Zenker042580f2019-01-29 15:48:12 +0100356 int optchar = getopt_long(argc, argv, "ht:", long_opts, nullptr);
David Reiss709b69f2010-10-06 17:10:30 +0000357 if (optchar == -1) {
358 break;
359 }
360
361 switch (optchar) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100362 case 't':
363 tmp_dir = optarg;
364 break;
365 case 'h':
366 print_usage(stdout, argv[0]);
367 exit(0);
368 case '?':
369 exit(1);
370 default:
371 // Only happens if someone adds another option to the optarg string,
372 // but doesn't update the switch statement to handle it.
373 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
374 exit(1);
David Reiss709b69f2010-10-06 17:10:30 +0000375 }
376 }
377}
378
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100379#ifdef BOOST_TEST_DYN_LINK
380static int myArgc = 0;
Sebastian Zenker042580f2019-01-29 15:48:12 +0100381static char **myArgv = nullptr;
James E. King, III36200902016-10-05 14:47:18 -0400382
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100383bool init_unit_test_suite() {
384 boost::unit_test::framework::master_test_suite().p_name.value = "TFileTransportTest";
James E. King, III36200902016-10-05 14:47:18 -0400385
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100386 // Parse arguments
387 parse_args(myArgc,myArgv);
388 return true;
389}
James E. King, III36200902016-10-05 14:47:18 -0400390
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100391int main( int argc, char* argv[] ) {
392 myArgc = argc;
393 myArgv = argv;
394 return ::boost::unit_test::unit_test_main(&init_unit_test_suite,argc,argv);
395}
James E. King, III36200902016-10-05 14:47:18 -0400396#else
David Reiss709b69f2010-10-06 17:10:30 +0000397boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
Konrad Grochowski16a23a62014-11-13 15:33:38 +0100398 boost::unit_test::framework::master_test_suite().p_name.value = "TFileTransportTest";
David Reiss109693c2010-10-06 17:10:42 +0000399
David Reiss709b69f2010-10-06 17:10:30 +0000400 // Parse arguments
401 parse_args(argc, argv);
zeshuai00726681fb2020-06-03 17:24:38 +0800402 return nullptr;
David Reiss709b69f2010-10-06 17:10:30 +0000403}
James E. King, III36200902016-10-05 14:47:18 -0400404#endif