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

Is it available some kind of init actor #155

Open
lucky-pucka opened this issue Feb 12, 2025 · 5 comments
Open

Is it available some kind of init actor #155

lucky-pucka opened this issue Feb 12, 2025 · 5 comments

Comments

@lucky-pucka
Copy link

Hello!

Thanks a lot for such great actor framework and your work guys!

But I have an issue how to make some kind of init stage. For now I have some actors and one of them read config during routing and spawn all necessary actors. It becomes not convenient when I have more then 3 logical actors. Is there some way to make something like in Docker with init container but with actor in Elfo?

Thanks for your reply in advance!

@loyd
Copy link
Collaborator

loyd commented Feb 17, 2025

Hello,

one of them read config during routing

Can you provide more details here? Do you mean MapRouter::with_state()?


In general, there are several options for how to implement the "init" actor.

Firstly, if a group routes UpdateConfig to some key with Unicast or Multicast, that actor is started on startup (actoromicon). If a group router isn't installed, the default one routes everything (including UpdateConfig) to a single actor, so it's always started.
Thus, you can mount a group with ActorGroup::new().exec(|mut ctx| ..) and use it as some sort of "init" actor, which can then send messages (e.g. a custom Start) to start actors in other groups. In this case, you can provide configuration to this actor only.

Secondly, you can provide configuration to the destination group and use MapRouter::with_state() (actoromicon), which has access to the configuration and can route UpdateConfig according to that configuration. If multiple groups share the same configuration, you can use [common] section in the toml config.

Thirdly, you can mark your group as .entrypoint() in the topology (system.configurers is usually the only group that is marked). In this case, that group will receive elfo::messages::StartEntrypoint. Note that multiple entry points should work but haven't been tested, so I cannot recommend this way for now. Also, writing entry points is not easy because the actor is started before other groups are configured. Writing custom entrypoints (i.e. if you want to replace elfo-confiturer totally) isn't documented yet and is considered unstable.


Anyway, if you would like to provide more information, I can improve the acromion (probably start filling the "Patterns" section to show how to implement some common use cases).

@lucky-pucka
Copy link
Author

@loyd thanks a lot for your response! There are a lot of interesting things that I even didn't know about common section and entrypoint group.

Before you replied I had a working version that was doing that I wanted but I'm not sure about usage of elfo in right way.

I have some kind of next code to make implementation:

#[derive(Debug, serde::Deserialize)]
struct SpawnerConfig {
    names: Vec<String>,
    value: u8,
}

pub fn spawn_spawner() -> Blueprint {
    ActorGroup::new()
        .config::<SpawnerConfig>()
        .router(MapRouter::with_state(
            |_, _| { "spawner".to_string() },
            |envelope, actor_name| {
                msg!(match envelope {
                    UpdateConfig => Outcome::Unicast(actor_name.clone()),
                    _ => Outcome::Default,
                })
            }
        ))
        .exec(move |ctx| {
            async move {
                Spawner::new(ctx).run().await
            }
        })
}

pub struct Spawner {
    ctx: Context<SpawnerConfig, String>,
}

impl Spawner {
    pub fn new(ctx: Context<SpawnerConfig, String>) -> Self {
        Self {
            ctx,
        }
    }

    async fn spawn_actors(&self) -> Result<()> {
        let config = self.ctx.config();
        for name in config.names.iter() {
            let spawn_actor = SpawnActor {
                name: name.clone(),
                value: config.value,
            };
            
             let spawned_response = self.ctx
                 .request(spawn_actor)
                 .resolve()
                 .await()?;

            tracing::info!("Spawned actor: {:?}", spawned_response);
     }

     async fn run(self) -> Result<()> {
          self.spawn_actors().await
     }

This actor runs that sends all necessary spawn messages, collects responses from actor groups and then terminates. For now it's something that I need but I'm not sure that I've made it right way

@loyd
Copy link
Collaborator

loyd commented Feb 18, 2025

@lucky-pucka This is exactly the first pattern described above. with_state looks useless here (but you probably use it in the full code via ctx.key() somewhere).

Note: your spawner actor will be started on every reconfiguration (e.g. try to send SIGHUP to the process) and send requests repeatedly, so destination actors should be ready to receive SpawnActor during their main cycle (while let Some(envelope) = ctx.recv().await { .. }).

@loyd
Copy link
Collaborator

loyd commented Feb 18, 2025

I even didn't know about common section

This is described in the usage example

[common]
# All properties defined in this section will be merged into each actor group's section.
# The primary purpose is to define default values of system settings (logging, dumping, and so on).
# Parameters and their defaults
# Mailbox
#system.mailbox.capacity = 100
#
# Logging
#system.logging.max_level = "Info" # one of: Trace, Debug, Info, Warn, Error, Off.
#system.logging.max_rate_per_level = 1000 # per second
#
# Dumping
#system.dumping.disabled = false
#system.dumping.max_rate = 100_000 # per second
#
# Telemetry
#system.telemetry.per_actor_group = true
#system.telemetry.per_actor_key = false
# Each parameter can be redefined on the actor group level.

But obviously should be documented in the actoromicon.


Let this issue be open until the first two patterns are documented in the actoromicon.

@lucky-pucka
Copy link
Author

@loyd do I understand correctly that .with_state I can have some custom logic and load some data from JSON file for example and have access to it via self.ctx somehow?

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

No branches or pull requests

2 participants