Skip to content

ENT-14195 2. cf-reactor data structure and polling - #6354

Open
victormlg wants to merge 2 commits into
cfengine:masterfrom
victormlg:event-handling-2-polling
Open

ENT-14195 2. cf-reactor data structure and polling#6354
victormlg wants to merge 2 commits into
cfengine:masterfrom
victormlg:event-handling-2-polling

Conversation

@victormlg

@victormlg victormlg commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Spec

In order to track all the events promises, we use two datastructures: a global list of "Watcher", which is a struct associated with an event type and the promise name (also called key) and a global hashmap mapping this key to a bundle which is parsed from the policy.

On an agent run, cf-agent makes cf-reactor read the policy, and rebuilds the list of watchers and the hashmap using the single function WatcherRegister(key, event_type, payload, bundle, interval). Each events promise is associated with an event type, which is defined in when bodies:

body when file_deleted(filename)
{
    file_deleted => "$(filename)";
}

Every event type is must have defined:

  • A check function (called check_fn in Watcher): This is a function defined specifically for the event that checks if the conditions holds. For example, in case of file deletion, we check if the file doesn't exist anymore compare to the last time we checked. If yes, then it returns true.
  • A state (called "payload"): This is a struct whose interpretation depends on the event type (thus being declared as void *). We typically need some state that we compare between each event-check. In the case of file deletion, we need to know the name of the file we are watching, and whether the file existed last time we checked.
  • A payload destroying function (called destroy_payload): This is simply a function to free the state associated with the event type.

Polling

ReactorContextInitialize() sets up all the necessary data structures for polling, and then starts WatcherThreadMain, which polls for events as follows:

  • It iterates through each watcher in the global list of watchers.
  • If the elapsed time exceeds the watcher's interval, it runs check_fn to determine whether an event has been triggered.
  • If an event was triggered, it pushes the watcher's key (the promise name) onto a thread-safe queue, then signals the file descriptor via WakeupChannelNotify, which select(2) will pick up on its next iteration. It then goes back to sleep.

In parallel, EventWatcherHandleEvents, called from within ReactorContextHandleEvents, reads from the file descriptor with WakeupChannelReadFd() once notified that an event has occurred, and pops the thread-safe queue until it's empty. Each key popped from the queue is looked up in the global hashmap to retrieve the corresponding bundle, which cf-reactor then runs (in another thread or subprocess)

Signed-off-by: Victor Moene <victor.moene@northern.tech>
Signed-off-by: Victor Moene <victor.moene@northern.tech>
@victormlg victormlg changed the title Event handling 2 polling ENT-14195 2. cf-reactor data structure and polling Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant