Skip to content

A very primitive Actor implementation#468

Draft
jturel wants to merge 5 commits intoDynflow:masterfrom
jturel:concurrent_agent
Draft

A very primitive Actor implementation#468
jturel wants to merge 5 commits intoDynflow:masterfrom
jturel:concurrent_agent

Conversation

@jturel
Copy link
Copy Markdown
Contributor

@jturel jturel commented Apr 18, 2026

I was able to pull something basic together pretty quickly. It works with my related Katello change https://github.com/jturel/katello/pull/1/changes Of course, it's lacking in the finer details.

Copy link
Copy Markdown
Contributor

@adamruzicka adamruzicka left a comment

Choose a reason for hiding this comment

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

I finally had some time to read this and the concurrent ruby docs again and I must admit I was a bit rusty on it. It seems an actor was the thing I had in mind. Considering agents are more about state, an actor would most likely be a better fit. Sorry about that.

Apart from the katello queue, I'd eventually like to migrate this mqtt re-sending part of sp-rex-ssh to this rather just spawning actors ad-hoc.

Comment thread lib/dynflow/world.rb Outdated
@agents[name]
end

def register_agent(name, value:, observers: [])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rather than being able to register agents on the fly, I was thinking about going more declarative route. World configuration would have an extra_agents option (not set on the name), which would accept a hash name-class pairs.

When used with foreman, foreman-tasks would provide an option through which plugins could register their agents and foreman-tasks would pass the hash with all the agents to dynflow when it actually initializes it.

Comment thread lib/dynflow/world.rb Outdated
def post_initialization
@delayed_executor ||= try_spawn(:delayed_executor, Coordinator::DelayedExecutorLock)
@execution_plan_cleaner ||= try_spawn(:execution_plan_cleaner, Coordinator::ExecutionPlanCleanerLock)
# TODO: is an agent executor needed?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No, I don't think so.

Comment thread lib/dynflow/coordinator.rb Outdated
Comment on lines +191 to +200
class AgentLock < LockByWorld
def initialize(world, agent_name)
super(world)
@data[:id] = self.class.lock_id(agent_name)
end

def self.lock_id(agent_name)
"agent:#{agent_name}"
end
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would mean two things:

  • the agent would be globally unique
  • the agent would only be spawned during world initialization

I don't see any mechanism that would cause other world to try and start the agent if the world that currently holds the lock went away. We could either have the worlds themselves retry or always spawn the agents in all the worlds, but let them compete for the lock.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was thinking more toward the latter but only through the lens of "how many places does the applicability host queue need to be cleared from?" Other use cases may not want global uniqueness at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't see any mechanism that would cause other world to try and start the agent if the world that currently holds the lock went away

Were you asking this simply from the point of safety or is there a general case where a world may go away and not be replaced by another that would restart the actor?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mostly from the point of safety.

General cases could be found once in multi-executor deployments, but those are pretty rare and even then you'd have to try hard to trigger it.

Comment thread .rubocop_todo.yml Outdated
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods.
Metrics/MethodLength:
Max: 47
Max: 48
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Eh, sure, I don't feel strongly about this either way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wasn't sure if rubocop would gate running of tests so I quickly did this. No intention of keeping it

@jturel jturel changed the title A very primitive Agent implementation A very primitive Actor implementation Apr 27, 2026
@jturel
Copy link
Copy Markdown
Contributor Author

jturel commented Apr 27, 2026

Moving to draft as this is really just an experiment at the moment.

@jturel jturel force-pushed the concurrent_agent branch from 3342793 to 0efd5f3 Compare May 4, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants