add poll notification support#140
Conversation
Expose libfuse low-level poll support through pyfuse3 so filesystems can implement poll(2), select(2) and epoll readiness notifications. Add bindings for struct fuse_pollhandle, fuse_reply_poll(), fuse_lowlevel_notify_poll() and fuse_pollhandle_destroy(). Introduce a Python PollHandle wrapper and a notify_poll() helper, allowing a filesystem to retain the poll handle provided by Operations.poll() and notify it later when readiness changes. Wire the low-level FUSE poll callback into Operations.poll(), returning the current readiness mask to the kernel. The default implementation continues to raise ENOSYS so existing filesystems keep the previous fallback behaviour unless they opt in. This is needed by filesystems that emulate pollable kernel interfaces, such as sysfs GPIO value files, where edge events must wake userspace processes waiting for POLLPRI. Fixes: libfuse#139 Signed-off-by: Christopher Obbard <christopher.obbard@linaro.org>
Add a pollable file to the test filesystem and exercise the new Operations.poll() and notify_poll() APIs. The test opens the synthetic file, starts a userspace poll(2) waiter, and waits until the filesystem has received and stored a PollHandle. It then triggers readiness through the existing setxattr command channel. The filesystem marks the file ready, calls notify_poll() and the test verifies that the blocked poller wakes with POLLPRI. This covers the notification path from the low-level FUSE poll callback, through the Python PollHandle wrapper, to fuse_lowlevel_notify_poll(). Signed-off-by: Christopher Obbard <christopher.obbard@linaro.org>
de00d33 to
66c617d
Compare
| events (e.g. ``select.POLLIN``, ``select.POLLOUT``, ``select.POLLPRI``). | ||
| If no events are currently ready, return ``0``. | ||
| If *poll_handle* is not ``None``, the kernel is requesting to be |
There was a problem hiding this comment.
What are the semantics when poll_handle is None?
| raise | ||
|
|
||
|
|
||
| class Fs(pyfuse3.Operations): |
There was a problem hiding this comment.
I think it'd be nicer to subclass this for different tests (i.e., define a PollTestFs), rather than aggregating all the functionality in one big class.
There was a problem hiding this comment.
Makes sense - will see what I can do.
There was a problem hiding this comment.
Tried to do it. Haven't squashed the commits yet - can you review again from diff view, please ?
There was a problem hiding this comment.
This is the right direction, but you're redefining almost everything instead of inheriting it. For example, your PollTestFs shouldn't need to declare its own readdir implementation, getattr implementation, etc. I think the only thing you need to overwrite is poll and setxattr.
|
Thanks for the MR! I only just replied on the issue tracker :-). |
There was a problem hiding this comment.
Pull request overview
This PR exposes libfuse low-level poll(2) readiness support through pyfuse3, allowing filesystems to implement Operations.poll() and later wake blocked poll/select/epoll waiters via notify_poll().
Changes:
- Wire up the libfuse low-level
pollcallback and reply path (fuse_ops.poll→fuse_poll→Operations.poll→fuse_reply_poll). - Introduce a
PollHandleopaque type plusnotify_poll(handle)to deliver readiness notifications. - Add an integration test filesystem and test case covering poll notification behavior.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
test/test_fs.py |
Adds a pollable test file and an integration test validating wakeup via notify_poll. |
src/pyfuse3/internal.pxi |
Registers the new low-level poll handler in fuse_lowlevel_ops. |
src/pyfuse3/handlers.pxi |
Implements fuse_poll/fuse_poll_async bridging to Operations.poll and replies with fuse_reply_poll. |
src/pyfuse3/_pyfuse3.py |
Adds the Operations.poll() API and its documentation. |
src/pyfuse3/__init__.pyx |
Adds PollHandle and the notify_poll() API. |
src/pyfuse3/__init__.pyi |
Updates typing stubs for PollHandle and notify_poll(). |
Include/fuse_lowlevel.pxd |
Declares low-level poll op, fuse_reply_poll, and fuse_lowlevel_notify_poll. |
Include/fuse_common.pxd |
Declares fuse_pollhandle and fuse_pollhandle_destroy. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| A single notification is enough to clear all pending waiters; calling | ||
| this function again on the same handle is harmless but redundant. | ||
| The handle remains valid (and may be notified again) until its Python | ||
| reference is dropped, at which point the underlying ``fuse_pollhandle`` | ||
| is destroyed. |
Expose libfuse low-level poll support through pyfuse3 so filesystems can implement poll(2), select(2) and epoll readiness notifications. Add bindings for struct fuse_pollhandle, fuse_reply_poll(), fuse_lowlevel_notify_poll() and fuse_pollhandle_destroy(). Introduce a Python PollHandle wrapper with a notify() method, allowing a filesystem to retain the handle provided by Operations.poll() and wake waiters later when readiness changes. Wire the low-level FUSE poll callback into Operations.poll(), returning the current readiness mask to the kernel. The default implementation continues to raise ENOSYS so existing filesystems keep the previous fallback behaviour unless they opt in. This is needed by filesystems that emulate pollable kernel interfaces, such as sysfs GPIO value files, where edge events must wake userspace processes waiting for POLLPRI. Signed-off-by: Christopher Obbard <christopher.obbard@linaro.org>
|
|
||
| cdef int ret | ||
|
|
||
| if self._ph is NULL: |
| def __dealloc__(self): | ||
| if self._ph is not NULL: | ||
| fuse_pollhandle_destroy(self._ph) | ||
| self._ph = NULL |
There was a problem hiding this comment.
Nit: Looking at https://cython.readthedocs.io/en/latest/src/userguide/special_methods.html#finalization-methods-dealloc-and-del, nothing can possibly access the object after __dealloc__ has run, so there's no need to reset the pointer.
| if ph is NULL: | ||
| py_ph = None | ||
| else: | ||
| py_ph = PollHandle.__new__(PollHandle) |
There was a problem hiding this comment.
Why not just PollHandle()?
| raise | ||
|
|
||
|
|
||
| class Fs(pyfuse3.Operations): |
There was a problem hiding this comment.
This is the right direction, but you're redefining almost everything instead of inheriting it. For example, your PollTestFs shouldn't need to declare its own readdir implementation, getattr implementation, etc. I think the only thing you need to overwrite is poll and setxattr.
Expose libfuse low-level poll support through pyfuse3.