Skip to content

Support listening for file signal events#3250

Merged
stevenengler merged 5 commits intoshadow:mainfrom
stevenengler:callback-signal
Dec 19, 2023
Merged

Support listening for file signal events#3250
stevenengler merged 5 commits intoshadow:mainfrom
stevenengler:callback-signal

Conversation

@stevenengler
Copy link
Copy Markdown
Contributor

@stevenengler stevenengler commented Dec 13, 2023

We currently allow things to listen for state changes on files (ex: not readable -> readable), but there are cases where we want to listen for things that aren't state changes. For example edge-triggered epoll_wait()s will sometimes return if more bytes are written to a file's input buffer, even if the file's state didn't change (readable -> readable).

This adds the ability to emit signals from files, and to listen for them in the same way that you listen for state changes. This PR doesn't actually define any signals. For example listener callbacks were previously:

let listen_state = FileState::READABLE;
let filter = StateListenerFilter::Always;

let handle = file.add_listener(listen_state, filter, move |state, changed, cb_queue| {
    todo!();
});

You can now write:

let listen_state = FileState::READABLE;
let listen_signals = FileSignals::INPUT_BUFFER_CHANGED; // this signal doesn't actually exist
let filter = StateListenerFilter::Always;

/// listener will run when either the READABLE state changes, or when INPUT_BUFFER_CHANGED is emitted
let handle = file.add_listener(listen_state, listen_signals, filter, move |state, changed, signals, cb_queue| {
    todo!();
});

You emit signals using:

event_source.notify_listeners(file_state, states_changed, signals, cb_queue);

@stevenengler stevenengler self-assigned this Dec 13, 2023
@github-actions github-actions bot added Component: Libraries Support functions like LD_PRELOAD and logging Component: Main Composing the core Shadow executable labels Dec 13, 2023
@robgjansen
Copy link
Copy Markdown
Member

👍 This design seems pretty elegant and should make it easier for us to implement some of epoll's api, and maybe other things too.

@haxxpop
Copy link
Copy Markdown
Contributor

haxxpop commented Dec 15, 2023

I have a concern on this design. Without signals, it's fine to accidentally add more than one listeners to the event source, but, with them, it's not. Let's look at the refresh_listener function of epoll.

    fn refresh_listener(&mut self, weak_self: Weak<AtomicRefCell<Epoll>>, key: Key) {
        let Some(entry) = self.monitoring.get_mut(&key) else {
            return;
        };

        // Check what state we need to listen for this entry.
        // We always listen for closed so we know when to stop monitoring the entry.
        let listen = entry.get_listener_state().union(FileState::CLOSED);
        let filter = StateListenerFilter::Always;

        // Set up a callback so we get informed when the file changes.
        let file = key.file().clone();
        let handle =
            file.borrow_mut()
                .add_listener(listen, filter, move |state, changed, cb_queue| {
                    if let Some(epoll) = weak_self.upgrade() {
                        epoll
                            .borrow_mut()
                            .notify_entry(&key, state, changed, cb_queue);
                    }
                });
        entry.set_listener_handle(Some(handle));
    }

This seems to me that I can call refresh_listener as many times as I want and the number of listeners added is the same as the number of times I call. Without signals, it's fine because you will just get excessive notifications, but with signals, you will be too many signals.

Can you make sure that all the listeners of all the event sources actually call add_listener only once? If not, I think we will probably need a new design.

@stevenengler
Copy link
Copy Markdown
Contributor Author

stevenengler commented Dec 15, 2023

Without signals, it's fine because you will just get excessive notifications, but with signals, you will be too many signals.

Each time refresh_listener is called, it adds a new listener and removes the old listener. When the handle returned by add_listener is dropped, the listener for that handle is removed. So when entry.set_listener_handle(Some(handle)) is called, it replaces the old handle with the new handle, dropping the old handle and removing the old listener. So it should only receive a single notification, even if you call refresh_listener many times on the same key/entry.

I'm not completely on clear on the concern about multiple listeners receiving the same signal. And is the original "INPUT_BUFFER_PARITY" any different, I think you'd also see that state flag change on all listeners as well?

@haxxpop
Copy link
Copy Markdown
Contributor

haxxpop commented Dec 15, 2023

Each time refresh_listener is called, it adds a new listener and removes the old listener. When the handle returned by add_listener is dropped, the listener for that handle is removed. So when entry.set_listener_handle(Some(handle)) is called, it replaces the old handle with the new handle, dropping the old handle and removing the old listener. So it should only receive a single notification, even if you call refresh_listener many times on the same key/entry.

Alright, got it. Thanks.

I'm not completely on clear on the concern about multiple listeners receiving the same signal. And is the original "INPUT_BUFFER_PARITY" any different, I think you'd also see that state flag change on all listeners as well?

Yeah, it's probably not different. So, never mind :)

@haxxpop
Copy link
Copy Markdown
Contributor

haxxpop commented Dec 15, 2023

I think this PR is great. I will do the edged-triggered epoll after this PR is merged.

@stevenengler stevenengler marked this pull request as ready for review December 15, 2023 17:46
@stevenengler stevenengler merged commit 8584a6b into shadow:main Dec 19, 2023
@stevenengler stevenengler deleted the callback-signal branch December 19, 2023 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Component: Libraries Support functions like LD_PRELOAD and logging Component: Main Composing the core Shadow executable

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants