Coder Social home page Coder Social logo

ractor's Introduction

Hi, I'm Sean and I'm a Rustacean.

Rusty wave

About me

I'm currently a software engineer at Meta Platforms in Core Systems. I work on distributed, high-throughput systems.

Selected repos

  1. ractor - Inspired by Erlang's OTP, ractor provides save actor programming paradigms in 100% Rust.
  2. akd - Auditable Key Directories (AKDs) are the foundation of Key Transparency at WhatsApp. This is the core logic for WhatsApp's realization.

ractor's People

Contributors

dcadenas avatar dependabot[bot] avatar george-miao avatar hardliner66 avatar jsgf avatar kianmeng avatar leonqadirie avatar marcusirgens avatar poteto avatar quietlychris avatar romac avatar seeekr avatar simonsan avatar slawlor avatar tbillington avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ractor's Issues

Typed errors and events

Have you looked into the possibility of more strongly typed SupervisionEvents and ActorProcessingErrs ?

For example, a supervising actor could receive a ActorTerminated with a typed response as opposed to Option<String>. That way actors can clearly signal what happened in a way more idiomatic, akin to rusts Result handling.

It feels like the exactly erlang model of actors panicing and leaving it to the supervisor to handle does not map 1:1. "Most" rust code will use ? and From some error types into others to surface errors to callers as opposed to the throw exception model. So a library/developer experience focused on panic handling as the primary error communication method doesn't allow rust to work to it's fullest. Perhaps it's me attempting to fit actors into my mental model though, and I should rethink.

My understanding is there's no type level link between two actors, one of which is supervising the other, so implementing this currently would be interesting, though the conversion between ActorCell and ActorRef feels like it could be related.

Just spit balling, but hopefully communicates the idea

use ractor::{Actor, ActorRef};

enum ChildError {
    DatabaseError(String),
    ProcessingError(String),
}

struct Parent;

#[async_trait::async_trait]
impl Actor for Parent {
    type Msg = ();
    type State = ();
    type Arguments = ();

    async fn pre_start(
        &self,
        myself: ractor::ActorRef<Self>,
        _: Self::Arguments,
    ) -> Result<Self::State, ractor::ActorProcessingErr> {
        Actor::spawn_linked(None, Child, (), myself.get_cell()).await?;
        Ok(())
    }
}

impl Supervisor<Child> for Parent {
    type StopReason = ChildError;

    async fn handle_supervisor_evt(
        &self,
        _this_actor: ActorRef<Self>,
        message: SupervisionEvent<Self::StopReason>,
        _state: &mut Self::State,
    ) -> Result<(), ActorProcessingErr> {
        match message {
            SupervisionEvent::ActorTerminated(_, _, reason) => {
                match reason {
                    ChildError::DatabaseError(msg) => {
                        warn!("child experience database error");
                        // restart
                    }
                    ChildError::ProcessingError(msg) => {
                        error!("child experiences processing error");
                        // don't restart
                    }
                }
            }
            _ => {
                // usual Supervision events
            }
        }
    }
}

struct Child;

#[async_trait::async_trait]
impl Actor for Child {
    type Msg = ();
    type State = ();
    type Arguments = ();

    async fn pre_start(
        &self,
        _myself: ractor::ActorRef<Self>,
        _args: Self::Arguments,
    ) -> Result<Self::State, ractor::ActorProcessingErr> {
        Ok(())
    }

    async fn handle(
        &self,
        myself: ActorRef<Self>,
        _message: Self::Msg,
        state: &mut Self::State,
    ) -> Result<(), ractor::ActorProcessingErr> {
        // alternative to manually calling stop would be returning Result<(), ChildError> and implementing From<DbError> and From<ProcessingError> so we could just `.await?` instead
        if let Err(msg) = database_work(state).await {
            myself.stop(ChildError::DatabaseError(msg));
            return Ok(());
        }

        if let Err(msg) = processing_work(state).await {
            myself.stop(ChildError::ProcessingError(msg));
        }

        Ok(())
    }
}

Add note about MSRV

I was kicking around adding ractor as a dependency to one of my projects recently, and kept running into some issues with some tokio-related build issues. After spending some time changing around features in other deps to try to match tokio, I decided to try a rustup update on my toolchain, which seems to have resolved the issues I was seeing. I believe this update from from rustc 1.65.0-nightly to rustc 1.71-nightly.

I'm not sure if anyone else has seen something similar (maybe it was just a quirk of my system), but I know that some Rust projects add a Minimum Supported Rust Version (MSRV), with a specific build in their CI toolchain to make sure new releases don't break people who haven't update their toolchain very recently, or can at least make informed decisions about when to do so. I was wondering if you might consider doing something similar for ractor. Thanks working on this crate--I'm really interested about the possibilities, and it's nice to see something actively worked on now that actix and bastion don't really seem to be being developed any longer!

Mod Driver Aplikasi Fake GPS

** Is your feature request related to a problem? Please describe**. Fake GPS features or applications that are accurate and undetectable By the company and the cyber IT maps application team [...]

Describe the solution you'd like
I'm an online driver. Can you make a Ghost MOD for the Shopee application with an accurate fake GPS driver? . I am a Shope Food driver partner. Make me a fake GPS application so that my work runs smoothly and the signal is strong and wide. Not detected by the company.

Describe alternatives you've considered
How do I make sure the provider's signal is strong and not weak? To expand the Google Maps network and coordinate points

Additional context
SHOPEE DRIVER

Screenshot_2023-11-28-12-42-58-70_428762c02d9d96f83ebe3be73b8d5f35

Adding a ChildSpec for actors

ractor could potentially expose a ChildSpec trait that inherits Clone and exposes a crate_child function that returns an instance of the child. This way a supervisor could pass an instance of its own state to create children in a generic way.

Log messages

We have 0 log messages in the actor flow, and definitely need some. Let's standardize on the log crate.

Bidirectional linking can cause stack overflow on actor shutdown

Discussed in #166

Before starting this discussion, I had already conducted tests. Here is how I tested it:

sampler.get_cell().link(proc.get_cell());
proc.get_cell().link(sampler.get_cell());
concurrency::sleep(concurrency::Duration::from_secs(3)).await;
// sampler.stop(Some("user shutdown".to_owned()));
proc.stop(Some("user shutdown".to_owned()));

However, during runtime, it crashed with the following error message:
"thread 'tokio-runtime-worker' has overflowed its stack, fatal runtime error: stack overflow".
Did I use the link method incorrectly?

WebAssembly Support

Is your feature request related to a problem? Please describe.
Ractor seems to be very close to capable of running in a WASM environment, even a browser one. It would be amazing to have an actor system that could be used client- and server-side in a Rust app.

Describe the solution you'd like
I was able to get ractor running in the browser very very simply and with minimal changes over at https://github.com/tekacs/ractor/tree/wasm-support from master by using the WIP swappable runtimes in concurrency.rs.

The implementation is janky and a little incorrect, but mostly works and does the job nicely. Making this more correct and validating tests would be the next step.

I would love to incorporate such changes into upstream ractor to make it possible to keep going with an approach like this in the longer term.

Describe alternatives you've considered
I currently have a custom client-side actor system in my WASM environments... it would be nice to unify things under the banner of ractor.

Enhance documentation

We need

  • More examples
  • Better documentation of structs + interfaces
  • More readme details
  • Library documentation in lib.rs

Add more derive to types?

When using types like factory::Job, it is impossible to clone it due to lack of derives. It will be useful to add them. If this is desirable, I can make a PR.

send_interval can drift

Based on the implementation of send_interval being through sleeping, the interval will drift over time in long running programs.

The sources of drift can come from both the sleep timer potentially not being woken up on time, and the time taken to run the user code to produce the message, send_message itself, and the loop mechanism will all introduce delay before the next call to sleep.

Since the sleep period does not take into account the delta between the last and current, it is unable to compensate for added execution time and therefore will be subject to drift over time.

As a source of prior art, the tokio Interval default behaviour accounts for this and will schedule the next accordingly. This behaviour can be configured on the interval through set_missed_tick_behavior with a MissedTickBehaviour.

Initial test coverage

After #1 merges, we'll need additional test coverage of

  1. More complex supervisor tests (perhaps multiple supervisors of an agent)
  2. TBD

ASK pattern

In one of the examples in the Quickstart mentions sending a message and not expecting a reply. Does that mean that there is a way of sending a message and getting a reply?

I'm trying to replicate the ASK pattern found in Akka and other Actor platforms. Blocking an actor (in an async way, obviously) while waiting for a message to come back.

SpawnErr is misleading

Describe the bug
When a Err is returned during actor startup, the err gets wrapped with a SpawnErr::StartupPanic, while there was no panic. There should probably be a SpawnErr::StartupError or something similar, or StartupPanic should be renamed, to avoid confusion.

Create a typed actor ref

We should have a strong typed ActorRef which gets passed around most often. It'll just be a wrapper over actorcell but with a phantom data pointing to the ActorHandler implementation so that you don't need to always pass the type.

Then we should implement From both ways between actorcell so you can easily convert to/from.

That should cleanup usage a lot

Multiple concurrent async tasks on actors

While the documentation around it is quite sparse, the current implementation notes that long-running asynchronous tasks "block" the actor's message handler, and that delays or similar should be translated to timers and message events.

For use as a sort of in-app microservice architecture, this makes it difficult to run long-running tasks as an actor, as something like sending them a graceful stop request could have the message handler trigger a cancellation token stored in the actor's state, but the message pump is not being polled as long as the actor's state is claimed.

Suggested solution

If actor state were held under async lock guards, messages could be queued immediately any time the state is not currently locked, allowing for actors to "yield" back to the loop any time they are not currently processing work.

For any task which must be performed in a serial manner, holding the guard across the await allows the message pump to be disabled until the runtime can achieve a new lock, allowing actors to opt in to non-concurrency.

This may even be simpler to implement than the above- in that the message handler could always be called by the runtime, with the expectation that any message handler that uses "state" will simply await a lock on the state guard. This means that messages will be processed as concurrently as possible, but has the performance drawback in that it could result in many message futures being queued and awaiting a lock on that actor's state.

Potential alternatives

  • futures-esque combinators atop timers to allow network-serializable poll-able events
    • Wrap any future in a "wake-me and come back to this point"
    • yield(myState)-like construct required in order to allow concurrency by temporarily returning state control to the runtime
    • Simplifies "correct" usage of the current API, but doesn't follow Rust conventions on asynchrony
  • Externally accessible state stored in shared references to hold cancellation tokens
    • Bypasses the messaging pump, weakening the communication model
  • Actor-level first-class support for graceful cancellation tokens
    • Partially solves the microservice case
    • Doesn't provide for inter-microservice communication over the message system, thus weakening the communication model

Additional context

The root of the issue is that concurrency is limited by mutable access to Actor::State. Bevy's scheduling approach of reader / writer isolation may also be of interest here, in that queries which read a resource may be concurrent, while queries which write to that resource must be run in isolation from each other.

[RFC] Move `ActorRef` to the message type not the actor type

Presently ActorRef is bound to the TActor: Actor type, which means the ActorRef references the

  1. Actor type
  2. Message type
  3. State type
  4. Startup arguments type

which is generally unnecessary, since all we really care about is the message type. This means that we should move from

ActorRef<TActor: Actor> -> ActorRef<TMessage: Message>

which doesn't require leaking as many types for simple message passing. This however is a large refactor and contract break so would be a major version bump across all libs and downstream usages.

Output ports + subscriptions

We should be able to setup an "output" port kind of publish-subscribe model so actors can "emit" messages without knowing exactly who is listening.

This would require actors to emit some kind of ports which can be wired up to with a disposable (drop-able) handle to the subscription

Remote actors don't appear in the local registry

I was looking for a way to be able to communicate with a remote actor without having to join it to a process group, and found this section in the code that prevents the RemoteActors from registering with the remote actor's name into the local registry:

// TODO: remote actors don't appear in the name registry
// if let Some(r_name) = name {
// crate::registry::register(r_name, cell.clone())?;
// }

Uncommenting this code (and cloning the name to get around the move into ActorProperties::new_remote) seems to work as expected, with all the remote actors successfully being reigstered.

Is there an issue that this approach causes, or could this be enabled?

Feature request: utility to call the same actor with multiple messages

We have written up a (crude) utility function to send multiple messages to the same actor and receive the response with a mpsc channel.

This is mainly to minimize overhead.

Is it possible to include such feature in ractor?

Here's my implementation. One big flaw is that this requires making another message type for MpscSender instead of using RpcReplyPort

pub async fn call_multi<TMessage, TReply, TMsgBuilder>(
    actor: &ActorCell,
    msg_builder: TMsgBuilder,
    size: usize,
) -> Result<Vec<TReply>, MessagingErr<TMessage>>
where
    TMessage: Message,
    TMsgBuilder: FnOnce(MpscSender<TReply>) -> Vec<TMessage>,
{
    let (tx, mut rx) = concurrency::mpsc_bounded(size);
    let msgs = msg_builder(tx);

    for msg in msgs {
        actor.send_message::<TMessage>(msg)?;
    }

    let mut replies = Vec::with_capacity(size);

    // wait for the reply
    while let Some(result) = rx.recv().await {
        replies.push(result);
    }

    Ok(replies)
}

Can't NodeSession::new

It seems that ractor_cluster::protocol::auth::NameMessage is private (the whole protocol package is private)

And so I can't pass anything valid to https://docs.rs/ractor_cluster/latest/ractor_cluster/node/node_session/struct.NodeSession.html#method.new

Is this a mistake or am I doing something wrong?

(A more general question might be: what's the correct way to connect to a remote actor during runtime?)


Edit: I believe this can just have pub added: https://github.com/slawlor/ractor/blob/main/ractor_cluster/src/lib.rs#L56

Actor MailBox - Bounded Channel

The actor mailbox is currently implemented with an unbounded mpsc channel.

This seems to allow an actor input port to get filled up with lots of messages and does not provide any backpressure mechanism.

It would be nice to have a configuration option to use a bounded channel instead, with message delivery either blocking until capacity is available, or failing immediately . (with different methods)

Motiviation: both providing back pressure and preventing infinite buildup of messages are important design properties that are easy to do by just using regular channels.

This could be emulated by a top level actor that delegates to child actors and keeps track of in-flight messages being processed, but that's a lot more complicated.

Distributed Nodes

Following from Erlang, we want to support distributed nodes
https://www.erlang.org/doc/reference_manual/distributed.html

We need

  • Tcp connections and transmission
  • Encrypted socket communications #55
  • Distribution handshake
  • Dist specification
  • Serializable messages which can go over the network boundary in a distributed environment
  • Each "node" needs to know of all nodes it's dist connected to (this includes updating the local PG groups and named registry with remote nodes)
  • Each "remote" node should be a local actor that talks to the remote channel, allowing for local message processing without knowing anything about the actor being remote
  • Registration of remote actors on a local system (control.proto)
  • Lifecycle events of actors (when an actor dies, it should notify it's local NodeSession who propagates it to the remote system to cleanup the RemoteActor instance)
  • In Erlang, nodes connect to other nodes a given node is connected to (as an optional functionality), so like A connects to B who's also connected to C, then A also initiates a connection to C. This should be relatively simple to implement as an optional functionality. #53
  • A lot of documentation
  • Added e2e framework with initial test on auth handshake + pg group synchronization

Already present:

Ergonomics issue: RpcReplyPort with custom BytesConvertable impls

Hi again!

I have implemented BytesConvertable for a custom type, something like this:

impl BytesConvertable for Value { ... }

This works well and fine on its own, but I wanted to express an RpcReplyPort in terms of a wrapper around Value, i.e. Option<Value> or Vec<Value>

Unfortunately, there's no blanket impl <T> BytesConvertable for Vec<T> where T: BytesConvertable, so this doesn't work.

Further, as a user, I can't impl BytesConvertable for Vec<Value> because it's not permitted to implement foreign traits for foreign types

I had a look at your existing BytesConvertable impls and noticed that you manually implement a family of serdes using macros.

I did also try to build a blanket impl myself but it got very messy when I ran into a lack of numeric traits

For reference, I was trying to wrangle some code like this:

impl<T> BytesConvertable for Vec<T> where T: BytesConvertable + Sized {
    fn into_bytes(self) -> Vec<u8> {
        let mut result = vec![0u8; self.len() * std::mem::size_of::<T>()];
        for (offset, item) in self.into_iter().enumerate() {
            result[offset * std::mem::size_of::<T>()
                ..offset * std::mem::size_of::<T>() + std::mem::size_of::<T>()]
                .copy_from_slice(&item.to_be_bytes());
        }
        result
    }
    fn from_bytes(bytes: Vec<u8>) -> Self {
        let num_el = bytes.len() / std::mem::size_of::<T>();
        let mut result = vec![T::MIN; num_el];

        let mut data = [0u8; std::mem::size_of::<T>()];
        for offset in 0..num_el {
            data.copy_from_slice(
                &bytes[offset * std::mem::size_of::<T>()
                    ..offset * std::mem::size_of::<T>() + std::mem::size_of::<T>()],
            );
            result[offset] = <T>::from_be_bytes(data);
        }

        result
    }
}

(and some other variations, e.g. trying to use const generics in place of std::mem::size_of::<T>(), etc)

I came across this PR that I think would add the special sauce to do an appropriate blanket impl for numerics and therefore I think also blanket impl for Vec<T> where T: BytesConvertable.

In summary:

  • At the moment, the best a user can do if they want to RpcReplyPort<Collection<T>> seems to be to wrap Vec with a custom struct and then impl BytesConvertable for MyVecWrapper<Value>
  • It would be nice to have a built-in blanket impl BytesConvertable for Vec<T> where T: BytesConvertable
  • I'm wondering if you're aware/tracking this and/or have other suggestions
  • More generally, BytesConvertable feels clumsy compared to using serde -- it would be nice to have serde integration, perhaps behind a feature flag?

Add `Scope`-> `GroupName` mapping

Relating to #177 (comment):

This is where having another index might be helpful, i.e. Scope -> Group name in a separate mapping. But that's an optimization that can be added in the future.

To make sure I understand you correctly before tackling this:
Are we talking about sth. along the lines of substituting ScopeGroupKey with sth. like this?

pub struct Scope {
    name: ScopeName,
    groups: HashSet<GroupName>,
}

Uses Handler trait instead of Actor trait to define possible messages

One of the things I like about actix, is the possibility to define handlers for multiple messages per actor, which is something that erlang, caf and akka (iirc) allow as well. I know that this can be emulated with enums, but having it work without would be a good quality of life improvement.

Actix Example:

use actix::prelude::*;

// this is our Message
// we have to define the response type (rtype)
#[derive(Message)]
#[rtype(result = "usize")]
struct Sum(usize, usize);

// Actor definition
struct Calculator;

impl Actor for Calculator {
    type Context = Context<Self>;
}

// now we need to implement `Handler` on `Calculator` for the `Sum` message.
impl Handler<Sum> for Calculator {
    type Result = usize; // <- Message response type

    fn handle(&mut self, msg: Sum, ctx: &mut Context<Self>) -> Self::Result {
        msg.0 + msg.1
    }
}

#[actix::main] // <- starts the system and block until future resolves
async fn main() {
    let addr = Calculator.start();
    let res = addr.send(Sum(10, 5)).await; // <- send message and get future for result

    match res {
        Ok(result) => println!("SUM: {}", result),
        _ => println!("Communication to the actor has failed"),
    }
}

Full Example of Remote Actors with Supervision Trees

I've read a lot of the existing tests and example code and much (all?) of the documentation on ractor_cluster and RactorClusterMessage and I haven't been able to figure out how to get past problems with BytesConvertable.

It's not clear which trait bounds I haven't satisfied when I attempt to work with something like:

MidLevelActorMessage::GetLeaf( RpcReplyPort<ActorRef<LeafActorMessage>> )

I started with an existing example, the one that shows how to create a supervision tree, and then extend that to have the Actors be accessible from a different node, by deriving RactorClusterMessage and annotating GetLeaf with #[Rpc].

An example of how to use this code would be very helpful.

`CallResult` should return errors that occured handling the message

Is your feature request related to a problem? Please describe.
If a message handler fails when calling .call on an actor, CallResult::SenderError is returned with no error information.

Describe the solution you'd like
CallResult::Failed(err) should be added to the CallResult enum containing if an error occured when handling the message.

Ideally, I'd be able to write the following code:

#[async_trait::async_trait]
impl Actor for TestActor {
    async fn handle(...) -> Result<(), ActorProcessingErr> {
        Err(anyhow!("command failed"))
    }
}

let result = call!(actor, MessageFormat::TestRpc, "Something".to_string());
match result {
    Ok(value) => { println!("{value}"); }
    Err(CallError::Failed(err)) => // Handle the error returned in the handle function
    Err(CallError::Timeout) => // Call timed out
    Err(CallError::SenderError) => // Is this still needed if we add Failed?
}

Describe alternatives you've considered
Using a RpcReplyPort<Result<T, E>> to send the error back. However this makes it difficult to work with, as I cannot use the ? operator to simply return the error.

Add utility actors

We have a gen_server equivalent (basic actor), now we can expand on that

  • factory for a pool of workers local to a node #46 , #47

Other possible utility actors that might be helpful to have:

  • Timer (execute on a period ignore comp time, execute on a period handling comp time taking too long, execute with a delay between, etc)
  • FileWatcher? Watch a specified file (or directory) and notify someone on changes (output-port use case?)
  • Basic networking [tcp/udp] servers and clients? This however might require enforcing some kind of encoding (i.e. length pre-pended frames) or something

multi_call fails with index out of bounds consistently

The current implementation creates a Vec of the correct capacity, bind it to results, and then index it.

This won't work because that's the capacity, not the length, the error message discloses that index out of bounds: len is 0 while index is 0.

Simple solution: (this is not the only way to write it!)

let mut results = Vec::new();
results.resize_with(join_set.len(), || CallResult::Timeout);

Which will resize the results, allocate space as needed, and fill the space with Timeout's. Note that CallResult is not Clone so you can't just use vec!.

Re-starting a panicked Actor from a supervisor?

Is there any chance there might be some documentation that I've missed on how to re-start a panicked Actor once the supervisor is notified it has failed using the handle_supervisor_evt() function, or another method? I've been banging my head against doing this in the supervisor example for a while, but can't seem to properly update the held leaf_actor ID under MidLevelActorState, with a newly-spawn_linked() Leaf Actor, which then causes my code to panic.

I'm primarily just interested in dealing with a case where I've written an Actor, spawned it as a child of a supervisor, then after it's panicked (because I was lazy while doing error -handling and missed an unwrap or something), have the supervisor node catch the unhandled panic and just re-initialize the actor appropriately.

bug in monte_carlo example

Describe the bug
Monte_carlo example gives me an error like this:

    Finished dev [unoptimized + debuginfo] target(s) in 0.22s
     Running `target/debug/examples/monte_carlo`
thread 'tokio-runtime-worker' panicked at 'Failed to send message: Messaging(SendErr)', ractor/examples/monte_carlo.rs:105:14
thread 'tokio-runtime-worker' panicked at 'Failed to send message: Messaging(SendErr)', ractor/examples/monte_carlo.rs:105:14
thread 'thread 'tokio-runtime-workertokio-runtime-worker' panicked at '' panicked at 'Failed to send message: Messaging(SendErr)Failed to send message: Messaging(SendErr)', ', ractor/examples/monte_carlo.rsractor/examples/monte_carlo.rs::105105::1414

Seems like when child actor ends itself, parent actor ends as well, but there are still several child actors who tries to send messages to their parent

To Reproduce
Steps to reproduce the behavior:

  1. just downloaded repo and started with

alexromensky@Alexs-MacBook-Pro ractor % cargo run --example monte_carlo

Expected behavior
supposed to run without an error

Additional context
MacOS 13.5.2, rustc 1.72.0 (5680fa18f 2023-08-23)

Process group scopes

Our process groups don't have "scopes" like the Erlang ones (https://www.erlang.org/doc/man/pg.html)

As we move towards distributed actors, scopes will probably help limit which actors are being referenced in process groups and when.

This is a tracking issue to add scopes to our pg module.

protobuf-src build requires autotools, which is not available on windows (by default)

Description of the problem encountered

I tried to build examples with ractor and ractor_cluster on windows 10 machine
and encountered following error:

Compiling protobuf-src v1.1.0+21.5
error: failed to run custom build command for `protobuf-src v1.1.0+21.5`

Caused by:
  process didn't exit successfully: `D:\nolmelab\daily_rust\target\debug\build\protobuf-src-fa7f52d64a0497cb\build-script-build` (exit code: 101)
  --- stderr
  thread 'main' panicked at '
  `sh` is required to run `configure`

  build script failed, must exit now', C:\Users\keedongpark\.cargo\registry\src\index.crates.io-6f17d22bba15001f\autotools-0.2.6\src\lib.rs:781:5
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish... 

autotools requires configure & make, which are not available on windows by default.

Describe the solution you'd like
I tried to remove dependency on protobuf-src in ractor repository by removing
protobuf-src line in ractor_cluster/Cargo.toml and std::env::set_var("PROTOC", ... )
line in build.rs file.

Then the build complains about missing google.protobuf.Timestamp and some other files while compiling *.proto files. (I made protoc.exe available via PATH env variable)

If ractor_cluster requires some of the .proto files from protobuf and protoc only,
then I think the dependency on protobuf-src can be removed.

It will make using ractor_cluster on windows really simple.

Describe alternatives you've considered
I also consider that dependency on protobuf can be removed entirely using rust serialization
(binary or json) in platform compatible way. Then it can make building and using ractor_cluster easier than now still supporting (possibly) other languages and platforms.

Additional context

ActorRef::where_is returns None when actor should exist

From looking at the code, I think the error is that in ActorProperties the type_id comes from TActor::Msg where ActorRef::where_is expects the type_id to come from TActor itself.
Looking at the initial PR for ActorRef, type_id was first assigned from TActor: https://github.com/slawlor/ractor/pull/13/files#diff-13f6bef8e8c77187a4a13283938bbac0dbe687e3ef686ce925e1c49d079f4d5aR58
So I guess, it was just forgotten to update the ActorRef::where_is implementation after the type_id was changed?

This fails with a ActorAlreadyRegistered Error when called twice with the same id:

    let user = if let Some(user) = ActorRef::where_is(jwt.uid.clone()) {
      user
    } else {
      Actor::spawn(Some(jwt.uid.clone()), UserActor, ()).await.unwrap().0
    };

This works flawlessly

    let user = if let Some(user) = registry::where_is(jwt.uid.clone()) {
      user.into()
    } else {
      Actor::spawn(Some(jwt.uid.clone()), UserActor, ()).await.unwrap().0
    };

quickstart bugs

Small issues on https://slawlor.github.io/ractor/quickstart/ (I couldn't find a repo for these docs, so posting here):

  1. type MyFirstActor; should be struct MyFirstActor; (or pub struct, perhaps combined with pub enum)
  2. [tokio::main] should be #[tokio::main]

There are also warnings about unused variables, but I guess that's fine in a hello world example.

Thanks for a very interesting project!

How to communicate between local actors and remote actors across hosts using ractor?

Hello, I'm very interested in ractor and am keen to use it in my project. I've previously used C++ CAF, which is also an open-source actor framework inspired by Erlang's actor model. I've noticed many similarities between ractor and CAF, which allows me to get started with ractor more quickly. However, I couldn't find any examples regarding communication between local and remote actors. Could you provide me with some examples of how to communicate with remote actors in ractor and ractor_cluster? For instance: an actor on Host A communicating with an actor on Host B, where the actor on Host B is considered a remote actor from the perspective of Host A.

Add typeid to actor properties

To protect against runtime crashes due to downcasting to the wrong message type, we should add the type ID to the actor properties struct and check it every time and interaction is done with the actor properties, returning an error that someone tries to interact with the actor for an unsupported type

API improvement

While using ractor, I found some inconvinience while dealing with the API, especially DiscardHandler and WorkerBuilder. Is it possible to impl those two traits for closures? For now, I have these shim code:

fn worker_builder(&self) -> Box<dyn WorkerBuilder<SourceWorker>> {
        struct SourceWorkerBuilder {
            db: Collection<Record>,
        }

        impl WorkerBuilder<SourceWorker> for SourceWorkerBuilder {
            fn build(&self, id: WorkerId) -> SourceWorker {
                SourceWorker {
                    id,
                    db: self.db.clone(),
                }
            }
        }

        Box::new(SourceWorkerBuilder {
            db: self.db.clone(),
        })
    }

and when initializing:

let builder = self.worker_builder();
Actor::spawn(None, factory, builder).await?;

which if we can rewrite it with closure:

let builder = Box::new({
  let db = self.db.clone(); 
  move |id| SourceWorkerBuilder { id, db: db.clone() }
})
Actor::spawn(None, factory, builder).await?;

Also, it might be good if we can use

pub fn where_is<K: Borrow<ActorName>>(name: K) -> Option<ActorCell>

Which should work no matter what ActorName is because underlying API provided by DashMap.

Production. (Actix).

Hey there, I am coming from actix actors background, Suffering from some limitations, and I on search for an alternative or an easy convert.

I am going to state down my use case, and I wonder if it will be feasible to make the convert upon your answers. It is also good to know if the crate is being used on production.

  1. Websocket actor, handle read/write. With actix simple as ctx.addStream(), then implement the message types, for write add write part of the stream to the sate of the actor, and calling when needed.
  2. Db actor, handle db operations. here comes majore suffering with async/await/blocking thread ..etc, will share a snippets at the end.
  3. Also using tauri, so the runtime enviroment has to be shared, where as had a blummer back in while to figure actix runtime fix to make it work, which is still not compelling.
  4. some cpu intensive actors dealing with file system on seperate threads.
  5. back to 4 where as cross actor comunication through recipients or actor refs.

Not going to mention supervisoring as I already read that it is there.
Making the move to an already crate or using pure rust / tokio channels is the way to go for this project of mine, but knwoing a crate already has the potentials to solve the issues am facing will just make things easier, It will come at the end how much time the conversion of the project will take. So your answers are really important.

Thank you.

Blocking thread. // lack of async/await support.
I also tried atomicResponse but in that case I losse access to self, and being static issues.
Not trying to figure out a fix, as moving forward async/await is the future to go.

impl Handler<DbOperations> for McActor {
    type Result = String;
    //type Result = ResponseFuture<String>;
    fn handle(&mut self, msg: DbOperations, ctx: &mut Self::Context) -> Self::Result {
        return match msg {
             DbOperations::Get { key } => {
               block_on(async {
                    sqlx::query_as::<_, DbOperationValues>(select_query)
                        .bind(key)
                        .fetch_one(self.db.as_ref().unwrap())
                        .await
                        .unwrap()
                }).value
                 
            }
            DbOperations::Set { key, value } => {   
                block_on(async {
                    sqlx::query_as::<_, DbOperationValues>(insert_query)
                        .bind(key)
                        .bind(value)
                        .fetch_one(self.db.as_ref().unwrap())
                        .await
                        .unwrap()
                }).value
            }
        };
    }
}

Factory tweaks identified through other usages

  • Factories should keep a handle to their worker's JoinHandle<()> instance and block shutdown on successful shutdown of their workers. Otherwise there's a slight chance that process shutdown may occur prior to fully shutting down workers, resulting in a mem leak.
  • (wish) Add a builder pattern for factories

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.