Lauch scheduled task with actix and access to self - rust

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);
}
}

Related

How to use the typestate pattern in other struct

I want to use the typestate pattern to define several states that allow some exclusive operations on each of them.
I'm using traits instead of an enum to allow further customizations.
So, I'm able to use this pattern until I try to include it inside a struct (the Session part) that is mutated when files are added, changed or removed.
trait IssueState {}
struct Open;
impl IssueState for Open {}
struct WIP {
elapsed_time: u32,
}
impl IssueState for WIP {}
struct Closed {
elapsed_time: u32,
}
impl IssueState for Closed {}
struct Issue<T: IssueState + ?Sized> {
state: Box<T>,
comments: Vec<String>,
}
impl<T: IssueState> Issue<T> {
pub fn comment<S: Into<String>>(&mut self, comment: S) -> &mut Self {
self.comments.push(comment.into());
self
}
}
impl Issue<Open> {
pub fn new() -> Self {
Self {
state: Box::new(Open),
comments: vec![],
}
}
pub fn start(self) -> Issue<WIP> {
Issue {
state: Box::new(WIP { elapsed_time: 0 }),
comments: self.comments,
}
}
}
impl Issue<WIP> {
pub fn work(&mut self, time: u32) -> &mut Self {
self.state.elapsed_time += time;
self
}
pub fn done(self) -> Issue<Closed> {
let elapsed_time = self.state.elapsed_time;
Issue {
state: Box::new(Closed { elapsed_time }),
comments: self.comments,
}
}
}
impl Issue<Closed> {
pub fn elapsed(&self) -> u32 {
self.state.elapsed_time
}
}
struct Session<T: IssueState> {
user: String,
current_issue: Issue<T>,
}
impl<T: IssueState> Session<T> {
pub fn new<S: Into<String>>(user: S, issue: Issue<T>) -> Self {
Self {
user: user.into(),
current_issue: issue,
}
}
pub fn comment<S: Into<String>>(&mut self, comment: S) {
self.current_issue.comment(comment);
}
}
impl Session<WIP> {
pub fn work(&mut self, time: u32) {
self.current_issue.work(time);
}
}
trait Watcher {
fn watch_file_create(&mut self);
fn watch_file_change(&mut self);
fn watch_file_delete(&mut self);
}
impl<T: IssueState> Watcher for Session<T> {
fn watch_file_create(&mut self) {
self.current_issue = Issue::<Open>::new();
}
fn watch_file_change(&mut self) {}
fn watch_file_delete(&mut self) {}
}
fn main() {
let open = Issue::<Open>::new();
let mut wip = open.start();
wip.work(10).work(30).work(60);
let closed = wip.done();
println!("Elapsed {}", closed.elapsed());
let mut session = Session::new("Reviewer", closed);
session.comment("It is OK");
session.watch_file_create();
}
Rust Playground (original)
Rust Playground (edited)
What can I do to fix the problems?
Is the typestate pattern limited to only some situations that do not depend a lot on external events? I mean, I'm trying to use it for processing events, but is it a dead end?, why?
Your Session has a Issue<dyn IssueState> member, but you want to implement its work method by calling Issue<WIP>'s work method. The compiler complains, because an Issue<dyn IssueState> is not (necessarily) a Issue<WIP> and so does not implement that method.

Allowing extension (beyond the crate) of implementation with event loop

Within the crate we can happily do something like this:
mod boundary {
pub struct EventLoop;
impl EventLoop {
pub fn run(&self) {
for _ in 0..2 {
self.handle("bundled");
self.foo();
}
}
pub fn handle(&self, message: &str) {
println!("{} handling", message)
}
}
pub trait EventLoopExtend {
fn foo(&self);
}
}
use boundary::EventLoopExtend;
impl EventLoopExtend for boundary::EventLoop {
fn foo(&self) {
self.handle("extended")
}
}
fn main() {
let el = boundary::EventLoop{};
el.run();
}
But if mod boundary were a crate boundary we get error[E0117]: only traits defined in the current crate can be implemented for arbitrary types.
I gather that a potential solution to this could be the New Type idiom, so something like this:
mod boundary {
pub struct EventLoop;
impl EventLoop {
pub fn run(&self) {
for _ in 0..2 {
self.handle("bundled");
self.foo();
}
}
pub fn handle(&self, message: &str) {
println!("{} handling", message)
}
}
pub trait EventLoopExtend {
fn foo(&self);
}
impl EventLoopExtend for EventLoop {
fn foo(&self) {
self.handle("unimplemented")
}
}
}
use boundary::{EventLoop, EventLoopExtend};
struct EventLoopNewType(EventLoop);
impl EventLoopExtend for EventLoopNewType {
fn foo(&self) {
self.0.handle("extended")
}
}
fn main() {
let el = EventLoopNewType(EventLoop {});
el.0.run();
}
But then the problem here is that the extended trait behaviour isn't accessible from the underlying EventLoop instance.
I'm still quite new to Rust, so I'm sure I'm missing something obvious, I wouldn't be surprised if I need to take a completely different approach.
Specifically in my case, the event loop is actually from wgpu, and I'm curious if it's possible to build a library where end users can provide their own "render pass" stage.
Thanks to #AlexN's comment I dug deeper into the Strategy Pattern and found a solution:
mod boundary {
pub struct EventLoop<'a, T: EventLoopExtend> {
extension: &'a T
}
impl<'a, T: EventLoopExtend> EventLoop<'a, T> {
pub fn new(extension: &'a T) -> Self {
Self { extension }
}
pub fn run(&self) {
for _ in 0..2 {
self.handle("bundled");
self.extension.foo(self);
}
}
pub fn handle(&self, message: &str) {
println!("{} handling", message)
}
}
pub trait EventLoopExtend {
fn foo<T: EventLoopExtend>(&self, el: &EventLoop<T>) {
el.handle("unimplemented")
}
}
}
use boundary::{EventLoop, EventLoopExtend};
struct EventLoopExtension;
impl EventLoopExtend for EventLoopExtension {
fn foo<T: EventLoopExtend>(&self, el: &EventLoop<T>) {
el.handle("extended")
}
}
fn main() {
let el = EventLoop::new(&EventLoopExtension {});
el.run();
}
The basic idea is to use generics with a trait bound. I think the first time I looked into this approach I was worried about type recursion. But it turns out passing the EventLoop object as an argument to EventLoopExtend trait methods is perfectly reasonable.

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");
}

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

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
}
}

Borrows and ownership of object on thread

Sorry for newbie question. The error here is
<anon>:30:5: 30:17 error: cannot borrow immutable borrowed content as mutable
<anon>:30 routing_node.put(3);
^^^^^^^^^^^^
I have tried many things to work around this but know for sure this is a simple error. Any help much appreciated.
use std::thread;
use std::thread::spawn;
use std::sync::Arc;
struct RoutingNode {
data: u16
}
impl RoutingNode {
pub fn new() -> RoutingNode {
RoutingNode { data: 0 }
}
pub fn run(&self) {
println!("data : {}", self.data);
}
pub fn put(&mut self, increase: u16) {
self.data += increase;
}
}
fn main() {
let mut routing_node = Arc::new(RoutingNode::new());
let mut my_node = routing_node.clone();
{
spawn(move || {my_node.run(); });
}
routing_node.put(3);
}
Arc isn't allowing to mutate of it's inner state, even if container is marked as mutable. You should use one of Cell, RefCell or Mutex. Both Cell and RefCell are non-threadsafe so you should use Mutex (last paragraph in docs).
Example:
use std::thread::spawn;
use std::sync::Mutex;
use std::sync::Arc;
struct RoutingNode {
data: u16,
}
impl RoutingNode {
pub fn new() -> Self { RoutingNode { data: 0, } }
pub fn run(&self) { println!("data : {}" , self.data); }
pub fn put(&mut self, increase: u16) { self.data += increase; }
}
fn main() {
let routing_node = Arc::new(Mutex::new(RoutingNode::new()));
let my_node = routing_node.clone();
let thread = spawn(move || { my_node.lock().unwrap().run(); });
routing_node.lock().unwrap().put(3);
let _ = thread.join();
}
Playpen

Resources