Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kqueue.go: Remove timeout from reading kevents. #262

Closed

Conversation

paulquerna-okta
Copy link

@paulquerna-okta paulquerna-okta commented Aug 22, 2018

What does this pull request do?

Instead of having a timeout that would trigger the readEvents to check it's
done channel, run a seaprate go-routine that watches w.done, and directly
closes the KQueue FD.

This change allows a go-process to fully sleep and go into CPU idle mode,
instead of waking up every 100ms on macOS.

Fixes #237

gdey
gdey previously approved these changes Aug 31, 2018
Copy link
Contributor

@gdey gdey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@paulquerna-okta
Copy link
Author

paulquerna-okta commented Sep 12, 2018

Closing as this was merged to master

wasn't actually merged to master -- issue is still there.

@wweir
Copy link

wweir commented Dec 19, 2018

Expect a new release. This problem has caused me a big headache.

@paulquerna-okta
Copy link
Author

Is it possible to merge this? Just rebased it against master, and we've been running with this patch on our software for about 8 months now.

@paulquerna-okta paulquerna-okta mentioned this pull request Mar 21, 2019
Copy link
Contributor

@nathany nathany left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kqueue.go Outdated Show resolved Hide resolved
@paulquerna-okta
Copy link
Author

Rebased against current master -- just a note we've been running this patch for 18 months to reduce CPU usage on our MacOS app. Without it the app would consistently show up Activity Monitor's Energy tab.

@dmt0
Copy link

dmt0 commented Jul 7, 2020

Any chance this could be merged in?

Instead of having a timeout that would trigger the readEvents to check it's
done channel, run a seaprate go-routine that watches w.done, and directly
closes the KQueue FD.

This change allows a go-process to fully sleep and go into CPU idle mode,
instead of waking up every 100ms on macOS.
@arp242
Copy link
Member

arp242 commented Aug 1, 2022

On FreeBSD and NetBSD this blocks forever on:

n, err := unix.Kevent(kq, nil, events, timeout)

Called from the main event loop:

kevents, err := read(w.kq(), eventBuffer, nil)

kqueue(2) on FreeBSD says:

If timeout is a non-NULL pointer, it specifies a maximum interval to wait for an event, which will be interpreted as a struct timespec. If timeout is a NULL pointer, kevent() waits indefinitely.

kqueue(2) on OpenBSD and macOS have the same language; I added some debug printf statements, and got:

OpenBSD:

read ...  readDone <nil> 1
read ...  readDone <nil> 1
read ...  readDone <nil> 2
read ...  readDone <nil> 1
read ...  readDone <nil> 1
read ...  readDone <nil> 1
read ...closeKq ....  readDone bad file descriptor -1
closeKqDone: <nil>

FreeBSD:

read ...  readDone <nil> 1
read ...  readDone <nil> 1
read ...  readDone <nil> 2
read ...  readDone <nil> 1
read ...  readDone <nil> 1
read ...  readDone <nil> 1
read ...closeKq ....closeKqDone: <nil>
    helpers_test.go:278: event stream was not closed after 1 second

So the difference is that on OpenBSD Kevent() will return if the file descriptor is closed, whereas on FreeBSD it won't. I assume it's the same on macOS, but I didn't try that yet.

I can't really find this behaviour documented anywhere, and seems to be something of an implementation detail that may change on macOS and OpenBSD in the future too.


Some other (minor) issues:

  • It seems to call unix.Close() on _kq twice? once in closeKq() and once at the end of readEvents(). You added a unix.EBADF error check for this, but that's only needed because it's already closed and set to -1 in closeKq().

  • Also, the timeout parameters is now always nil, and durationToTimespec() is never used (but those are minor issues, compared to it not working at all).


And my printf debugging, in case it's useful to anyone else:

diff --git kqueue.go kqueue.go
index 1d19378..a3aa768 100644
--- kqueue.go
+++ kqueue.go
@@ -304,7 +304,9 @@ func (w *Watcher) kq() int {
func (w *Watcher) closeKq() {
        <-w.done
        w.mu.Lock()
-       unix.Close(w._kq)
+       fmt.Print("closeKq ....")
+       err := unix.Close(w._kq)
+       fmt.Println("closeKqDone:", err)
        w._kq = -1
        w.mu.Unlock()
}
@@ -560,7 +562,9 @@ func register(kq int, fds []int, flags int, fflags uint32) error {
// read retrieves pending events, or waits until an event occurs.
// A timeout of nil blocks indefinitely, while 0 polls the queue.
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
+       fmt.Print("read ...")
        n, err := unix.Kevent(kq, nil, events, timeout)
+       fmt.Printf("  readDone %v %d\n", err, n)
        if err != nil {
                return nil, err
        }

@arp242 arp242 added the need-feedback Requires feedback to be actionable label Aug 1, 2022
@arp242
Copy link
Member

arp242 commented Aug 1, 2022

#124 seems to fix the same problem, and doesn't seem to have any of the above issues in a quick check. Updated branch here, but needs some more work as it's a pretty old branch: https://github.com/fsnotify/fsnotify/tree/kqueue-no-timeout

arp242 added a commit that referenced this pull request Aug 1, 2022
The timeout for unix.Kevent() is causing issues; every 100ms it will do a new
unix.Kevent() syscall, which isn't too efficient: even if you have just one
change an hour, you will still keep calling kevent() ten times per second,
resulting in a needlessly high CPU usage.

Without a timeout, kevent() will block indefinitely until there are some events,
which is much more efficient. We can't just remove the timout however, since we
can't interrupt the kevent() call on FreeBSD and NetBSD, and it will hang
forever. This issue is described in more detail here:
#262 (comment)

To solve this we register a new kevent() with the file descriptor set to the
closepipe; when this pipe is closed an event is sent and kevent() will stop
blocking.

This is a rebased version of #124.

Fixes #89
Fixes #237
Fixes #333
Supersedes and closes #124
Supersedes and closes #262
Supersedes and closes #334
@arp242
Copy link
Member

arp242 commented Aug 1, 2022

I created #480, which should solve this issue better.

@arp242 arp242 closed this Aug 1, 2022
arp242 added a commit that referenced this pull request Aug 6, 2022
The timeout for unix.Kevent() is causing issues; every 100ms it will do a new
unix.Kevent() syscall, which isn't too efficient: even if you have just one
change an hour, you will still keep calling kevent() ten times per second,
resulting in a needlessly high CPU usage.

Without a timeout, kevent() will block indefinitely until there are some events,
which is much more efficient. We can't just remove the timout however, since we
can't interrupt the kevent() call on FreeBSD and NetBSD, and it will hang
forever. This issue is described in more detail here:
#262 (comment)

To solve this we register a new kevent() with the file descriptor set to the
closepipe; when this pipe is closed an event is sent and kevent() will stop
blocking.

This is a rebased version of #124.

Fixes #89
Fixes #237
Fixes #333
Supersedes and closes #124
Supersedes and closes #262
Supersedes and closes #334

Co-authored-by: Felix Lange <fjl@twurst.com>
shogo82148 pushed a commit to shogo82148/fsnotify that referenced this pull request Mar 6, 2024
The timeout for unix.Kevent() is causing issues; every 100ms it will do a new
unix.Kevent() syscall, which isn't too efficient: even if you have just one
change an hour, you will still keep calling kevent() ten times per second,
resulting in a needlessly high CPU usage.

Without a timeout, kevent() will block indefinitely until there are some events,
which is much more efficient. We can't just remove the timout however, since we
can't interrupt the kevent() call on FreeBSD and NetBSD, and it will hang
forever. This issue is described in more detail here:
fsnotify/fsnotify#262 (comment)

To solve this we register a new kevent() with the file descriptor set to the
closepipe; when this pipe is closed an event is sent and kevent() will stop
blocking.

This is a rebased version of #124.

Fixes #89
Fixes #237
Fixes #333
Supersedes and closes #124
Supersedes and closes #262
Supersedes and closes #334

Co-authored-by: Felix Lange <fjl@twurst.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
freebsd macOS need-feedback Requires feedback to be actionable
Projects
None yet
Development

Successfully merging this pull request may close these issues.

High CPU usage
6 participants