blob: a75ec755bc91fda0edeb069a7a0bdb70b64ebf1f [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
23#include <sys/time.h>
24#include <getopt.h>
25#include <boost/test/unit_test.hpp>
26
27#include <transport/TFileTransport.h>
28
29using namespace apache::thrift::transport;
30
31/**************************************************************************
32 * Global state
33 **************************************************************************/
34
35static const char* tmp_dir = "/tmp";
36
37class FsyncLog;
38FsyncLog* fsync_log;
39
40
41/**************************************************************************
42 * Helper code
43 **************************************************************************/
44
45// Provide BOOST_CHECK_LT() and BOOST_CHECK_GT(), in case we're compiled
46// with an older version of boost
47#ifndef BOOST_CHECK_LT
48#define BOOST_CHECK_CMP(a, b, op) \
49 BOOST_CHECK_MESSAGE((a) op (b), \
50 "check " BOOST_STRINGIZE(a) " " BOOST_STRINGIZE(op) " " \
51 BOOST_STRINGIZE(b) " failed: " BOOST_STRINGIZE(a) "=" <<\
52 (a) << " " BOOST_STRINGIZE(b) "=" << (b))
53
54#define BOOST_CHECK_LT(a, b) BOOST_CHECK_CMP(a, b, <)
55#define BOOST_CHECK_GT(a, b) BOOST_CHECK_CMP(a, b, >)
56#endif // BOOST_CHECK_LT
57
58/**
59 * Class to record calls to fsync
60 */
61class FsyncLog {
62 public:
63 struct FsyncCall {
64 struct timeval time;
65 int fd;
66 };
67 typedef std::list<FsyncCall> CallList;
68
69 FsyncLog() {}
70
71 void fsync(int fd) {
72 FsyncCall call;
73 gettimeofday(&call.time, NULL);
74 calls_.push_back(call);
75 }
76
77 const CallList* getCalls() const {
78 return &calls_;
79 }
80
81 private:
82 CallList calls_;
83};
84
85/**
86 * Helper class to clean up temporary files
87 */
88class TempFile {
89 public:
90 TempFile(const char* directory, const char* prefix) {
91 size_t path_len = strlen(directory) + strlen(prefix) + 8;
92 path_ = new char[path_len];
93 snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix);
94
95 fd_ = mkstemp(path_);
96 if (fd_ < 0) {
97 throw apache::thrift::TException("mkstemp() failed");
98 }
99 }
100
101 ~TempFile() {
102 unlink();
103 close();
104 }
105
106 const char* getPath() const {
107 return path_;
108 }
109
110 int getFD() const {
111 return fd_;
112 }
113
114 void unlink() {
115 if (path_) {
116 ::unlink(path_);
117 delete[] path_;
118 path_ = NULL;
119 }
120 }
121
122 void close() {
123 if (fd_ < 0) {
124 return;
125 }
126
127 ::close(fd_);
128 fd_ = -1;
129 }
130
131 private:
132 char* path_;
133 int fd_;
134};
135
136// Use our own version of fsync() for testing.
137// This returns immediately, so timing in test_destructor() isn't affected by
138// waiting on the actual filesystem.
139extern "C"
140int fsync(int fd) {
141 if (fsync_log) {
142 fsync_log->fsync(fd);
143 }
144 return 0;
145}
146
147
148int time_diff(const struct timeval* t1, const struct timeval* t2) {
149 return (t2->tv_usec - t1->tv_usec) + (t2->tv_sec - t1->tv_sec) * 1000000;
150}
151
152
153/**************************************************************************
154 * Test cases
155 **************************************************************************/
156
157/**
158 * Make sure the TFileTransport destructor exits "quickly".
159 *
160 * Previous versions had a bug causing the writer thread not to exit
161 * right away.
162 *
163 * It's kind of lame that we just check to see how long the destructor takes in
164 * wall-clock time. This could result in false failures on slower systems, or
165 * on heavily loaded machines.
166 */
167void test_destructor() {
168 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
169
170 unsigned int const NUM_ITERATIONS = 1000;
171 int const MAX_DESTRUCTOR_USEC = 500;
172
173 for (unsigned int n = 0; n < NUM_ITERATIONS; ++n) {
174 ftruncate(f.getFD(), 0);
175
176 TFileTransport* transport = new TFileTransport(f.getPath());
177
178 // write something so that the writer thread gets started
179 transport->write(reinterpret_cast<const uint8_t*>("foo"), 3);
180
181 // Every other iteration, also call flush(), just in case that potentially
182 // has any effect on how the writer thread wakes up.
183 if (n & 0x1) {
184 transport->flush();
185 }
186
187 /*
188 * Time the call to the destructor
189 */
190 struct timeval start;
191 struct timeval end;
192
193 gettimeofday(&start, NULL);
194 delete transport;
195 gettimeofday(&end, NULL);
196
197 int delta = time_diff(&start, &end);
198 BOOST_CHECK_LT(delta, MAX_DESTRUCTOR_USEC);
199 }
200}
201
202/**
203 * Make sure setFlushMaxUs() is honored.
204 */
205void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us,
206 uint32_t test_us) {
207 // TFileTransport only calls fsync() if data has been written,
208 // so make sure the write interval is smaller than the flush interval.
209 BOOST_REQUIRE(write_us < flush_us);
210
211 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
212
213 // Record calls to fsync()
214 FsyncLog log;
215 fsync_log = &log;
216
217 TFileTransport* transport = new TFileTransport(f.getPath());
218 // Don't flush because of # of bytes written
219 transport->setFlushMaxBytes(0xffffffff);
220 uint8_t buf[] = "a";
221 uint32_t buflen = sizeof(buf);
222
223 // Flush every 500us
224 transport->setFlushMaxUs(flush_us);
225
226 // Add one entry to the fsync log, just to mark the start time
227 log.fsync(-1);
228
229 // Loop doing write(), sleep(), ...
230 uint32_t total_time = 0;
231 while (true) {
232 transport->write(buf, buflen);
233 if (total_time > test_us) {
234 break;
235 }
236 usleep(write_us);
237 total_time += write_us;
238 }
239
240 delete transport;
241
242 // Stop logging new fsync() calls
243 fsync_log = NULL;
244
245 // Examine the fsync() log
246 //
247 // TFileTransport uses pthread_cond_timedwait(), which only has millisecond
248 // resolution. In my testing, it normally wakes up about 1 millisecond late.
249 // However, sometimes it takes a bit longer. Allow 5ms leeway.
250 int max_allowed_delta = flush_us + 5000;
251
252 const FsyncLog::CallList* calls = log.getCalls();
253 // We added 1 fsync call above.
254 // Make sure TFileTransport called fsync at least once
255 BOOST_CHECK_GT(calls->size(), 1);
256
257 const struct timeval* prev_time = NULL;
258 for (FsyncLog::CallList::const_iterator it = calls->begin();
259 it != calls->end();
260 ++it) {
261 if (prev_time) {
262 int delta = time_diff(prev_time, &it->time);
263 BOOST_CHECK_LT(delta, max_allowed_delta);
264 }
265 prev_time = &it->time;
266 }
267}
268
269void test_flush_max_us1() {
270 // fsync every 10ms, write every 5ms, for 500ms
271 test_flush_max_us_impl(10000, 5000, 500000);
272}
273
274void test_flush_max_us2() {
275 // fsync every 50ms, write every 20ms, for 500ms
276 test_flush_max_us_impl(50000, 20000, 500000);
277}
278
279void test_flush_max_us3() {
280 // fsync every 400ms, write every 300ms, for 1s
281 test_flush_max_us_impl(400000, 300000, 1000000);
282}
283
284/**************************************************************************
285 * General Initialization
286 **************************************************************************/
287
288void print_usage(FILE* f, const char* argv0) {
289 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
290 fprintf(f, "Options:\n");
291 fprintf(f, " --tmp-dir=DIR, -t DIR\n");
292 fprintf(f, " --help\n");
293}
294
295void parse_args(int argc, char* argv[]) {
296 int seed;
297 int *seedptr = NULL;
298
299 struct option long_opts[] = {
300 { "help", false, NULL, 'h' },
301 { "tmp-dir", true, NULL, 't' },
302 { NULL, 0, NULL, 0 }
303 };
304
305 while (true) {
306 optopt = 1;
307 int optchar = getopt_long(argc, argv, "ht:", long_opts, NULL);
308 if (optchar == -1) {
309 break;
310 }
311
312 switch (optchar) {
313 case 't':
314 tmp_dir = optarg;
315 break;
316 case 'h':
317 print_usage(stdout, argv[0]);
318 exit(0);
319 case '?':
320 exit(1);
321 default:
322 // Only happens if someone adds another option to the optarg string,
323 // but doesn't update the switch statement to handle it.
324 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
325 exit(1);
326 }
327 }
328}
329
330boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
331 // Parse arguments
332 parse_args(argc, argv);
333
334 boost::unit_test::test_suite* suite =
335 BOOST_TEST_SUITE("TFileTransportTests");
336
337 suite->add(BOOST_TEST_CASE(test_destructor));
338 suite->add(BOOST_TEST_CASE(test_flush_max_us1));
339 suite->add(BOOST_TEST_CASE(test_flush_max_us2));
340 suite->add(BOOST_TEST_CASE(test_flush_max_us3));
341
342 return suite;
343}