How to send a message to another actor from actix SyncContext? - rust

I would like to implement a long running background task that can report progress to other Actors. I already accomplished that.
But I would also like to be able to cancel the long running background task again.
What I got so far is this:
use actix::prelude::*;
struct Worker {}
impl Actor for Worker {
type Context = SyncContext<Self>;
}
struct Manager {
worker: Addr<Worker>,
}
impl Actor for Manager {
type Context = Context<Self>;
}
impl Supervised for Manager {}
impl SystemService for Manager {
fn service_started(&mut self, _ctx: &mut Context<Self>) {}
}
struct Work {}
#[derive(Message)]
#[rtype(result = "()")]
struct PerformWork(Work);
#[derive(Message)]
#[rtype(result = "()")]
pub struct ReportProgress(i32);
impl Handler<PerformWork> for Worker {
type Result = ();
fn handle(&mut self, msg: PerformWork, ctx: &mut Self::Context) -> Self::Result {
for i in 0..10000000 {
// Report progress
Manager::from_registry().do_send(ReportProgress(i));
// Do some very slow I/O.
thread::sleep(time::Duration::from_millis(1));
}
}
}
impl Handler<ReportProgress> for Manager {
type Result = ();
fn handle(&mut self, msg: ReportProgress, ctx: &mut Self::Context) -> Self::Result {
// Do something with the progress here
}
}
The Manager also handles a Message that sends the PerformWork Message to the Worker.
I thought of giving the ReportProgress Message a bool return type that would allow the Worker decide if it should break out of its loop. However, I cannot manage sending a Message with a return result to the Manager.
Using send() instead of do_send() returns a Future that I cannot resolve within the SyncContext.
Any ideas are very much appreciated.
A bit more background:
the really slow I/O is serial communication.
actix is version 0.10

I found a solution, but I am not convinced that it is a good one.
I added an Arc<AtomicBool>> that is passed to the Worker. The Manager keeps a reference to the AtomicBool and can modify it. The Worker breaks out of its loop if the AtomicBool is modified by the Manager.
use actix::prelude::*;
use std::sync::atomic::{AtomicBool, Ordering};
struct Worker {}
impl Actor for Worker {
type Context = SyncContext<Self>;
}
struct Manager {
worker: Addr<Worker>,
}
impl Actor for Manager {
type Context = Context<Self>;
}
impl Supervised for Manager {}
impl SystemService for Manager {
fn service_started(&mut self, _ctx: &mut Context<Self>) {}
}
struct Work {}
#[derive(Message)]
#[rtype(result = "()")]
struct PerformWork(Work, Arc<AtomicBool>>);
#[derive(Message)]
#[rtype(result = "()")]
pub struct ReportProgress(i32);
impl Handler<PerformWork> for Worker {
type Result = ();
fn handle(&mut self, msg: PerformWork, ctx: &mut Self::Context) -> Self::Result {
for i in 0..10000000 {
// Report progress
Manager::from_registry().do_send(ReportProgress(i));
if msg.1.load(Ordering::Relaxed) {
break;
}
// Do some very slow I/O.
thread::sleep(time::Duration::from_millis(1));
}
}
}
impl Handler<ReportProgress> for Manager {
type Result = ();
fn handle(&mut self, msg: ReportProgress, ctx: &mut Self::Context) -> Self::Result {
// Do something with the progress here
}
}

Related

Using a trait object in a background job (different thread)

I want to have a background worker which uses a trait implementation / object for some time. The background worker owns this object as long as it is used. After the background worker is "destroyed", the object should be free to be used again.
I tried to make all the things with async/await, but it produced some more problems. Therefore, I use plain threads to create kind of a minimal example. First I also used Box<&dyn mut...> to pass the object to the background worker, but I think that is not even needed.
My minimal example contains a MyWriter-trait which can write string to somewhere. There exists one implementation which writes strings to stdout. A background-worker uses this writer for a background-job. The worker has a start-method to start the job and a stop-method to join it (in my real code I would use a channel to send a stop-info to the worker and joining then).
I'll post the code and then a description with my problems:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a01745c15ba1088acd2e3d287d60e270
use std::sync::Arc;
use std::sync::Mutex;
use std::thread::{spawn, JoinHandle};
/* Trait + an implementation */
trait MyWriter {
fn write(&mut self, text: &str);
}
struct StdoutWriter {}
impl StdoutWriter {
pub fn new() -> Self {
Self {}
}
}
impl MyWriter for StdoutWriter {
fn write(&mut self, text: &str) {
println!("{}", text);
}
}
/* A background job which uses a "MyWriter" */
struct BackgroundJob<'a> {
writer: Arc<Mutex<&'a dyn MyWriter>>,
job: Option<JoinHandle<()>>,
}
impl<'a> BackgroundJob<'a> {
pub fn new(writer: &'a mut dyn MyWriter) -> Self {
Self {
writer: Arc::new(Mutex::new(writer)),
job: None,
}
}
pub fn start(&mut self) {
assert!(self.job.is_none());
let writer = &self.writer;
self.job = Some(std::thread::spawn(move || {
// this background job uses "writer"
let mut my_writer = writer.lock().unwrap();
my_writer.write("x");
// do something
my_writer.write("y");
}));
}
pub fn stop(&mut self) {
if let Some(job) = self.job {
job.join().unwrap();
self.job = None;
}
}
}
/* Using BackgroundJob */
fn main() {
let mut writer = StdoutWriter::new();
writer.write("a");
{
let mut job = BackgroundJob::new(&mut writer);
// inside this block, writer is owned by "job"
job.start();
job.stop();
}
// writer should be usable again
writer.write("b");
}
The desired output on stdout is a\nx\ny\nz\n, but the program does not even compile. My main problem is that (dyn MyWriter + 'a) cannot be shared between threads safely (compiler error).
How can I implement Send / Sync for a trait? It does not seem to be possible. Actually, I assumed it should be ok if the object (or a ref.) is inside Arc<Mutex<...>>, but that does not seem to be sufficient. Why not?
Maybe someone has an idea how this can be fixed or even more important what exactly is the underlying issue?
Putting a reference in an Arc doesn't work. Since the Arc can be kept alive indefinitely simply by cloning it, the reference could easily outlive whatever it was borrowed from, so that can't compile. You need to put an owned object in the Arc, such as Box<dyn MyWriter>. (Ideally you'd just use Arc<dyn MyWriter>, but that would conflict with returning the writer from the BackgroundJob, as shown below.)
Since you can't borrow from writer in main, you must move it into the BackgroundJob. But at this point you've relinquished ownership over writer, having moved the value to BackgroundJob, so your only option is to have BackgroundJob return the writer. However, since BackgroundJob keeps its writer behind a trait object, it can only give back the Box<dyn MyWriter> it stores, not the original StdoutWriter.
Here is the version that works that way, retaining type erasure and giving back the type-erased writer:
// Trait definition and StdoutWriter implementation unchanged
struct BackgroundJob {
writer: Arc<Mutex<Box<dyn MyWriter + Send>>>,
job: Option<JoinHandle<()>>,
}
impl BackgroundJob {
pub fn new(writer: Box<dyn MyWriter + Send>) -> Self {
Self {
writer: Arc::new(Mutex::new(writer)),
job: None,
}
}
pub fn start(&mut self) {
assert!(self.job.is_none());
let writer = Arc::clone(&self.writer);
self.job = Some(std::thread::spawn(move || {
// this background job uses "writer"
let mut my_writer = writer.lock().unwrap();
my_writer.write("x");
// do something
my_writer.write("y");
}));
}
pub fn stop(&mut self) {
if let Some(job) = self.job.take() {
job.join().unwrap();
}
}
pub fn into_writer(self) -> Box<dyn MyWriter> {
Arc::try_unwrap(self.writer)
.unwrap_or_else(|_| panic!())
.into_inner()
.unwrap()
}
}
fn main() {
let mut writer = StdoutWriter::new();
writer.write("a");
let mut writer = {
let mut job = BackgroundJob::new(Box::new(writer));
job.start();
job.stop();
job.into_writer()
};
writer.write("b");
}
Playground
A version that gave back the writer of the same type would have to give up on type erasure and be generic over the writer type. Though a bit more complex, its ownership semantics would be very close (at least conceptually) to what you originally envisioned:
struct BackgroundJob<W> {
writer: Arc<Mutex<W>>,
job: Option<JoinHandle<()>>,
}
impl<W: MyWriter + Send + 'static> BackgroundJob<W> {
pub fn new(writer: W) -> Self {
Self {
writer: Arc::new(Mutex::new(writer)),
job: None,
}
}
// start() and stop() are unchanged
pub fn into_writer(self) -> W {
Arc::try_unwrap(self.writer)
.unwrap_or_else(|_| panic!())
.into_inner()
.unwrap()
}
}
fn main() {
let mut writer = StdoutWriter::new();
writer.write("a");
{
// inside this block, writer is moved into "job"
let mut job = BackgroundJob::new(writer);
job.start();
job.stop();
// reclaim the writer
writer = job.into_writer();
}
writer.write("b");
}
Playground
The main issue is that you want to pass a reference to the thread. The problem with that approach is that the thread can outlive the referenced object. Obviously this does not happen in your case, but the rust compiler cannot reason about that.
The solution to that problem is to use Arc<Mutex<dyn MyType>> instead of Arc<Mutex<&dyn MyType>> - no lifetimes - no problems.
The next issue is with Mutex<T> - it can be send across threads only if T can. So you have to make T, in your case dyn MyType, implement Send. This can be done in two ways:
Make MyType require Send - in that case that trait can be implemented only by Send objects:
trait MyWriter : Send{
fn write(&mut self, text: &str);
}
Or use an additional trait bound - in that case your trait is less restrictive, but you must always specify MyTrait + Send when you want to send it across threads:
Arc<Mutex<dyn MyWriter + Send>>
So far so good, but now your new() method does not work, because dyn MyWriter is not Sized. In order to fix that you have to make your method generic:
pub fn new<T: MyWriter + Send>(writer: T) -> Self {
Self {
writer: Arc::new(Mutex::new(writer)),
job: None,
}
}
or directly pass an Arc<Mutex<dyn MyWriter + Send>>:
pub fn new(writer: Arc<Mutex<dyn MyWriter + Send>>) -> Self {
Self { writer, job: None }
}
Full working code
use std::sync::Arc;
use std::sync::Mutex;
use std::thread::JoinHandle;
trait MyWriter {
fn write(&mut self, text: &str);
}
struct StdoutWriter {}
impl StdoutWriter {
pub fn new() -> Self {
Self {}
}
}
impl MyWriter for StdoutWriter {
fn write(&mut self, text: &str) {
println!("{}", text);
}
}
/* A background job which uses a "MyWriter" */
struct BackgroundJob {
writer: Arc<Mutex<dyn MyWriter + Send>>,
job: Option<JoinHandle<()>>,
}
impl BackgroundJob {
pub fn new(writer: Arc<Mutex<dyn MyWriter + Send>>) -> Self {
Self { writer, job: None }
}
pub fn start(&mut self) {
assert!(self.job.is_none());
let writer = self.writer.clone();
self.job = Some(std::thread::spawn(move || {
let mut my_writer = writer.lock().unwrap();
my_writer.write("x");
// do something
my_writer.write("y");
}));
}
pub fn stop(&mut self) {
if let Some(job) = self.job.take() {
job.join().unwrap();
}
}
}
fn main() {
let mut writer = StdoutWriter::new();
writer.write("a");
let writer = Arc::new(Mutex::new(writer));
{
let mut job = BackgroundJob::new(writer.clone());
// inside this block, writer is owned by "job"
job.start();
job.stop();
}
// you have to acquire the lock in order to use the writer
writer.lock().unwrap_or_else(|e| e.into_inner()).write("b");
}

Why is receiver declared with Receiver<Job>?

Here is the full code from the last chapter in the Rust book, under multi-threading a web-server.
use std::thread;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::mpsc;
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(job).unwrap();
}
}
struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || loop {
let job = receiver.lock().unwrap().recv().unwrap();
println!("Worker {} got a job; executing.", id);
job();
});
Worker { id, thread }
}
}
I was wondering why the new() function when implementing Worker receives an Arc<Mutex<mpsc::Receiver<Job>>> type.
I would assume it would just be Arc<Mutex<mpsc::Receiver>>. Same with sender in ThreadPool. How does mpsc::channel() know to return those types in the ThreadPool implementation?
Up front T in Sender<T> (and Receiver<T>) is unknown. However, when you attempt to create a ThreadPool with sender, then the compiler can infer that T must be Job. Because ThreadPool's sender field is a Sender<Job>.
Given that the mpsc::channel() returns a Sender<T> and Receiver<T> with the same T, then if Sender<T> is Sender<Job>, then Receiver<T> must also be Receiver<Job>.
This becomes apparent when you take a look at the function signature of mpsc::channel():
pub fn channel<T>() -> (Sender<T>, Receiver<T>)

Lauch scheduled task with actix and access to self

new on rust, i have some problem to handle async and lifetime in rust.
I try to run a scheduled task into an Actix runtime (actix-web)
I'm blocked cause of lifetime.
I got this errror:
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
-> this.execute().into_actor(this)
Code :
use actix::prelude::*;
use std::time::Duration;
pub struct SleepUnusedCloneTask {
pub count: i32
}
impl Actor for SleepUnusedCloneTask {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
ctx.run_interval(Duration::from_millis(100), |this, ctx| {
ctx.spawn(
this.execute().into_actor(this)
);
});
}
}
impl SleepUnusedCloneTask {
async fn execute(&mut self) {
println!("Flood: {}", self.count);
}
}
And in my main function :
let _sleep_unused_task = SleepUnusedCloneTask::create(move |_| {
SleepUnusedCloneTask { count: 5 }
});
Can be solve with Arc and clone ;)
use actix::prelude::*;
use std::time::Duration;
pub struct Task {
pub count: Arc<i32>
}
impl Actor for Task {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
ctx.run_interval(Duration::from_millis(100), |this, ctx| {
Arbiter::spawn(Task::execute(this.count.clone()));
});
}
}
impl Task {
async fn execute(count: Arc<i32>) {
println!("Flood: {}", self.count);
}
}

How to make a subscriber object with RAII properties?

I'm talking to some hardware over a link with kind of a publisher/subscriber model. In C++, I did subscriptions with RAII to remember to always unsubscribe, but I can't seem to get the ownerships/borrows right in rust.
Naively, this is something like what I would like to do. send and receive probably needs to be &mut self, so as I understand, Subscription needs mutable access to the Transport.
struct Transport;
impl Transport {
pub fn send(&mut self, cmd: &str) { unimplemented!() }
pub fn subscribe(&mut self, cmd: &str) -> Subscription {
self.send("subscribe-with-params");
Subscription { trans: &mut self }
}
}
struct Subscription {
trans: &mut Transport,
}
impl Drop for Subscription {
fn drop(&mut self) {
self.trans.send("unsubscribe-with params");
}
}
impl Subscription {
fn receive(&mut self) -> &[u8] { /*blocking wait for data*/ }
}
fn test(t: Transport) {
// Need to subscribe before command, as command might generate status messages
let mut status_sub = t.subscribe("status-message");
{
let mut short_lived_sub = t.subscribe("command_reply");
t.send("command");
short_lived_sub.receive(); // Wait for ack
}
loop {
println!("{:?}", status_sub.receive());
/*processing of status */
}
}
There are at least two problems here. One is how Subscription should keep some reference to it's "parent", the Transport, and another is the problem in fn test that I can't borrow Transport twice for two different subscriptions.
I have a feeling that I'm kind of asking the wrong question here, so maybe there's a good way of approaching this in a different way entirely?
It is problematic for your Subscription to hold a mutable reference to Transport because, as you discovered, you'll only be able to hold one at a time and you won't be able to do anything else with the transport in the meantime.
Instead, you can use an Rc (for shared ownership) and RefCell (for interior mutability):
use std::rc::Rc;
use std::cell::RefCell;
struct TransportInner;
pub struct Transport {
inner: Rc<RefCell<TransportInner>>,
}
pub struct Subscription {
trans: Rc<RefCell<TransportInner>>
}
impl TransportInner {
pub fn send(&mut self, cmd: &str) { }
}
impl Transport {
pub fn send(&mut self, cmd: &str) {
self.inner.borrow_mut().send(cmd)
}
pub fn subscribe(&mut self, cmd: &str) -> Subscription {
self.send("subscribe-with-params");
Subscription { trans: Rc::clone(&self.inner) }
}
}
impl Drop for Subscription {
fn drop(&mut self) {
self.trans.borrow_mut().send("unsubscribe-with params");
}
}
You can do this without splitting it into an inner and outer structure, but that would require the user to access the Transport via an Rc too, which could be unwieldy.
If you need this to work across threads, you should use Arc<Mutex> instead.

How do I safely use an object while it is mutably borrowed?

I have this code:
use std::sync::atomic::{AtomicIsize, Ordering};
#[derive(Default)]
pub struct Worker {
work: Vec<u32>,
progress: AtomicIsize,
}
impl Worker {
fn do_work(&mut self) {
self.work.push(0u32);
self.progress.store(self.progress.load(Ordering::SeqCst) + 1, Ordering::SeqCst);
}
fn get_progress(&self) -> isize {
self.progress.load(Ordering::SeqCst)
}
}
pub struct Manager<CB: FnMut()> {
cb: CB
}
impl<CB: FnMut()> Manager<CB> {
fn do_a_bit_more_work(&mut self) {
(self.cb)();
}
}
fn main() {
let mut worker = Worker::default();
let mut manager = Manager {
cb: || worker.do_work()
};
while worker.get_progress() < 100 {
manager.do_a_bit_more_work();
}
}
That is, I have some manager that calls a callback to do some work. I want the callback to be Worker::do_work() and that function updates the members of Worker so it needs &mut self. However once I pass worker.do_work() to the manager it means worker is mutably borrowed so I can never use it again.
I want to use it again to check progress, and maybe change its behaviour. I can use atomic operations and mutexes and so on to try to make sure it is safe to do so, but how can I tell Rust to allow this without getting the cannot borrow X as immutable because it is also borrowed as mutable error?
I'm guessing it is something to do with Cell or RefCell but I can't work it out.
Here's the simpler example I'm going to use:
struct Manager<F> {
cb: F,
}
impl<F> Manager<F>
where F: FnMut()
{
fn do_a_bit_more_work(&mut self) { (self.cb)() }
}
struct Worker;
impl Worker {
fn do_work(&mut self) {}
fn get_progress(&self) -> u8 { 100 }
}
fn main() {
let mut worker = Worker;
let mut manager = Manager {
cb: || worker.do_work()
};
while worker.get_progress() < 100 {
manager.do_a_bit_more_work();
}
}
Adding RefCell allows it to compile:
use std::cell::RefCell;
fn main() {
let worker = RefCell::new(Worker);
let mut manager = Manager {
cb: || worker.borrow_mut().do_work()
};
while worker.borrow().get_progress() < 100 {
manager.do_a_bit_more_work();
}
}
Now the closure borrows an immutable reference of the RefCell<Worker> and checking for an exclusive mutable borrow moves from compile time to runtime.
Of course, RefCell isn't required to solve the problem, but avoiding RefCell does mean you have to look at the problem from a different direction. One solution is instead of keeping the Worker, give it to the Manager. Then borrow it back as needed:
trait DoWork {
fn do_work(&mut self);
}
struct Manager<T> {
work: T,
}
impl<T> Manager<T>
where T: DoWork
{
fn do_a_bit_more_work(&mut self) {
self.work.do_work()
}
fn inspect<F, U>(&self, mut f: F) -> U
where F: FnMut(&T) -> U
{
f(&self.work)
}
// Optionally
// fn inspect_mut<F, U>(&mut self, mut f: F) -> U
// where F: FnMut(&mut T) -> U
// {
// f(&mut self.work)
// }
fn into_inner(self) -> T {
self.work
}
}
struct Worker;
impl Worker {
fn get_progress(&self) -> u8 {
100
}
}
impl DoWork for Worker {
fn do_work(&mut self) {}
}
fn main() {
let worker = Worker;
let mut manager = Manager { work: worker };
while manager.inspect(|w| w.get_progress()) < 100 {
manager.do_a_bit_more_work();
}
let worker = manager.into_inner();
}

Resources