Skip to content

Commit cd2700f

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 5a5bd3b commit cd2700f

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
@@ -379,11 +379,11 @@ void free_work(void *p);
379379
struct dir_rc {
380380
DIR *dir;
381381
uint64_t rc;
382+
int dont_clone;
382383
};
383384

384385
struct dir_rc *open_dir_rc(struct work *w);
385386
int get_dir_fd(struct dir_rc *dir);
386-
void dir_inc(struct dir_rc *dir);
387387
struct dir_rc *dir_clone(struct dir_rc *dir);
388388
void dir_dec(struct dir_rc *dir);
389389

src/QueuePerThreadPool.c

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

6464

6565
#include <assert.h>
66+
#include <errno.h>
6667
#include <pthread.h>
6768
#include <stdio.h>
69+
#include <string.h>
6870
#include <stdlib.h>
71+
#include <sys/resource.h>
6972

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

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

610+
init_open_file_limit(nthreads);
611+
574612
QPTPool_t *ctx = malloc(sizeof(QPTPool_t));
575613
ctx->nthreads = nthreads;
576614
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"
@@ -590,6 +592,34 @@ INSTALL_NUMBER(INT, int, "%d")
590592
INSTALL_NUMBER(SIZE, size_t, "%zu")
591593
INSTALL_NUMBER(UINT64, uint64_t, "%" PRIu64)
592594

595+
rlim_t MAX_OPEN_FILES; /* Maximum number of files that may be held open by dir_rc objects */
596+
uint64_t CUR_OPEN_FILES; /* Current number of open files held by dir_rc objects */
597+
598+
/*
599+
* For understanding performance, track how many directories were opened
600+
* with relative vs. absolute paths.
601+
*/
602+
static uint64_t n_openats = 0;
603+
static uint64_t n_opens = 0;
604+
605+
#if defined(DEBUG) && 0
606+
__attribute__((destructor)) static void print_openat_stats(void) {
607+
uint64_t tot = n_openats + n_opens;
608+
if (tot > 0) {
609+
printf("%" PRIu64 "/%" PRIu64 " (%" PRIu64 "%%) of directories used openat() over open()\n",
610+
n_openats, tot, n_openats * 100 / tot);
611+
}
612+
}
613+
#endif
614+
615+
/*
616+
* Increment the reference count for a dir_rc.
617+
*/
618+
static void dir_inc(struct dir_rc *dir) {
619+
// XXX: is relaxed OK here?
620+
__atomic_fetch_add(&dir->rc, 1, __ATOMIC_ACQ_REL);
621+
}
622+
593623
/*
594624
* Create a new reference-counted DIR object for the given `struct work`.
595625
*
@@ -599,31 +629,49 @@ INSTALL_NUMBER(UINT64, uint64_t, "%" PRIu64)
599629
* Otherwise, just opendir() the path.
600630
*
601631
* Increments the refcount on the new object.
632+
*
633+
* If we are hitting the limit on the maximum number of open files available,
634+
* then designate the new dir_rc as "non-clonable" so that it is closed as soon as
635+
* processing the current directory is complete.
602636
*/
603637
struct dir_rc *open_dir_rc(struct work *w) {
604638
DIR *dir;
605639

606640
if (w->parent_dir) {
641+
__atomic_add_fetch(&n_openats, 1, __ATOMIC_RELAXED);
607642
int d_fd = get_dir_fd(w->parent_dir);
608643
char *basename = w->name + w->name_len - w->basename_len;
609644
int fd = openat(d_fd, basename, O_RDONLY|O_DIRECTORY);
610645
if (fd < 0) {
611-
return NULL;
646+
goto err;
612647
}
613648
dir = fdopendir(fd);
614649
} else {
650+
__atomic_add_fetch(&n_opens, 1, __ATOMIC_RELAXED);
615651
dir = opendir(w->name);
616652
}
617653

618654
if (!dir) {
619-
return NULL;
655+
goto err;
620656
}
621657

622658
struct dir_rc *new = calloc(1, sizeof(*new));
623-
// printf("creating dir at %p\n", new);
624659
new->dir = dir;
625660
dir_inc(new);
661+
662+
uint64_t cur_open_files = __atomic_add_fetch(&CUR_OPEN_FILES, 1, __ATOMIC_ACQ_REL);
663+
if (cur_open_files >= MAX_OPEN_FILES) {
664+
new->dont_clone = 1;
665+
}
666+
626667
return new;
668+
669+
err:
670+
if (errno == EMFILE) {
671+
fprintf(stderr, "Warning: too many open files, index may not be complete!\n");
672+
}
673+
674+
return NULL;
627675
}
628676

629677
/*
@@ -634,19 +682,20 @@ int get_dir_fd(struct dir_rc *dir) {
634682
}
635683

636684
/*
637-
* Increment the reference count for a dir_rc.
638-
*/
639-
void dir_inc(struct dir_rc *dir) {
640-
// printf("incrementing dir at %p\n", dir);
641-
// XXX: is relaxed OK here?
642-
__atomic_fetch_add(&dir->rc, 1, __ATOMIC_ACQ_REL);
643-
}
644-
645-
/*
646-
* Clone a `dir_rc`.
647-
* This increments the refcount and returns a pointer to the dir_rc to the caller.
685+
* Attempt to clone a `dir_rc`.
686+
*
687+
* If succesful, this increments the refcount and returns a pointer to the
688+
* dir_rc to the caller.
689+
*
690+
* If unsuccesful, returns NULL.
691+
*
692+
* The caller MUST be prepared for cloning to fail!
648693
*/
649694
struct dir_rc *dir_clone(struct dir_rc *dir) {
695+
if (dir->dont_clone) {
696+
return NULL;
697+
}
698+
650699
dir_inc(dir);
651700
return dir;
652701
}
@@ -656,10 +705,9 @@ struct dir_rc *dir_clone(struct dir_rc *dir) {
656705
*/
657706
void dir_dec(struct dir_rc *dir) {
658707
if (dir) {
659-
// printf("decrementing dir at %p\n", dir);
660708
if (__atomic_sub_fetch(&dir->rc, 1, __ATOMIC_ACQ_REL) == 0) {
661-
// printf("freeing dir at %p\n", dir);
662709
closedir(dir->dir);
710+
__atomic_sub_fetch(&CUR_OPEN_FILES, 1, __ATOMIC_ACQ_REL);
663711
free(dir);
664712
}
665713
}

0 commit comments

Comments
 (0)