Skip to content

Commit 1985be7

Browse files
committed
track the number of open FDs in order to not exceed max
Since keeping directory FDs alive in order to open child directories with relative paths incurs the risk of exhausting available file descriptors, this tracks the maximum number of FDs that should be allocated to long-living directory handles. When that maximum is reached, directory handles will no longer have their lifetime extended, until the limit goes back down again.
1 parent 8111a08 commit 1985be7

File tree

3 files changed

+103
-17
lines changed

3 files changed

+103
-17
lines changed

include/bf.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,11 +368,11 @@ void free_work(void *p);
368368
struct dir_rc {
369369
DIR *dir;
370370
uint64_t rc;
371+
int dont_clone;
371372
};
372373

373374
struct dir_rc *open_dir_rc(struct work *w);
374375
int get_dir_fd(struct dir_rc *dir);
375-
void dir_inc(struct dir_rc *dir);
376376
struct dir_rc *dir_clone(struct dir_rc *dir);
377377
void dir_dec(struct dir_rc *dir);
378378

src/QueuePerThreadPool.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,51 @@ OF SUCH DAMAGE.
6262

6363

6464

65+
#include <errno.h>
6566
#include <pthread.h>
6667
#include <stdio.h>
68+
#include <string.h>
6769
#include <stdlib.h>
70+
#include <sys/resource.h>
6871

6972
#include "QueuePerThreadPool.h"
7073
#include "SinglyLinkedList.h"
7174
#include "swap.h"
7275
#include "utils.h"
7376

77+
extern rlim_t MAX_OPEN_FILES;
78+
/*
79+
* Determine how many file descriptors to allocate to potentially long-lived
80+
* directory handles. Since the number of directory handles alive at once could
81+
* be unbounded, it would risk file descriptor exhaustion if a limnit is not
82+
* imposed.
83+
*/
84+
void init_open_file_limit(size_t nthreads) {
85+
struct rlimit rl;
86+
int res = getrlimit(RLIMIT_NOFILE, &rl);
87+
if (res) {
88+
fprintf(stderr, "Warning: could not get open file limit: %s\n", strerror(errno));
89+
MAX_OPEN_FILES = 0;
90+
return;
91+
}
92+
93+
/*
94+
* Reserve 3 file descriptors per thread so that they always can always open
95+
* the files they are currently working on. (This factor was determined
96+
* experimentally.)
97+
*/
98+
size_t reserve_fds = nthreads * 3;
99+
100+
if (rl.rlim_cur <= reserve_fds) {
101+
// fprintf(stderr, "Warning: system may not allow enough open files for the number of requested threads.\n");
102+
// fprintf(stderr, "Max number of open files: %llu; Number of threads requested: %llu\n", rl.rlim_cur, nthreads);
103+
MAX_OPEN_FILES = 0;
104+
return;
105+
};
106+
107+
MAX_OPEN_FILES = rl.rlim_cur - reserve_fds;
108+
}
109+
74110
typedef enum {
75111
INITIALIZED,
76112
RUNNING,
@@ -569,6 +605,8 @@ QPTPool_t *QPTPool_init_with_props(const size_t nthreads, void *args,
569605
return NULL;
570606
}
571607

608+
init_open_file_limit(nthreads);
609+
572610
QPTPool_t *ctx = malloc(sizeof(QPTPool_t));
573611
ctx->nthreads = nthreads;
574612
ctx->queue_limit = queue_limit;

src/bf.c

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,15 @@ OF SUCH DAMAGE.
6262

6363

6464

65+
#include <errno.h>
6566
#include <fcntl.h>
6667
#include <pwd.h>
6768
#include <stdint.h>
6869
#include <stdio.h>
6970
#include <stdlib.h>
7071
#include <string.h>
7172
#include <unistd.h>
73+
#include <sys/resource.h>
7274

7375
#include "bf.h"
7476
#include "dbutils.h"
@@ -580,6 +582,34 @@ INSTALL_NUMBER(INT, int, "%d")
580582
INSTALL_NUMBER(SIZE, size_t, "%zu")
581583
INSTALL_NUMBER(UINT64, uint64_t, "%" PRIu64)
582584

585+
rlim_t MAX_OPEN_FILES; /* Maximum number of files that may be held open by dir_rc objects */
586+
uint64_t CUR_OPEN_FILES; /* Current number of open files held by dir_rc objects */
587+
588+
/*
589+
* For understanding performance, track how many directories were opened
590+
* with relative vs. absolute paths.
591+
*/
592+
static uint64_t n_openats = 0;
593+
static uint64_t n_opens = 0;
594+
595+
#if defined(DEBUG) && 0
596+
__attribute__((destructor)) static void print_openat_stats(void) {
597+
uint64_t tot = n_openats + n_opens;
598+
if (tot > 0) {
599+
printf("%" PRIu64 "/%" PRIu64 " (%" PRIu64 "%%) of directories used openat() over open()\n",
600+
n_openats, tot, n_openats * 100 / tot);
601+
}
602+
}
603+
#endif
604+
605+
/*
606+
* Increment the reference count for a dir_rc.
607+
*/
608+
static void dir_inc(struct dir_rc *dir) {
609+
// XXX: is relaxed OK here?
610+
__atomic_fetch_add(&dir->rc, 1, __ATOMIC_ACQ_REL);
611+
}
612+
583613
/*
584614
* Create a new reference-counted DIR object for the given `struct work`.
585615
*
@@ -589,31 +619,49 @@ INSTALL_NUMBER(UINT64, uint64_t, "%" PRIu64)
589619
* Otherwise, just opendir() the path.
590620
*
591621
* Increments the refcount on the new object.
622+
*
623+
* If we are hitting the limit on the maximum number of open files available,
624+
* then designate the new dir_rc as "non-clonable" so that it is closed as soon as
625+
* processing the current directory is complete.
592626
*/
593627
struct dir_rc *open_dir_rc(struct work *w) {
594628
DIR *dir;
595629

596630
if (w->parent_dir) {
631+
__atomic_add_fetch(&n_openats, 1, __ATOMIC_RELAXED);
597632
int d_fd = get_dir_fd(w->parent_dir);
598633
char *basename = w->name + w->name_len - w->basename_len;
599634
int fd = openat(d_fd, basename, O_RDONLY|O_DIRECTORY);
600635
if (fd < 0) {
601-
return NULL;
636+
goto err;
602637
}
603638
dir = fdopendir(fd);
604639
} else {
640+
__atomic_add_fetch(&n_opens, 1, __ATOMIC_RELAXED);
605641
dir = opendir(w->name);
606642
}
607643

608644
if (!dir) {
609-
return NULL;
645+
goto err;
610646
}
611647

612648
struct dir_rc *new = calloc(1, sizeof(*new));
613-
// printf("creating dir at %p\n", new);
614649
new->dir = dir;
615650
dir_inc(new);
651+
652+
uint64_t cur_open_files = __atomic_add_fetch(&CUR_OPEN_FILES, 1, __ATOMIC_ACQ_REL);
653+
if (cur_open_files >= MAX_OPEN_FILES) {
654+
new->dont_clone = 1;
655+
}
656+
616657
return new;
658+
659+
err:
660+
if (errno == EMFILE) {
661+
fprintf(stderr, "Warning: too many open files, index may not be complete!\n");
662+
}
663+
664+
return NULL;
617665
}
618666

619667
/*
@@ -624,19 +672,20 @@ int get_dir_fd(struct dir_rc *dir) {
624672
}
625673

626674
/*
627-
* Increment the reference count for a dir_rc.
628-
*/
629-
void dir_inc(struct dir_rc *dir) {
630-
// printf("incrementing dir at %p\n", dir);
631-
// XXX: is relaxed OK here?
632-
__atomic_fetch_add(&dir->rc, 1, __ATOMIC_ACQ_REL);
633-
}
634-
635-
/*
636-
* Clone a `dir_rc`.
637-
* This increments the refcount and returns a pointer to the dir_rc to the caller.
675+
* Attempt to clone a `dir_rc`.
676+
*
677+
* If succesful, this increments the refcount and returns a pointer to the
678+
* dir_rc to the caller.
679+
*
680+
* If unsuccesful, returns NULL.
681+
*
682+
* The caller MUST be prepared for cloning to fail!
638683
*/
639684
struct dir_rc *dir_clone(struct dir_rc *dir) {
685+
if (dir->dont_clone) {
686+
return NULL;
687+
}
688+
640689
dir_inc(dir);
641690
return dir;
642691
}
@@ -646,10 +695,9 @@ struct dir_rc *dir_clone(struct dir_rc *dir) {
646695
*/
647696
void dir_dec(struct dir_rc *dir) {
648697
if (dir) {
649-
// printf("decrementing dir at %p\n", dir);
650698
if (__atomic_sub_fetch(&dir->rc, 1, __ATOMIC_ACQ_REL) == 0) {
651-
// printf("freeing dir at %p\n", dir);
652699
closedir(dir->dir);
700+
__atomic_sub_fetch(&CUR_OPEN_FILES, 1, __ATOMIC_ACQ_REL);
653701
free(dir);
654702
}
655703
}

0 commit comments

Comments
 (0)