1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5
6 #include "port/port.h"
7 #include "port/stack_trace.h"
8 #include "rocksdb/file_system.h"
9 #include "file/random_access_file_reader.h"
10 #include "test_util/testharness.h"
11 #include "test_util/testutil.h"
12
13 namespace ROCKSDB_NAMESPACE {
14
15 class RandomAccessFileReaderTest : public testing::Test {
16 public:
SetUp()17 void SetUp() override {
18 #ifdef OS_LINUX
19 // TEST_TMPDIR may be set to /dev/shm in Makefile,
20 // but /dev/shm does not support direct IO.
21 // The default TEST_TMPDIR is under /tmp, but /tmp might also be a tmpfs
22 // which does not support direct IO neither.
23 unsetenv("TEST_TMPDIR");
24 char* tmpdir = getenv("DISK_TEMP_DIR");
25 if (tmpdir == nullptr) {
26 tmpdir = getenv("HOME");
27 }
28 if (tmpdir != nullptr) {
29 setenv("TEST_TMPDIR", tmpdir, 1);
30 }
31 #endif
32 env_ = Env::Default();
33 fs_ = FileSystem::Default();
34 test_dir_ = test::PerThreadDBPath("random_access_file_reader_test");
35 ASSERT_OK(fs_->CreateDir(test_dir_, IOOptions(), nullptr));
36 alignment_ = GetAlignment();
37 }
38
TearDown()39 void TearDown() override {
40 EXPECT_OK(test::DestroyDir(env_, test_dir_));
41 }
42
IsDirectIOSupported()43 bool IsDirectIOSupported() {
44 Write(".direct", "");
45 FileOptions opt;
46 opt.use_direct_reads = true;
47 std::unique_ptr<FSRandomAccessFile> f;
48 auto s = fs_->NewRandomAccessFile(Path(".direct"), opt, &f, nullptr);
49 return s.ok();
50 }
51
Write(const std::string & fname,const std::string & content)52 void Write(const std::string& fname, const std::string& content) {
53 std::unique_ptr<FSWritableFile> f;
54 ASSERT_OK(fs_->NewWritableFile(Path(fname), FileOptions(), &f, nullptr));
55 ASSERT_OK(f->Append(content, IOOptions(), nullptr));
56 ASSERT_OK(f->Close(IOOptions(), nullptr));
57 }
58
Read(const std::string & fname,const FileOptions & opts,std::unique_ptr<RandomAccessFileReader> * reader)59 void Read(const std::string& fname, const FileOptions& opts,
60 std::unique_ptr<RandomAccessFileReader>* reader) {
61 std::string fpath = Path(fname);
62 std::unique_ptr<FSRandomAccessFile> f;
63 ASSERT_OK(fs_->NewRandomAccessFile(fpath, opts, &f, nullptr));
64 (*reader).reset(new RandomAccessFileReader(std::move(f), fpath, env_));
65 }
66
AssertResult(const std::string & content,const std::vector<FSReadRequest> & reqs)67 void AssertResult(const std::string& content,
68 const std::vector<FSReadRequest>& reqs) {
69 for (const auto& r : reqs) {
70 ASSERT_OK(r.status);
71 ASSERT_EQ(r.len, r.result.size());
72 ASSERT_EQ(content.substr(r.offset, r.len), r.result.ToString());
73 }
74 }
75
alignment() const76 size_t alignment() const { return alignment_; }
77
78 private:
79 Env* env_;
80 std::shared_ptr<FileSystem> fs_;
81 std::string test_dir_;
82 size_t alignment_;
83
Path(const std::string & fname)84 std::string Path(const std::string& fname) {
85 return test_dir_ + "/" + fname;
86 }
87
GetAlignment()88 size_t GetAlignment() {
89 std::string f = "get_alignment";
90 Write(f, "");
91 std::unique_ptr<RandomAccessFileReader> r;
92 Read(f, FileOptions(), &r);
93 size_t alignment = r->file()->GetRequiredBufferAlignment();
94 EXPECT_OK(fs_->DeleteFile(Path(f), IOOptions(), nullptr));
95 return alignment;
96 }
97 };
98
TEST_F(RandomAccessFileReaderTest,MultiReadDirectIO)99 TEST_F(RandomAccessFileReaderTest, MultiReadDirectIO) {
100 if (!IsDirectIOSupported()) {
101 printf("Direct IO is not supported, skip this test\n");
102 return;
103 }
104
105 // Creates a file with 3 pages.
106 std::string fname = "multi-read-direct-io";
107 Random rand(0);
108 std::string content;
109 test::RandomString(&rand, 3 * static_cast<int>(alignment()), &content);
110 Write(fname, content);
111
112 FileOptions opts;
113 opts.use_direct_reads = true;
114 std::unique_ptr<RandomAccessFileReader> r;
115 Read(fname, opts, &r);
116 ASSERT_TRUE(r->use_direct_io());
117
118 {
119 // Reads 2 blocks in the 1st page.
120 // The results should be SharedSlices of the same underlying buffer.
121 //
122 // Illustration (each x is a 1/4 page)
123 // First page: xxxx
124 // 1st block: x
125 // 2nd block: xx
126 FSReadRequest r0;
127 r0.offset = 0;
128 r0.len = alignment() / 4;
129 r0.scratch = nullptr;
130
131 FSReadRequest r1;
132 r1.offset = alignment() / 2;
133 r1.len = alignment() / 2;
134 r1.scratch = nullptr;
135
136 std::vector<FSReadRequest> reqs;
137 reqs.push_back(std::move(r0));
138 reqs.push_back(std::move(r1));
139 AlignedBuf aligned_buf;
140 ASSERT_OK(r->MultiRead(reqs.data(), reqs.size(), &aligned_buf));
141
142 AssertResult(content, reqs);
143 }
144
145 {
146 // Reads 3 blocks:
147 // 1st block in the 1st page;
148 // 2nd block from the middle of the 1st page to the middle of the 2nd page;
149 // 3rd block in the 2nd page.
150 // The results should be SharedSlices of the same underlying buffer.
151 //
152 // Illustration (each x is a 1/4 page)
153 // 2 pages: xxxxxxxx
154 // 1st block: x
155 // 2nd block: xxxx
156 // 3rd block: x
157 FSReadRequest r0;
158 r0.offset = 0;
159 r0.len = alignment() / 4;
160 r0.scratch = nullptr;
161
162 FSReadRequest r1;
163 r1.offset = alignment() / 2;
164 r1.len = alignment();
165 r1.scratch = nullptr;
166
167 FSReadRequest r2;
168 r2.offset = 2 * alignment() - alignment() / 4;
169 r2.len = alignment() / 4;
170 r2.scratch = nullptr;
171
172 std::vector<FSReadRequest> reqs;
173 reqs.push_back(std::move(r0));
174 reqs.push_back(std::move(r1));
175 reqs.push_back(std::move(r2));
176 AlignedBuf aligned_buf;
177 ASSERT_OK(r->MultiRead(reqs.data(), reqs.size(), &aligned_buf));
178
179 AssertResult(content, reqs);
180 }
181
182 {
183 // Reads 3 blocks:
184 // 1st block in the middle of the 1st page;
185 // 2nd block in the middle of the 2nd page;
186 // 3rd block in the middle of the 3rd page.
187 // The results should be SharedSlices of the same underlying buffer.
188 //
189 // Illustration (each x is a 1/4 page)
190 // 3 pages: xxxxxxxxxxxx
191 // 1st block: xx
192 // 2nd block: xx
193 // 3rd block: xx
194 FSReadRequest r0;
195 r0.offset = alignment() / 4;
196 r0.len = alignment() / 2;
197 r0.scratch = nullptr;
198
199 FSReadRequest r1;
200 r1.offset = alignment() + alignment() / 4;
201 r1.len = alignment() / 2;
202 r1.scratch = nullptr;
203
204 FSReadRequest r2;
205 r2.offset = 2 * alignment() + alignment() / 4;
206 r2.len = alignment() / 2;
207 r2.scratch = nullptr;
208
209 std::vector<FSReadRequest> reqs;
210 reqs.push_back(std::move(r0));
211 reqs.push_back(std::move(r1));
212 reqs.push_back(std::move(r2));
213 AlignedBuf aligned_buf;
214 ASSERT_OK(r->MultiRead(reqs.data(), reqs.size(), &aligned_buf));
215
216 AssertResult(content, reqs);
217 }
218
219 {
220 // Reads 2 blocks:
221 // 1st block in the middle of the 1st page;
222 // 2nd block in the middle of the 3rd page.
223 // The results are two different buffers.
224 //
225 // Illustration (each x is a 1/4 page)
226 // 3 pages: xxxxxxxxxxxx
227 // 1st block: xx
228 // 2nd block: xx
229 FSReadRequest r0;
230 r0.offset = alignment() / 4;
231 r0.len = alignment() / 2;
232 r0.scratch = nullptr;
233
234 FSReadRequest r1;
235 r1.offset = 2 * alignment() + alignment() / 4;
236 r1.len = alignment() / 2;
237 r1.scratch = nullptr;
238
239 std::vector<FSReadRequest> reqs;
240 reqs.push_back(std::move(r0));
241 reqs.push_back(std::move(r1));
242 AlignedBuf aligned_buf;
243 ASSERT_OK(r->MultiRead(reqs.data(), reqs.size(), &aligned_buf));
244
245 AssertResult(content, reqs);
246 }
247 }
248
249 } // namespace ROCKSDB_NAMESPACE
250
main(int argc,char ** argv)251 int main(int argc, char** argv) {
252 ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
253 ::testing::InitGoogleTest(&argc, argv);
254 return RUN_ALL_TESTS();
255 }
256