Skip to content

Commit 646c358

Browse files
committed
Limit the number of read-only files the POSIX Env will have open.
Background compaction can create an unbounded number of leveldb::RandomAccessFile instances. On 64-bit systems mmap is used and file descriptors are only used beyond a certain number of mmap's. 32-bit systems to not use mmap at all. leveldb::RandomAccessFile does not observe Options.max_open_files so compaction could exhaust the file descriptor limit. This change uses getrlimit to determine the maximum number of open files and limits RandomAccessFile to approximately 20% of that value. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=143505556
1 parent a2fb086 commit 646c358

File tree

3 files changed

+204
-58
lines changed

3 files changed

+204
-58
lines changed

util/env_posix.cc

Lines changed: 134 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <stdlib.h>
1111
#include <string.h>
1212
#include <sys/mman.h>
13+
#include <sys/resource.h>
1314
#include <sys/stat.h>
1415
#include <sys/time.h>
1516
#include <sys/types.h>
@@ -23,15 +24,70 @@
2324
#include "util/logging.h"
2425
#include "util/mutexlock.h"
2526
#include "util/posix_logger.h"
27+
#include "util/env_posix_test_helper.h"
2628

2729
namespace leveldb {
2830

2931
namespace {
3032

33+
static int open_read_only_file_limit = -1;
34+
static int mmap_limit = -1;
35+
3136
static Status IOError(const std::string& context, int err_number) {
3237
return Status::IOError(context, strerror(err_number));
3338
}
3439

40+
// Helper class to limit resource usage to avoid exhaustion.
41+
// Currently used to limit read-only file descriptors and mmap file usage
42+
// so that we do not end up running out of file descriptors, virtual memory,
43+
// or running into kernel performance problems for very large databases.
44+
class Limiter {
45+
public:
46+
// Limit maximum number of resources to |n|.
47+
Limiter(intptr_t n) {
48+
SetAllowed(n);
49+
}
50+
51+
// If another resource is available, acquire it and return true.
52+
// Else return false.
53+
bool Acquire() {
54+
if (GetAllowed() <= 0) {
55+
return false;
56+
}
57+
MutexLock l(&mu_);
58+
intptr_t x = GetAllowed();
59+
if (x <= 0) {
60+
return false;
61+
} else {
62+
SetAllowed(x - 1);
63+
return true;
64+
}
65+
}
66+
67+
// Release a resource acquired by a previous call to Acquire() that returned
68+
// true.
69+
void Release() {
70+
MutexLock l(&mu_);
71+
SetAllowed(GetAllowed() + 1);
72+
}
73+
74+
private:
75+
port::Mutex mu_;
76+
port::AtomicPointer allowed_;
77+
78+
intptr_t GetAllowed() const {
79+
return reinterpret_cast<intptr_t>(allowed_.Acquire_Load());
80+
}
81+
82+
// REQUIRES: mu_ must be held
83+
void SetAllowed(intptr_t v) {
84+
allowed_.Release_Store(reinterpret_cast<void*>(v));
85+
}
86+
87+
Limiter(const Limiter&);
88+
void operator=(const Limiter&);
89+
};
90+
3591
class PosixSequentialFile: public SequentialFile {
3692
private:
3793
std::string filename_;
@@ -69,73 +125,51 @@ class PosixSequentialFile: public SequentialFile {
69125
class PosixRandomAccessFile: public RandomAccessFile {
70126
private:
71127
std::string filename_;
128+
bool temporary_fd_; // If true, fd_ is -1 and we open on every read.
72129
int fd_;
130+
Limiter* limiter_;
73131

74132
public:
75-
PosixRandomAccessFile(const std::string& fname, int fd)
76-
: filename_(fname), fd_(fd) { }
77-
virtual ~PosixRandomAccessFile() { close(fd_); }
133+
PosixRandomAccessFile(const std::string& fname, int fd, Limiter* limiter)
134+
: filename_(fname), fd_(fd), limiter_(limiter) {
135+
temporary_fd_ = !limiter->Acquire();
136+
if (temporary_fd_) {
137+
// Open file on every access.
138+
close(fd_);
139+
fd_ = -1;
140+
}
141+
}
142+
143+
virtual ~PosixRandomAccessFile() {
144+
if (!temporary_fd_) {
145+
close(fd_);
146+
limiter_->Release();
147+
}
148+
}
78149

79150
virtual Status Read(uint64_t offset, size_t n, Slice* result,
80151
char* scratch) const {
152+
int fd = fd_;
153+
if (temporary_fd_) {
154+
fd = open(filename_.c_str(), O_RDONLY);
155+
if (fd < 0) {
156+
return IOError(filename_, errno);
157+
}
158+
}
159+
81160
Status s;
82-
ssize_t r = pread(fd_, scratch, n, static_cast<off_t>(offset));
161+
ssize_t r = pread(fd, scratch, n, static_cast<off_t>(offset));
83162
*result = Slice(scratch, (r < 0) ? 0 : r);
84163
if (r < 0) {
85164
// An error: return a non-ok status
86165
s = IOError(filename_, errno);
87166
}
88-
return s;
89-
}
90-
};
91-
92-
// Helper class to limit mmap file usage so that we do not end up
93-
// running out virtual memory or running into kernel performance
94-
// problems for very large databases.
95-
class MmapLimiter {
96-
public:
97-
// Up to 1000 mmaps for 64-bit binaries; none for smaller pointer sizes.
98-
MmapLimiter() {
99-
SetAllowed(sizeof(void*) >= 8 ? 1000 : 0);
100-
}
101-
102-
// If another mmap slot is available, acquire it and return true.
103-
// Else return false.
104-
bool Acquire() {
105-
if (GetAllowed() <= 0) {
106-
return false;
107-
}
108-
MutexLock l(&mu_);
109-
intptr_t x = GetAllowed();
110-
if (x <= 0) {
111-
return false;
112-
} else {
113-
SetAllowed(x - 1);
114-
return true;
167+
if (temporary_fd_) {
168+
// Close the temporary file descriptor opened earlier.
169+
close(fd);
115170
}
171+
return s;
116172
}
117-
118-
// Release a slot acquired by a previous call to Acquire() that returned true.
119-
void Release() {
120-
MutexLock l(&mu_);
121-
SetAllowed(GetAllowed() + 1);
122-
}
123-
124-
private:
125-
port::Mutex mu_;
126-
port::AtomicPointer allowed_;
127-
128-
intptr_t GetAllowed() const {
129-
return reinterpret_cast<intptr_t>(allowed_.Acquire_Load());
130-
}
131-
132-
// REQUIRES: mu_ must be held
133-
void SetAllowed(intptr_t v) {
134-
allowed_.Release_Store(reinterpret_cast<void*>(v));
135-
}
136-
137-
MmapLimiter(const MmapLimiter&);
138-
void operator=(const MmapLimiter&);
139173
};
140174

141175
// mmap() based random-access
@@ -144,12 +178,12 @@ class PosixMmapReadableFile: public RandomAccessFile {
144178
std::string filename_;
145179
void* mmapped_region_;
146180
size_t length_;
147-
MmapLimiter* limiter_;
181+
Limiter* limiter_;
148182

149183
public:
150184
// base[0,length-1] contains the mmapped contents of the file.
151185
PosixMmapReadableFile(const std::string& fname, void* base, size_t length,
152-
MmapLimiter* limiter)
186+
Limiter* limiter)
153187
: filename_(fname), mmapped_region_(base), length_(length),
154188
limiter_(limiter) {
155189
}
@@ -332,7 +366,7 @@ class PosixEnv : public Env {
332366
mmap_limit_.Release();
333367
}
334368
} else {
335-
*result = new PosixRandomAccessFile(fname, fd);
369+
*result = new PosixRandomAccessFile(fname, fd, &fd_limit_);
336370
}
337371
return s;
338372
}
@@ -532,10 +566,42 @@ class PosixEnv : public Env {
532566
BGQueue queue_;
533567

534568
PosixLockTable locks_;
535-
MmapLimiter mmap_limit_;
569+
Limiter mmap_limit_;
570+
Limiter fd_limit_;
536571
};
537572

538-
PosixEnv::PosixEnv() : started_bgthread_(false) {
573+
// Return the maximum number of concurrent mmaps.
574+
static int MaxMmaps() {
575+
if (mmap_limit >= 0) {
576+
return mmap_limit;
577+
}
578+
// Up to 1000 mmaps for 64-bit binaries; none for smaller pointer sizes.
579+
mmap_limit = sizeof(void*) >= 8 ? 1000 : 0;
580+
return mmap_limit;
581+
}
582+
583+
// Return the maximum number of read-only files to keep open.
584+
static intptr_t MaxOpenFiles() {
585+
if (open_read_only_file_limit >= 0) {
586+
return open_read_only_file_limit;
587+
}
588+
struct rlimit rlim;
589+
if (getrlimit(RLIMIT_NOFILE, &rlim)) {
590+
// getrlimit failed, fallback to hard-coded default.
591+
open_read_only_file_limit = 50;
592+
} else if (rlim.rlim_cur == RLIM_INFINITY) {
593+
open_read_only_file_limit = std::numeric_limits<int>::max();
594+
} else {
595+
// Allow use of 20% of available file descriptors for read-only files.
596+
open_read_only_file_limit = rlim.rlim_cur / 5;
597+
}
598+
return open_read_only_file_limit;
599+
}
600+
601+
PosixEnv::PosixEnv()
602+
: started_bgthread_(false),
603+
mmap_limit_(MaxMmaps()),
604+
fd_limit_(MaxOpenFiles()) {
539605
PthreadCall("mutex_init", pthread_mutex_init(&mu_, NULL));
540606
PthreadCall("cvar_init", pthread_cond_init(&bgsignal_, NULL));
541607
}
@@ -610,6 +676,16 @@ static pthread_once_t once = PTHREAD_ONCE_INIT;
610676
static Env* default_env;
611677
static void InitDefaultEnv() { default_env = new PosixEnv; }
612678

679+
void EnvPosixTestHelper::SetReadOnlyFDLimit(int limit) {
680+
assert(default_env == NULL);
681+
open_read_only_file_limit = limit;
682+
}
683+
684+
void EnvPosixTestHelper::SetReadOnlyMMapLimit(int limit) {
685+
assert(default_env == NULL);
686+
mmap_limit = limit;
687+
}
688+
613689
Env* Env::Default() {
614690
pthread_once(&once, InitDefaultEnv);
615691
return default_env;

util/env_posix_test_helper.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2017 The LevelDB Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file. See the AUTHORS file for names of contributors.
4+
5+
#ifndef STORAGE_LEVELDB_UTIL_ENV_POSIX_TEST_HELPER_H_
6+
#define STORAGE_LEVELDB_UTIL_ENV_POSIX_TEST_HELPER_H_
7+
8+
namespace leveldb {
9+
10+
class EnvPosixTest;
11+
12+
// A helper for the POSIX Env to facilitate testing.
13+
class EnvPosixTestHelper {
14+
private:
15+
friend class EnvPosixTest;
16+
17+
// Set the maximum number of read-only files that will be opened.
18+
// Must be called before creating an Env.
19+
static void SetReadOnlyFDLimit(int limit);
20+
21+
// Set the maximum number of read-only files that will be mapped via mmap.
22+
// Must be called before creating an Env.
23+
static void SetReadOnlyMMapLimit(int limit);
24+
};
25+
26+
} // namespace leveldb
27+
28+
#endif // STORAGE_LEVELDB_UTIL_ENV_POSIX_TEST_HELPER_H_

util/env_test.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66

77
#include "port/port.h"
88
#include "util/testharness.h"
9+
#include "util/env_posix_test_helper.h"
910

1011
namespace leveldb {
1112

1213
static const int kDelayMicros = 100000;
14+
static const int kReadOnlyFileLimit = 4;
15+
static const int kMMapLimit = 4;
1316

1417
class EnvPosixTest {
1518
private:
@@ -19,6 +22,11 @@ class EnvPosixTest {
1922
public:
2023
Env* env_;
2124
EnvPosixTest() : env_(Env::Default()) { }
25+
26+
static void SetFileLimits(int read_only_file_limit, int mmap_limit) {
27+
EnvPosixTestHelper::SetReadOnlyFDLimit(read_only_file_limit);
28+
EnvPosixTestHelper::SetReadOnlyMMapLimit(mmap_limit);
29+
}
2230
};
2331

2432
static void SetBool(void* ptr) {
@@ -97,8 +105,42 @@ TEST(EnvPosixTest, StartThread) {
97105
ASSERT_EQ(state.val, 3);
98106
}
99107

108+
TEST(EnvPosixTest, TestOpenOnRead) {
109+
// Write some test data to a single file that will be opened |n| times.
110+
std::string test_dir;
111+
ASSERT_OK(Env::Default()->GetTestDirectory(&test_dir));
112+
std::string test_file = test_dir + "/open_on_read.txt";
113+
114+
FILE* f = fopen(test_file.c_str(), "w");
115+
ASSERT_TRUE(f != NULL);
116+
const char kFileData[] = "abcdefghijklmnopqrstuvwxyz";
117+
fputs(kFileData, f);
118+
fclose(f);
119+
120+
// Open test file some number above the sum of the two limits to force
121+
// open-on-read behavior of POSIX Env leveldb::RandomAccessFile.
122+
const int kNumFiles = kReadOnlyFileLimit + kMMapLimit + 5;
123+
leveldb::RandomAccessFile* files[kNumFiles] = {0};
124+
for (int i = 0; i < kNumFiles; i++) {
125+
ASSERT_OK(Env::Default()->NewRandomAccessFile(test_file, &files[i]));
126+
}
127+
char scratch;
128+
Slice read_result;
129+
for (int i = 0; i < kNumFiles; i++) {
130+
ASSERT_OK(files[i]->Read(i, 1, &read_result, &scratch));
131+
ASSERT_EQ(kFileData[i], read_result[0]);
132+
}
133+
for (int i = 0; i < kNumFiles; i++) {
134+
delete files[i];
135+
}
136+
ASSERT_OK(Env::Default()->DeleteFile(test_file));
137+
}
138+
100139
} // namespace leveldb
101140

102141
int main(int argc, char** argv) {
142+
// All tests currently run with the same read-only file limits.
143+
leveldb::EnvPosixTest::SetFileLimits(leveldb::kReadOnlyFileLimit,
144+
leveldb::kMMapLimit);
103145
return leveldb::test::RunAllTests();
104146
}

0 commit comments

Comments
 (0)