2 * Copyright (C) 2018 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License, version 2 only, as
6 * published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * You should have received a copy of the GNU General Public License along with
14 * this program; if not, write to the Free Software Foundation, Inc., 51
15 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include <urcu/list.h>
20 #include <urcu/rculfhash.h>
23 #include <sys/types.h>
29 #include "common/macros.h"
30 #include "common/error.h"
31 #include "common/defaults.h"
32 #include "common/hashtable/utils.h"
33 #include "common/hashtable/hashtable.h"
35 #include "fd-tracker.h"
37 /* Tracker lock must be taken by the user. */
38 #define TRACKED_COUNT(tracker) \
39 (tracker->count.suspendable.active + \
40 tracker->count.suspendable.suspended + \
41 tracker->count.unsuspendable)
43 /* Tracker lock must be taken by the user. */
44 #define ACTIVE_COUNT(tracker) \
45 (tracker->count.suspendable.active + \
46 tracker->count.unsuspendable)
48 /* Tracker lock must be taken by the user. */
49 #define SUSPENDED_COUNT(tracker) \
50 (tracker->count.suspendable.suspended)
52 /* Tracker lock must be taken by the user. */
53 #define SUSPENDABLE_COUNT(tracker) \
54 (tracker->count.suspendable.active + \
55 tracker->count.suspendable.suspended)
57 /* Tracker lock must be taken by the user. */
58 #define UNSUSPENDABLE_COUNT(tracker) \
59 (tracker->count.unsuspendable)
66 unsigned int suspended
;
68 unsigned int unsuspendable
;
70 unsigned int capacity
;
74 /* Failures to suspend or restore fs handles. */
78 * The head of the active_handles list is always the least recently
79 * used active handle. When an handle is used, it is removed from the
80 * list and added to the end. When a file has to be suspended, the
81 * first element in the list is "popped", suspended, and added to the
82 * list of suspended handles.
84 struct cds_list_head active_handles
;
85 struct cds_list_head suspended_handles
;
86 struct cds_lfht
*unsuspendable_fds
;
89 struct open_properties
{
99 * A fs_handle is not ref-counted. Therefore, it is assumed that a
100 * handle is never in-use while it is being reclaimed. It can be
101 * shared by multiple threads, but external synchronization is required
102 * to ensure it is not still being used when it is reclaimed (close method).
103 * In this respect, it is not different from a regular file descriptor.
105 * The fs_handle lock always nests _within_ the tracker's lock.
108 pthread_mutex_t lock
;
110 * Weak reference to the tracker. All fs_handles are assumed to have
111 * been closed at the moment of the destruction of the fd_tracker.
113 struct fd_tracker
*tracker
;
114 struct open_properties properties
;
116 /* inode number of the file at the time of the handle's creation. */
119 /* Offset to which the file should be restored. */
121 struct cds_list_head handles_list_node
;
124 struct unsuspendable_fd
{
126 * Accesses are only performed through the tracker, which is protected
131 struct cds_lfht_node tracker_node
;
135 pthread_mutex_t lock
;
139 .lock
= PTHREAD_MUTEX_INITIALIZER
,
142 static int match_fd(struct cds_lfht_node
*node
, const void *key
);
143 static void unsuspendable_fd_destroy(struct unsuspendable_fd
*entry
);
144 static struct unsuspendable_fd
*unsuspendable_fd_create(const char *name
,
146 static int open_from_properties(struct open_properties
*properties
);
148 static void fs_handle_log(struct fs_handle
*handle
);
149 static int fs_handle_suspend(struct fs_handle
*handle
);
150 static int fs_handle_restore(struct fs_handle
*handle
);
152 static void fd_tracker_track(struct fd_tracker
*tracker
,
153 struct fs_handle
*handle
);
154 static void fd_tracker_untrack(struct fd_tracker
*tracker
,
155 struct fs_handle
*handle
);
156 static int fd_tracker_suspend_handles(struct fd_tracker
*tracker
,
158 static int fd_tracker_restore_handle(struct fd_tracker
*tracker
,
159 struct fs_handle
*handle
);
161 /* Match function of the tracker's unsuspendable_fds hash table. */
163 int match_fd(struct cds_lfht_node
*node
, const void *key
)
165 struct unsuspendable_fd
*entry
=
166 caa_container_of(node
, struct unsuspendable_fd
, tracker_node
);
168 return hash_match_key_ulong((void *) (unsigned long) entry
->fd
,
173 void unsuspendable_fd_destroy(struct unsuspendable_fd
*entry
)
183 struct unsuspendable_fd
*unsuspendable_fd_create(const char *name
, int fd
)
185 struct unsuspendable_fd
*entry
=
186 zmalloc(sizeof(*entry
));
192 entry
->name
= strdup(name
);
197 cds_lfht_node_init(&entry
->tracker_node
);
201 unsuspendable_fd_destroy(entry
);
206 void fs_handle_log(struct fs_handle
*handle
)
208 pthread_mutex_lock(&handle
->lock
);
209 if (handle
->fd
>= 0) {
210 DBG_NO_LOC(" %s [active, fd %d%s]",
211 handle
->properties
.path
,
213 handle
->in_use
? ", in use" : "");
215 DBG_NO_LOC(" %s [suspended]", handle
->properties
.path
);
217 pthread_mutex_unlock(&handle
->lock
);
221 int fs_handle_suspend(struct fs_handle
*handle
)
226 pthread_mutex_lock(&handle
->lock
);
227 assert(handle
->fd
>= 0);
228 if (handle
->in_use
) {
229 /* This handle can't be suspended as it is currently in use. */
234 ret
= stat(handle
->properties
.path
, &fs_stat
);
236 PERROR("Filesystem handle to %s cannot be suspended as stat() failed",
237 handle
->properties
.path
);
242 if (fs_stat
.st_ino
!= handle
->ino
) {
243 /* Don't suspend as the handle would not be restorable. */
244 WARN("Filesystem handle to %s cannot be suspended as its inode changed",
245 handle
->properties
.path
);
250 handle
->offset
= lseek(handle
->fd
, 0, SEEK_CUR
);
251 if (handle
->offset
== -1) {
252 WARN("Filesystem handle to %s cannot be suspended as lseek() failed to sample its current position",
253 handle
->properties
.path
);
258 ret
= close(handle
->fd
);
260 PERROR("Filesystem handle to %s cannot be suspended as close() failed",
261 handle
->properties
.path
);
265 DBG("Suspended filesystem handle to %s (fd %i) at position %" PRId64
,
266 handle
->properties
.path
, handle
->fd
, handle
->offset
);
270 handle
->tracker
->stats
.errors
++;
272 pthread_mutex_unlock(&handle
->lock
);
276 /* Caller must hold the tracker and handle's locks. */
278 int fs_handle_restore(struct fs_handle
*handle
)
282 assert(handle
->fd
== -1);
283 ret
= open_from_properties(&handle
->properties
);
285 PERROR("Failed to restore filesystem handle to %s, open() failed",
286 handle
->properties
.path
);
292 ret
= lseek(fd
, handle
->offset
, SEEK_SET
);
294 PERROR("Failed to restore filesystem handle to %s, lseek() failed",
295 handle
->properties
.path
);
299 DBG("Restored filesystem handle to %s (fd %i) at position %" PRId64
,
300 handle
->properties
.path
, fd
, handle
->offset
);
312 int open_from_properties(struct open_properties
*properties
)
317 * open() ignores the 'flags' parameter unless the O_CREAT or O_TMPFILE
318 * flags are set. O_TMPFILE would not make sense in the context of a
319 * suspendable fs_handle as it would not be restorable (see OPEN(2)),
320 * thus it is ignored here.
322 if ((properties
->flags
& O_CREAT
) && properties
->mode
.is_set
) {
323 ret
= open(properties
->path
, properties
->flags
,
324 properties
->mode
.value
);
326 ret
= open(properties
->path
, properties
->flags
);
329 * Some flags should not be used beyond the initial open() of a
330 * restorable file system handle. O_CREAT and O_TRUNC must
331 * be cleared since it would be unexpected to re-use them
332 * when the handle is retored:
333 * - O_CREAT should not be needed as the file has been created
334 * on the initial call to open(),
335 * - O_TRUNC would destroy the file's contents by truncating it
338 properties
->flags
&= ~(O_CREAT
| O_TRUNC
);
347 struct fd_tracker
*fd_tracker_create(unsigned int capacity
)
349 struct fd_tracker
*tracker
= zmalloc(sizeof(struct fd_tracker
));
355 pthread_mutex_lock(&seed
.lock
);
356 if (!seed
.initialized
) {
357 seed
.value
= (unsigned long) time(NULL
);
358 seed
.initialized
= true;
360 pthread_mutex_unlock(&seed
.lock
);
362 CDS_INIT_LIST_HEAD(&tracker
->active_handles
);
363 CDS_INIT_LIST_HEAD(&tracker
->suspended_handles
);
364 tracker
->capacity
= capacity
;
365 tracker
->unsuspendable_fds
= cds_lfht_new(DEFAULT_HT_SIZE
, 1, 0,
366 CDS_LFHT_AUTO_RESIZE
| CDS_LFHT_ACCOUNTING
, NULL
);
367 DBG("File descriptor tracker created with a limit of %u simultaneously-opened FDs",
373 void fd_tracker_log(struct fd_tracker
*tracker
)
375 struct fs_handle
*handle
;
376 struct unsuspendable_fd
*unsuspendable_fd
;
377 struct cds_lfht_iter iter
;
379 pthread_mutex_lock(&tracker
->lock
);
380 DBG_NO_LOC("File descriptor tracker");
381 DBG_NO_LOC(" Stats:");
382 DBG_NO_LOC(" uses: %" PRIu64
, tracker
->stats
.uses
);
383 DBG_NO_LOC(" misses: %" PRIu64
, tracker
->stats
.misses
);
384 DBG_NO_LOC(" errors: %" PRIu64
, tracker
->stats
.errors
);
385 DBG_NO_LOC(" Tracked: %u", TRACKED_COUNT(tracker
));
386 DBG_NO_LOC(" active: %u", ACTIVE_COUNT(tracker
));
387 DBG_NO_LOC(" suspendable: %u", SUSPENDABLE_COUNT(tracker
));
388 DBG_NO_LOC(" unsuspendable: %u", UNSUSPENDABLE_COUNT(tracker
));
389 DBG_NO_LOC(" suspended: %u", SUSPENDED_COUNT(tracker
));
390 DBG_NO_LOC(" capacity: %u", tracker
->capacity
);
392 DBG_NO_LOC(" Tracked suspendable file descriptors");
393 cds_list_for_each_entry(handle
, &tracker
->active_handles
,
395 fs_handle_log(handle
);
397 cds_list_for_each_entry(handle
, &tracker
->suspended_handles
,
399 fs_handle_log(handle
);
401 if (!SUSPENDABLE_COUNT(tracker
)) {
405 DBG_NO_LOC(" Tracked unsuspendable file descriptors");
407 cds_lfht_for_each_entry(tracker
->unsuspendable_fds
, &iter
,
408 unsuspendable_fd
, tracker_node
) {
409 DBG_NO_LOC(" %s [active, fd %d]", unsuspendable_fd
->name
? : "Unnamed",
410 unsuspendable_fd
->fd
);
413 if (!UNSUSPENDABLE_COUNT(tracker
)) {
417 pthread_mutex_unlock(&tracker
->lock
);
420 int fd_tracker_destroy(struct fd_tracker
*tracker
)
425 * Refuse to destroy the tracker as fs_handles may still old
426 * weak references to the tracker.
428 pthread_mutex_lock(&tracker
->lock
);
429 if (TRACKED_COUNT(tracker
)) {
430 ERR("A file descriptor leak has been detected: %u tracked file descriptors are still being tracked",
431 TRACKED_COUNT(tracker
));
432 pthread_mutex_unlock(&tracker
->lock
);
433 fd_tracker_log(tracker
);
437 pthread_mutex_unlock(&tracker
->lock
);
439 ret
= cds_lfht_destroy(tracker
->unsuspendable_fds
, NULL
);
441 pthread_mutex_destroy(&tracker
->lock
);
447 struct fs_handle
*fd_tracker_open_fs_handle(struct fd_tracker
*tracker
,
448 const char *path
, int flags
, mode_t
*mode
)
451 struct fs_handle
*handle
= NULL
;
453 struct open_properties properties
= {
454 .path
= strdup(path
),
456 .mode
.is_set
= !!mode
,
457 .mode
.value
= mode
? *mode
: 0,
460 if (!properties
.path
) {
464 pthread_mutex_lock(&tracker
->lock
);
465 if (ACTIVE_COUNT(tracker
) == tracker
->capacity
) {
466 if (tracker
->count
.suspendable
.active
> 0) {
467 ret
= fd_tracker_suspend_handles(tracker
, 1);
473 * There are not enough active suspendable file
474 * descriptors to open a new fd and still accomodate the
475 * tracker's capacity.
477 WARN("Cannot open file system handle, too many unsuspendable file descriptors are opened (%u)",
478 tracker
->count
.unsuspendable
);
484 handle
= zmalloc(sizeof(*handle
));
489 ret
= pthread_mutex_init(&handle
->lock
, NULL
);
491 PERROR("Failed to initialize handle mutex while creating fs handle");
496 handle
->fd
= open_from_properties(&properties
);
497 if (handle
->fd
< 0) {
498 PERROR("Failed to open fs handle to %s, open() returned", path
);
504 * Clear the create flag from the open flags as it would make no sense
505 * to use it when restoring a fs handle.
507 properties
.flags
&= ~O_CREAT
;
508 handle
->properties
= properties
;
509 properties
.path
= NULL
;
511 if (fstat(handle
->fd
, &fd_stat
)) {
512 PERROR("Failed to retrieve file descriptor inode while creating fs handle, fstat() returned");
516 handle
->ino
= fd_stat
.st_ino
;
518 fd_tracker_track(tracker
, handle
);
519 handle
->tracker
= tracker
;
520 pthread_mutex_unlock(&tracker
->lock
);
522 free(properties
.path
);
525 pthread_mutex_unlock(&tracker
->lock
);
526 (void) fs_handle_close(handle
);
531 /* Caller must hold the tracker's lock. */
533 int fd_tracker_suspend_handles(struct fd_tracker
*tracker
,
536 unsigned int left_to_close
= count
;
537 struct fs_handle
*handle
, *tmp
;
539 cds_list_for_each_entry_safe(handle
, tmp
, &tracker
->active_handles
,
543 fd_tracker_untrack(tracker
, handle
);
544 ret
= fs_handle_suspend(handle
);
545 fd_tracker_track(tracker
, handle
);
550 if (!left_to_close
) {
554 return left_to_close
? -EMFILE
: 0;
557 int fd_tracker_open_unsuspendable_fd(struct fd_tracker
*tracker
,
558 int *out_fds
, const char **names
, unsigned int fd_count
,
559 fd_open_cb open
, void *user_data
)
561 int ret
, user_ret
, i
, fds_to_suspend
;
562 unsigned int active_fds
;
563 struct unsuspendable_fd
*entries
[fd_count
];
565 memset(entries
, 0, sizeof(entries
));
567 pthread_mutex_lock(&tracker
->lock
);
569 active_fds
= ACTIVE_COUNT(tracker
);
570 fds_to_suspend
= (int) active_fds
+ (int) fd_count
- (int) tracker
->capacity
;
571 if (fds_to_suspend
> 0) {
572 if (fds_to_suspend
<= tracker
->count
.suspendable
.active
) {
573 ret
= fd_tracker_suspend_handles(tracker
, fds_to_suspend
);
579 * There are not enough active suspendable file
580 * descriptors to open a new fd and still accomodate the
581 * tracker's capacity.
583 WARN("Cannot open unsuspendable fd, too many unsuspendable file descriptors are opened (%u)",
584 tracker
->count
.unsuspendable
);
590 user_ret
= open(user_data
, out_fds
);
597 * Add the fds returned by the user's callback to the hashtable
598 * of unsuspendable fds.
600 for (i
= 0; i
< fd_count
; i
++) {
601 struct unsuspendable_fd
*entry
=
602 unsuspendable_fd_create(names
? names
[i
] : NULL
,
607 goto end_free_entries
;
613 for (i
= 0; i
< fd_count
; i
++) {
614 struct cds_lfht_node
*node
;
615 struct unsuspendable_fd
*entry
= entries
[i
];
617 node
= cds_lfht_add_unique(
618 tracker
->unsuspendable_fds
,
619 hash_key_ulong((void *) (unsigned long) out_fds
[i
],
622 (void *) (unsigned long) out_fds
[i
],
623 &entry
->tracker_node
);
625 if (node
!= &entry
->tracker_node
) {
628 goto end_free_entries
;
632 tracker
->count
.unsuspendable
+= fd_count
;
636 pthread_mutex_unlock(&tracker
->lock
);
639 for (i
= 0; i
< fd_count
; i
++) {
640 unsuspendable_fd_destroy(entries
[i
]);
645 int fd_tracker_close_unsuspendable_fd(struct fd_tracker
*tracker
,
646 int *fds_in
, unsigned int fd_count
, fd_close_cb close
,
649 int i
, ret
, user_ret
;
653 * Maintain a local copy of fds_in as the user's callback may modify its
654 * contents (e.g. setting the fd(s) to -1 after close).
656 memcpy(fds
, fds_in
, sizeof(*fds
) * fd_count
);
658 pthread_mutex_lock(&tracker
->lock
);
661 /* Let the user close the file descriptors. */
662 user_ret
= close(user_data
, fds_in
);
668 /* Untrack the fds that were just closed by the user's callback. */
669 for (i
= 0; i
< fd_count
; i
++) {
670 struct cds_lfht_node
*node
;
671 struct cds_lfht_iter iter
;
672 struct unsuspendable_fd
*entry
;
674 cds_lfht_lookup(tracker
->unsuspendable_fds
,
675 hash_key_ulong((void *) (unsigned long) fds
[i
],
678 (void *) (unsigned long) fds
[i
],
680 node
= cds_lfht_iter_get_node(&iter
);
682 /* Unknown file descriptor. */
683 WARN("Untracked file descriptor %d passed to fd_tracker_close_unsuspendable_fd()",
688 entry
= caa_container_of(node
,
689 struct unsuspendable_fd
,
692 cds_lfht_del(tracker
->unsuspendable_fds
, node
);
693 unsuspendable_fd_destroy(entry
);
697 tracker
->count
.unsuspendable
-= fd_count
;
701 pthread_mutex_unlock(&tracker
->lock
);
705 /* Caller must have taken the tracker's and handle's locks. */
707 void fd_tracker_track(struct fd_tracker
*tracker
, struct fs_handle
*handle
)
709 if (handle
->fd
>= 0) {
710 tracker
->count
.suspendable
.active
++;
711 cds_list_add_tail(&handle
->handles_list_node
,
712 &tracker
->active_handles
);
714 tracker
->count
.suspendable
.suspended
++;
715 cds_list_add_tail(&handle
->handles_list_node
,
716 &tracker
->suspended_handles
);
720 /* Caller must have taken the tracker's and handle's locks. */
722 void fd_tracker_untrack(struct fd_tracker
*tracker
, struct fs_handle
*handle
)
724 if (handle
->fd
>= 0) {
725 tracker
->count
.suspendable
.active
--;
727 tracker
->count
.suspendable
.suspended
--;
729 cds_list_del(&handle
->handles_list_node
);
732 /* Caller must have taken the tracker's and handle's locks. */
734 int fd_tracker_restore_handle(struct fd_tracker
*tracker
,
735 struct fs_handle
*handle
)
739 fd_tracker_untrack(tracker
, handle
);
740 if (ACTIVE_COUNT(tracker
) >= tracker
->capacity
) {
741 ret
= fd_tracker_suspend_handles(tracker
, 1);
746 ret
= fs_handle_restore(handle
);
748 fd_tracker_track(tracker
, handle
);
749 return ret
? ret
: handle
->fd
;
752 int fs_handle_get_fd(struct fs_handle
*handle
)
757 * TODO This should be optimized as it is a fairly hot path.
758 * The fd-tracker's lock should only be taken when a fs_handle is
759 * restored (slow path). On the fast path (fs_handle is active),
760 * the only effect on the fd_tracker is marking the handle as the
761 * most recently used. Currently, it is done by a call to the
762 * track/untrack helpers, but it should be done atomically.
764 * Note that the lock's nesting order must still be respected here.
765 * The handle's lock nests inside the tracker's lock.
767 pthread_mutex_lock(&handle
->tracker
->lock
);
768 pthread_mutex_lock(&handle
->lock
);
769 assert(!handle
->in_use
);
771 handle
->tracker
->stats
.uses
++;
772 if (handle
->fd
>= 0) {
774 /* Mark as most recently used. */
775 fd_tracker_untrack(handle
->tracker
, handle
);
776 fd_tracker_track(handle
->tracker
, handle
);
778 handle
->tracker
->stats
.misses
++;
779 ret
= fd_tracker_restore_handle(handle
->tracker
, handle
);
781 handle
->tracker
->stats
.errors
++;
785 handle
->in_use
= true;
787 pthread_mutex_unlock(&handle
->lock
);
788 pthread_mutex_unlock(&handle
->tracker
->lock
);
792 void fs_handle_put_fd(struct fs_handle
*handle
)
794 pthread_mutex_lock(&handle
->lock
);
795 handle
->in_use
= false;
796 pthread_mutex_unlock(&handle
->lock
);
799 int fs_handle_unlink(struct fs_handle
*handle
)
803 pthread_mutex_lock(&handle
->tracker
->lock
);
804 pthread_mutex_lock(&handle
->lock
);
805 ret
= lttng_inode_defer_unlink(handle
->inode
);
806 pthread_mutex_unlock(&handle
->lock
);
807 pthread_mutex_unlock(&handle
->tracker
->lock
);
811 int fs_handle_close(struct fs_handle
*handle
)
820 pthread_mutex_lock(&handle
->tracker
->lock
);
821 pthread_mutex_lock(&handle
->lock
);
822 fd_tracker_untrack(handle
->tracker
, handle
);
823 if (handle
->fd
>= 0) {
824 assert(!handle
->in_use
);
826 * The return value of close() is not propagated as there
827 * isn't much the user can do about it.
829 if (close(handle
->fd
)) {
830 PERROR("Failed to close the file descritptor (%d) of fs handle to %s, close() returned",
831 handle
->fd
, handle
->properties
.path
);
835 pthread_mutex_unlock(&handle
->lock
);
836 pthread_mutex_destroy(&handle
->lock
);
837 pthread_mutex_unlock(&handle
->tracker
->lock
);
838 free(handle
->properties
.path
);