mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Fix named pipe universal notifier. No more threads. Tests now pass.
This commit is contained in:
parent
f27232bd0a
commit
8a263952ad
4 changed files with 172 additions and 101 deletions
|
@ -1472,11 +1472,6 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool needs_polling() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool poll()
|
bool poll()
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
@ -1502,7 +1497,7 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t
|
||||||
unsigned long usec_per_sec = 1000000;
|
unsigned long usec_per_sec = 1000000;
|
||||||
if (get_time() - last_change_time < 5LL * usec_per_sec)
|
if (get_time() - last_change_time < 5LL * usec_per_sec)
|
||||||
{
|
{
|
||||||
return usec_per_sec / 25; //10 times a second
|
return usec_per_sec / 10; //10 times a second
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1560,7 +1555,7 @@ public:
|
||||||
return notify_fd;
|
return notify_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool drain_notification_fd(int fd)
|
bool notification_fd_became_readable(int fd)
|
||||||
{
|
{
|
||||||
/* notifyd notifications come in as 32 bit values. We don't care about the value. We set ourselves as non-blocking, so just read until we can't read any more. */
|
/* notifyd notifications come in as 32 bit values. We don't care about the value. We set ourselves as non-blocking, so just read until we can't read any more. */
|
||||||
assert(fd == notify_fd);
|
assert(fd == notify_fd);
|
||||||
|
@ -1656,7 +1651,7 @@ public:
|
||||||
return watch_fd;
|
return watch_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool drain_notification_fd(int fd)
|
bool notification_fd_became_readable(int fd)
|
||||||
{
|
{
|
||||||
assert(fd == watch_fd);
|
assert(fd == watch_fd);
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
@ -1689,14 +1684,18 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define NAMED_PIPE_FLASH_DURATION_USEC (1000000 / 10)
|
||||||
|
|
||||||
|
#define SUSTAINED_READABILITY_CLEANUP_DURATION_USEC (1000000 * 5)
|
||||||
|
|
||||||
class universal_notifier_named_pipe_t : public universal_notifier_t
|
class universal_notifier_named_pipe_t : public universal_notifier_t
|
||||||
{
|
{
|
||||||
int pipe_fd;
|
int pipe_fd;
|
||||||
|
long long readback_time_usec;
|
||||||
|
size_t readback_amount;
|
||||||
|
|
||||||
// Remaining variables are protected by this lock
|
bool is_readable;
|
||||||
lock_t lock;
|
long long drain_if_still_readable_time_usec;
|
||||||
unsigned notification_seed;
|
|
||||||
bool notifier_thread_running;
|
|
||||||
|
|
||||||
void make_pipe(const wchar_t *test_path)
|
void make_pipe(const wchar_t *test_path)
|
||||||
{
|
{
|
||||||
|
@ -1727,101 +1726,162 @@ class universal_notifier_named_pipe_t : public universal_notifier_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int notify_in_background(universal_notifier_named_pipe_t *self)
|
void drain_excessive_data()
|
||||||
{
|
{
|
||||||
// We need to write some data (any data) to the pipe, then sleep for a while, then read it back.
|
// The pipe seems to have data on it, that won't go away
|
||||||
// Nobody is expected to read it except us.
|
// Read a big chunk out of it.
|
||||||
// For debugging, we write our pid.
|
// We don't read until it's exhausted, because if someone were to pipe say /dev/null, that would cause us to hang!
|
||||||
// Because we are in a background thread with all signals masked, we do not expect to get EINTR
|
size_t read_amt = 64 * 1024;
|
||||||
const int pid_nbo = htonl(getpid());
|
void *buff = malloc(read_amt);
|
||||||
scoped_lock locker(self->lock);
|
read_ignore(this->pipe_fd, buff, read_amt);
|
||||||
assert(self->notifier_thread_running);
|
free(buff);
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
// Determine the seed at the time we post our request
|
|
||||||
const unsigned initial_seed = self->notification_seed;
|
|
||||||
|
|
||||||
// Perform a notification for that seed
|
|
||||||
locker.unlock();
|
|
||||||
errno = 0;
|
|
||||||
ssize_t amt_written = write(self->pipe_fd, &pid_nbo, sizeof pid_nbo);
|
|
||||||
bool wrote_all = (amt_written == sizeof pid_nbo);
|
|
||||||
int err = errno;
|
|
||||||
|
|
||||||
if (! wrote_all)
|
|
||||||
{
|
|
||||||
// Paranoia. If for some reason our pipe is filled up, then we drain it.
|
|
||||||
// This might happen if there's a bug, or if the user manually redirects something into our pipe
|
|
||||||
bool wrote_partial = (amt_written >= 0 && amt_written < sizeof pid_nbo);
|
|
||||||
bool pipe_full = (wrote_partial || err == EWOULDBLOCK || err == EAGAIN);
|
|
||||||
if (pipe_full)
|
|
||||||
{
|
|
||||||
// Drain the pipe
|
|
||||||
unsigned char buff[256];
|
|
||||||
while (read(pid_nbo, buff, sizeof buff) > 0)
|
|
||||||
{
|
|
||||||
// Keep reading
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now sleep a little
|
|
||||||
const long useconds_per_second = 1000000;
|
|
||||||
usleep(useconds_per_second / 25);
|
|
||||||
|
|
||||||
// Read back what we we wrote
|
|
||||||
int read_back;
|
|
||||||
read_ignore(self->pipe_fd, &read_back, sizeof read_back);
|
|
||||||
|
|
||||||
// See if we need to go around again
|
|
||||||
locker.lock();
|
|
||||||
if (initial_seed == self->notification_seed)
|
|
||||||
{
|
|
||||||
// No more notifications came in, we're done
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Now we're done
|
|
||||||
// Note that we're still locked, so it's safe to manipulate this variable
|
|
||||||
self->notifier_thread_running = false;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
universal_notifier_named_pipe_t(const wchar_t *test_path) : pipe_fd(-1), notification_seed(0), notifier_thread_running(false)
|
universal_notifier_named_pipe_t(const wchar_t *test_path) : pipe_fd(-1), readback_time_usec(0), readback_amount(0), is_readable(false), drain_if_still_readable_time_usec(0)
|
||||||
{
|
{
|
||||||
make_pipe(test_path);
|
make_pipe(test_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
int notification_fd()
|
~universal_notifier_named_pipe_t()
|
||||||
{
|
{
|
||||||
return pipe_fd;
|
if (pipe_fd >= 0)
|
||||||
|
{
|
||||||
|
close(pipe_fd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool drain_notification_fd(int fd)
|
int notification_fd()
|
||||||
{
|
{
|
||||||
// We deliberately do nothing here
|
int result = -1;
|
||||||
return false;
|
if (! is_readable)
|
||||||
|
{
|
||||||
|
result = pipe_fd;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool notification_fd_became_readable(int fd)
|
||||||
|
{
|
||||||
|
// Our fd is readable. We deliberately do not read anything out of it: if we did, other sessions may miss the notification.
|
||||||
|
// Instead, we go into "polling mode:" we do not select() on our fd for a while, and then sync in the future.
|
||||||
|
// However, if we remain readable for too long, we'll read out data
|
||||||
|
if (readback_time_usec > 0)
|
||||||
|
{
|
||||||
|
is_readable = true;
|
||||||
|
drain_if_still_readable_time_usec = get_time() + SUSTAINED_READABILITY_CLEANUP_DURATION_USEC;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void post_notification()
|
void post_notification()
|
||||||
{
|
{
|
||||||
if (pipe_fd >= 0)
|
if (pipe_fd >= 0)
|
||||||
{
|
{
|
||||||
scoped_lock locker(lock);
|
// We need to write some data (any data) to the pipe, then wait for a while, then read it back.
|
||||||
notification_seed++;
|
// Nobody is expected to read it except us.
|
||||||
if (! notifier_thread_running)
|
int pid_nbo = htonl(getpid());
|
||||||
|
ssize_t amt_written = write(this->pipe_fd, &pid_nbo, sizeof pid_nbo);
|
||||||
|
if (amt_written < 0)
|
||||||
{
|
{
|
||||||
// Need to kick it off
|
if (errno == EWOULDBLOCK || errno == EAGAIN)
|
||||||
notifier_thread_running = true;
|
{
|
||||||
iothread_perform(notify_in_background, this);
|
// Very unsual: the pipe is full!
|
||||||
|
drain_excessive_data();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now schedule a read for some time in the future
|
||||||
|
readback_time_usec = get_time() + NAMED_PIPE_FLASH_DURATION_USEC;
|
||||||
|
readback_amount += sizeof pid_nbo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long usec_delay_between_polls() const
|
||||||
|
{
|
||||||
|
unsigned long result = 0;
|
||||||
|
if (this->readback_time_usec > 0)
|
||||||
|
{
|
||||||
|
// How long until the readback?
|
||||||
|
long long now = get_time();
|
||||||
|
if (now >= this->readback_time_usec)
|
||||||
|
{
|
||||||
|
// Oops, it already passed! Return something tiny.
|
||||||
|
result = 1000;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = (unsigned long)(this->readback_time_usec - now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_readable)
|
||||||
|
{
|
||||||
|
// We're in polling mode
|
||||||
|
// Don't return a value less than our polling interval
|
||||||
|
if (result == 0 || result > NAMED_PIPE_FLASH_DURATION_USEC)
|
||||||
|
{
|
||||||
|
result = NAMED_PIPE_FLASH_DURATION_USEC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool poll()
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
// Check if we are past the readback time
|
||||||
|
if (this->readback_time_usec > 0 && get_time() >= this->readback_time_usec)
|
||||||
|
{
|
||||||
|
// Read back what we wrote. We do nothing with the value.
|
||||||
|
while (this->readback_amount > 0)
|
||||||
|
{
|
||||||
|
char buff[64];
|
||||||
|
size_t amt_to_read = mini(this->readback_amount, sizeof buff);
|
||||||
|
read_ignore(this->pipe_fd, buff, amt_to_read);
|
||||||
|
this->readback_amount -= amt_to_read;
|
||||||
|
}
|
||||||
|
assert(this->readback_amount == 0);
|
||||||
|
this->readback_time_usec = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if we are doing readability polling
|
||||||
|
if (is_readable && pipe_fd >= 0)
|
||||||
|
{
|
||||||
|
// See if this is still readable
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(this->pipe_fd, &fds);
|
||||||
|
struct timeval timeout = {};
|
||||||
|
select(this->pipe_fd + 1, &fds, NULL, NULL, &timeout);
|
||||||
|
if (! FD_ISSET(this->pipe_fd, &fds))
|
||||||
|
{
|
||||||
|
// No longer readable
|
||||||
|
is_readable = false;
|
||||||
|
drain_if_still_readable_time_usec = 0;
|
||||||
|
// Sync with the file to pick up any changes
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Still readable. If it's been readable for a long time, there is probably lingering data on the pipe
|
||||||
|
if (get_time() >= drain_if_still_readable_time_usec)
|
||||||
|
{
|
||||||
|
drain_excessive_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use poll() as just a way to clean up our own messes. The real magic happens in notification_fd_became_readable
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
universal_notifier_t::notifier_strategy_t universal_notifier_t::resolve_default_strategy()
|
universal_notifier_t::notifier_strategy_t universal_notifier_t::resolve_default_strategy()
|
||||||
{
|
{
|
||||||
|
return strategy_named_pipe;
|
||||||
#if FISH_NOTIFYD_AVAILABLE
|
#if FISH_NOTIFYD_AVAILABLE
|
||||||
return strategy_notifyd;
|
return strategy_notifyd;
|
||||||
#else
|
#else
|
||||||
|
@ -1886,7 +1946,7 @@ bool universal_notifier_t::poll()
|
||||||
|
|
||||||
bool universal_notifier_t::needs_polling() const
|
bool universal_notifier_t::needs_polling() const
|
||||||
{
|
{
|
||||||
return false;
|
return this->usec_delay_between_polls() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long universal_notifier_t::usec_delay_between_polls() const
|
unsigned long universal_notifier_t::usec_delay_between_polls() const
|
||||||
|
@ -1894,7 +1954,7 @@ unsigned long universal_notifier_t::usec_delay_between_polls() const
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool universal_notifier_t::drain_notification_fd(int fd)
|
bool universal_notifier_t::notification_fd_became_readable(int fd)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,7 +308,7 @@ public:
|
||||||
virtual bool poll();
|
virtual bool poll();
|
||||||
|
|
||||||
/* Indicates whether this notifier requires polling. */
|
/* Indicates whether this notifier requires polling. */
|
||||||
virtual bool needs_polling() const;
|
bool needs_polling() const;
|
||||||
|
|
||||||
/* Triggers a notification */
|
/* Triggers a notification */
|
||||||
virtual void post_notification();
|
virtual void post_notification();
|
||||||
|
@ -320,7 +320,7 @@ public:
|
||||||
virtual int notification_fd();
|
virtual int notification_fd();
|
||||||
|
|
||||||
/* The notification_fd is readable; drain it. Returns true if a notification is considered to have been posted. */
|
/* The notification_fd is readable; drain it. Returns true if a notification is considered to have been posted. */
|
||||||
virtual bool drain_notification_fd(int fd);
|
virtual bool notification_fd_became_readable(int fd);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string get_machine_identifier();
|
std::string get_machine_identifier();
|
||||||
|
|
|
@ -2252,10 +2252,9 @@ bool poll_notifier(universal_notifier_t *note)
|
||||||
{
|
{
|
||||||
result = note->poll();
|
result = note->poll();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
int fd = note->notification_fd();
|
int fd = note->notification_fd();
|
||||||
if (fd > 0)
|
if (fd >= 0)
|
||||||
{
|
{
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
|
@ -2263,8 +2262,7 @@ bool poll_notifier(universal_notifier_t *note)
|
||||||
struct timeval tv = {0, 0};
|
struct timeval tv = {0, 0};
|
||||||
if (select(fd + 1, &fds, NULL, NULL, &tv) > 0 && FD_ISSET(fd, &fds))
|
if (select(fd + 1, &fds, NULL, NULL, &tv) > 0 && FD_ISSET(fd, &fds))
|
||||||
{
|
{
|
||||||
result = note->drain_notification_fd(fd);
|
result = note->notification_fd_became_readable(fd);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -2288,6 +2286,9 @@ static void trigger_or_wait_for_notification(universal_notifier_t *notifier, uni
|
||||||
usleep(1000000 / 25);
|
usleep(1000000 / 25);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case universal_notifier_t::strategy_named_pipe:
|
||||||
|
break;
|
||||||
|
|
||||||
case universal_notifier_t::strategy_inotify:
|
case universal_notifier_t::strategy_inotify:
|
||||||
{
|
{
|
||||||
// Hacktastic. Replace the file, then wait
|
// Hacktastic. Replace the file, then wait
|
||||||
|
@ -2342,7 +2343,17 @@ static void test_notifiers_with_strategy(universal_notifier_t::notifier_strategy
|
||||||
|
|
||||||
if (! poll_notifier(notifiers[i]))
|
if (! poll_notifier(notifiers[i]))
|
||||||
{
|
{
|
||||||
err(L"Universal variable notifier polled failed to notice changes, with strategy %d", (int)strategy);
|
err(L"Universal variable notifier (%lu) %p polled failed to notice changes, with strategy %d", i, notifiers[i], (int)strategy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named pipes have special cleanup requirements
|
||||||
|
if (strategy == universal_notifier_t::strategy_named_pipe)
|
||||||
|
{
|
||||||
|
usleep(1000000 / 10);
|
||||||
|
for (size_t i=0; i < notifier_count; i++)
|
||||||
|
{
|
||||||
|
poll_notifier(notifiers[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2361,20 +2372,20 @@ static void test_notifiers_with_strategy(universal_notifier_t::notifier_strategy
|
||||||
{
|
{
|
||||||
delete notifiers[i];
|
delete notifiers[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_universal_notifiers()
|
static void test_universal_notifiers()
|
||||||
{
|
{
|
||||||
|
if (system("mkdir -p /tmp/fish_uvars_test/ && touch /tmp/fish_uvars_test/varsfile.txt")) err(L"mkdir failed");
|
||||||
test_notifiers_with_strategy(universal_notifier_t::strategy_shmem_polling);
|
test_notifiers_with_strategy(universal_notifier_t::strategy_shmem_polling);
|
||||||
|
test_notifiers_with_strategy(universal_notifier_t::strategy_named_pipe);
|
||||||
#if __APPLE__
|
#if __APPLE__
|
||||||
test_notifiers_with_strategy(universal_notifier_t::strategy_notifyd);
|
test_notifiers_with_strategy(universal_notifier_t::strategy_notifyd);
|
||||||
#endif
|
#endif
|
||||||
#if __linux || linux
|
#if __linux || linux
|
||||||
if (system("mkdir -p /tmp/fish_uvars_test/ && touch /tmp/fish_uvars_test/varsfile.txt")) err(L"mkdir failed");
|
|
||||||
test_notifiers_with_strategy(universal_notifier_t::strategy_inotify);
|
test_notifiers_with_strategy(universal_notifier_t::strategy_inotify);
|
||||||
if (system("rm -Rf /tmp/fish_uvars_test/")) err(L"rm failed");
|
|
||||||
#endif
|
#endif
|
||||||
|
if (system("rm -Rf /tmp/fish_uvars_test/")) err(L"rm failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
class history_tests_t
|
class history_tests_t
|
||||||
|
|
|
@ -192,7 +192,7 @@ static wint_t readb()
|
||||||
|
|
||||||
if (notifier_fd > 0 && FD_ISSET(notifier_fd, &fdset))
|
if (notifier_fd > 0 && FD_ISSET(notifier_fd, &fdset))
|
||||||
{
|
{
|
||||||
bool notified = notifier.drain_notification_fd(notifier_fd);
|
bool notified = notifier.notification_fd_became_readable(notifier_fd);
|
||||||
if (notified)
|
if (notified)
|
||||||
{
|
{
|
||||||
env_universal_barrier();
|
env_universal_barrier();
|
||||||
|
|
Loading…
Reference in a new issue