Skip to content

Commit b85370b

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 b85370b

File tree

3 files changed

+101
-17
lines changed

3 files changed

+101
-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: 62 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,32 @@ 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)
596+
__attribute__((destructor)) static void print_openat_stats(void) {
597+
uint64_t tot = n_openats + n_opens;
598+
printf("%" PRIu64 "/%" PRIu64 " (%" PRIu64 "%%) of directories used openat() over open()\n",
599+
n_openats, tot, n_openats * 100 / tot);
600+
}
601+
#endif
602+
603+
/*
604+
* Increment the reference count for a dir_rc.
605+
*/
606+
static void dir_inc(struct dir_rc *dir) {
607+
// XXX: is relaxed OK here?
608+
__atomic_fetch_add(&dir->rc, 1, __ATOMIC_ACQ_REL);
609+
}
610+
583611
/*
584612
* Create a new reference-counted DIR object for the given `struct work`.
585613
*
@@ -589,31 +617,49 @@ INSTALL_NUMBER(UINT64, uint64_t, "%" PRIu64)
589617
* Otherwise, just opendir() the path.
590618
*
591619
* Increments the refcount on the new object.
620+
*
621+
* If we are hitting the limit on the maximum number of open files available,
622+
* then designate the new dir_rc as "non-clonable" so that it is closed as soon as
623+
* processing the current directory is complete.
592624
*/
593625
struct dir_rc *open_dir_rc(struct work *w) {
594626
DIR *dir;
595627

596628
if (w->parent_dir) {
629+
__atomic_add_fetch(&n_openats, 1, __ATOMIC_RELAXED);
597630
int d_fd = get_dir_fd(w->parent_dir);
598631
char *basename = w->name + w->name_len - w->basename_len;
599632
int fd = openat(d_fd, basename, O_RDONLY|O_DIRECTORY);
600633
if (fd < 0) {
601-
return NULL;
634+
goto err;
602635
}
603636
dir = fdopendir(fd);
604637
} else {
638+
__atomic_add_fetch(&n_opens, 1, __ATOMIC_RELAXED);
605639
dir = opendir(w->name);
606640
}
607641

608642
if (!dir) {
609-
return NULL;
643+
goto err;
610644
}
611645

612646
struct dir_rc *new = calloc(1, sizeof(*new));
613-
// printf("creating dir at %p\n", new);
614647
new->dir = dir;
615648
dir_inc(new);
649+
650+
uint64_t cur_open_files = __atomic_add_fetch(&CUR_OPEN_FILES, 1, __ATOMIC_ACQ_REL);
651+
if (cur_open_files >= MAX_OPEN_FILES) {
652+
new->dont_clone = 1;
653+
}
654+
616655
return new;
656+
657+
err:
658+
if (errno == EMFILE) {
659+
fprintf(stderr, "Warning: too many open files, index may not be complete!\n");
660+
}
661+
662+
return NULL;
617663
}
618664

619665
/*
@@ -624,19 +670,20 @@ int get_dir_fd(struct dir_rc *dir) {
624670
}
625671

626672
/*
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.
673+
* Attempt to clone a `dir_rc`.
674+
*
675+
* If succesful, this increments the refcount and returns a pointer to the
676+
* dir_rc to the caller.
677+
*
678+
* If unsuccesful, returns NULL.
679+
*
680+
* The caller MUST be prepared for cloning to fail!
638681
*/
639682
struct dir_rc *dir_clone(struct dir_rc *dir) {
683+
if (dir->dont_clone) {
684+
return NULL;
685+
}
686+
640687
dir_inc(dir);
641688
return dir;
642689
}
@@ -646,10 +693,9 @@ struct dir_rc *dir_clone(struct dir_rc *dir) {
646693
*/
647694
void dir_dec(struct dir_rc *dir) {
648695
if (dir) {
649-
// printf("decrementing dir at %p\n", dir);
650696
if (__atomic_sub_fetch(&dir->rc, 1, __ATOMIC_ACQ_REL) == 0) {
651-
// printf("freeing dir at %p\n", dir);
652697
closedir(dir->dir);
698+
__atomic_sub_fetch(&CUR_OPEN_FILES, 1, __ATOMIC_ACQ_REL);
653699
free(dir);
654700
}
655701
}

0 commit comments

Comments
 (0)