Subject: | Catalyst::Restarter wastes FDs under kqueue-based File::ChangeNotify watchers |
On BSD-like operating systems (including Mac OS), File::ChangeNotify prefers to use IO::KQueue to watch for filesystem changes. The way kqueue works requires holding an open file descriptor for every file and directory watched. In the case of modules being loaded from a local::lib, this can easily amount to many thousands of file descriptors. This is not only wasteful, but it also prevents select(2) from being called on any file descriptor subsequently opened: you're likely to get EINVAL because the descriptor exceeds FD_SETSIZE.
The attached patch fixes this by having Catalyst::Restarter::Forking clear the watcher in the child process, implicitly closing all the watched descriptors; while the parent process retains the watcher as expected.
Subject: | 0001-Catalyst-Restarter-Forking-clear-watcher-in-child-pr.patch |
From 16b1e5f59b47c4b11752098d2ca7bca8ff5e3d76 Mon Sep 17 00:00:00 2001
From: Aaron Crane <arc@cpan.org>
Date: Thu, 12 Jan 2017 09:45:30 +0000
Subject: [PATCH] Catalyst::Restarter::Forking: clear watcher in child process
This allows any resources held by the watcher to be released in the child
process, where they won't be needed.
This is particularly important in kqueue-based watcher implementations.
The way kqueue works requires an open file descriptor for each file and
directory watched. In the case of modules being loaded from a local::lib,
this can easily amount to many thousands of file descriptors. This is not
only wasteful, but it also prevents select(2) from being called on any
file descriptor subsequently opened: you're likely to get EINVAL because the
descriptor exceeds FD_SETSIZE.
---
lib/Catalyst/Restarter.pm | 5 +++--
lib/Catalyst/Restarter/Forking.pm | 3 +++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/lib/Catalyst/Restarter.pm b/lib/Catalyst/Restarter.pm
index 5ce4ea4..81295f1 100644
--- a/lib/Catalyst/Restarter.pm
+++ b/lib/Catalyst/Restarter.pm
@@ -22,8 +22,9 @@ has argv => (
);
has _watcher => (
- is => 'rw',
- isa => 'File::ChangeNotify::Watcher',
+ is => 'rw',
+ isa => 'File::ChangeNotify::Watcher',
+ clearer => '_clear_watcher',
);
has _filter => (
diff --git a/lib/Catalyst/Restarter/Forking.pm b/lib/Catalyst/Restarter/Forking.pm
index 40c81f0..a45e516 100644
--- a/lib/Catalyst/Restarter/Forking.pm
+++ b/lib/Catalyst/Restarter/Forking.pm
@@ -17,6 +17,9 @@ sub _fork_and_start {
$self->_child($pid);
}
else {
+ # Only the parent process needs to watch for changes, so the child
+ # should release any resources held by the watcher:
+ $self->_clear_watcher;
$self->start_sub->();
}
}
--
2.11.0