mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
allow concurrency in service actor (#2592)
This commit is contained in:
4
core/Cargo.lock
generated
4
core/Cargo.lock
generated
@@ -4968,9 +4968,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.34.0"
|
version = "1.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
|
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
|||||||
stderrlog = "0.5.4"
|
stderrlog = "0.5.4"
|
||||||
tar = "0.4.40"
|
tar = "0.4.40"
|
||||||
thiserror = "1.0.49"
|
thiserror = "1.0.49"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1.37", features = ["full"] }
|
||||||
tokio-rustls = "0.25.0"
|
tokio-rustls = "0.25.0"
|
||||||
tokio-socks = "0.5.1"
|
tokio-socks = "0.5.1"
|
||||||
tokio-stream = { version = "0.1.14", features = ["io-util", "sync", "net"] }
|
tokio-stream = { version = "0.1.14", features = ["io-util", "sync", "net"] }
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -154,7 +155,7 @@ impl S9pk<Section<MultiCursorFile>> {
|
|||||||
i.strip_prefix(&format!("start9/{}/", manifest.id))
|
i.strip_prefix(&format!("start9/{}/", manifest.id))
|
||||||
.map(|s| s.to_owned())
|
.map(|s| s.to_owned())
|
||||||
}) {
|
}) {
|
||||||
new_manifest.images.push(image.parse()?);
|
new_manifest.images.insert(image.parse()?);
|
||||||
let sqfs_path = images_dir.join(&image).with_extension("squashfs");
|
let sqfs_path = images_dir.join(&image).with_extension("squashfs");
|
||||||
let image_name = format!("start9/{}/{}:{}", manifest.id, image, manifest.version);
|
let image_name = format!("start9/{}/{}:{}", manifest.id, image, manifest.version);
|
||||||
let id = String::from_utf8(
|
let id = String::from_utf8(
|
||||||
@@ -334,7 +335,7 @@ impl From<ManifestV1> for Manifest {
|
|||||||
marketing_site: value.marketing_site.unwrap_or_else(|| default_url.clone()),
|
marketing_site: value.marketing_site.unwrap_or_else(|| default_url.clone()),
|
||||||
donation_url: value.donation_url,
|
donation_url: value.donation_url,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
images: Vec::new(),
|
images: BTreeSet::new(),
|
||||||
assets: value
|
assets: value
|
||||||
.volumes
|
.volumes
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use helpers::const_true;
|
use helpers::const_true;
|
||||||
@@ -43,9 +43,9 @@ pub struct Manifest {
|
|||||||
#[ts(type = "string | null")]
|
#[ts(type = "string | null")]
|
||||||
pub donation_url: Option<Url>,
|
pub donation_url: Option<Url>,
|
||||||
pub description: Description,
|
pub description: Description,
|
||||||
pub images: Vec<ImageId>,
|
pub images: BTreeSet<ImageId>,
|
||||||
pub assets: Vec<VolumeId>, // TODO: AssetsId
|
pub assets: BTreeSet<VolumeId>, // TODO: AssetsId
|
||||||
pub volumes: Vec<VolumeId>,
|
pub volumes: BTreeSet<VolumeId>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub alerts: Alerts,
|
pub alerts: Alerts,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|||||||
@@ -4,19 +4,27 @@ use models::{ActionId, ProcedureName};
|
|||||||
|
|
||||||
use crate::action::ActionResult;
|
use crate::action::ActionResult;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::service::config::GetConfig;
|
||||||
|
use crate::service::dependencies::DependencyConfig;
|
||||||
use crate::service::{Service, ServiceActor};
|
use crate::service::{Service, ServiceActor};
|
||||||
use crate::util::actor::{BackgroundJobs, Handler};
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::util::actor::{ConflictBuilder, Handler};
|
||||||
|
|
||||||
struct Action {
|
pub(super) struct Action {
|
||||||
id: ActionId,
|
id: ActionId,
|
||||||
input: Value,
|
input: Value,
|
||||||
}
|
}
|
||||||
impl Handler<Action> for ServiceActor {
|
impl Handler<Action> for ServiceActor {
|
||||||
type Response = Result<ActionResult, Error>;
|
type Response = Result<ActionResult, Error>;
|
||||||
|
fn conflicts_with(_: &Action) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::everything()
|
||||||
|
.except::<GetConfig>()
|
||||||
|
.except::<DependencyConfig>()
|
||||||
|
}
|
||||||
async fn handle(
|
async fn handle(
|
||||||
&mut self,
|
&mut self,
|
||||||
Action { id, input }: Action,
|
Action { id, input }: Action,
|
||||||
_: &mut BackgroundJobs,
|
_: &BackgroundJobQueue,
|
||||||
) -> Self::Response {
|
) -> Self::Response {
|
||||||
let container = &self.0.persistent_container;
|
let container = &self.0.persistent_container;
|
||||||
container
|
container
|
||||||
|
|||||||
@@ -3,19 +3,24 @@ use std::time::Duration;
|
|||||||
use models::ProcedureName;
|
use models::ProcedureName;
|
||||||
|
|
||||||
use crate::config::action::ConfigRes;
|
use crate::config::action::ConfigRes;
|
||||||
use crate::config::{action::SetResult, ConfigureContext};
|
use crate::config::ConfigureContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::service::dependencies::DependencyConfig;
|
||||||
use crate::service::{Service, ServiceActor};
|
use crate::service::{Service, ServiceActor};
|
||||||
use crate::util::actor::{BackgroundJobs, Handler};
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::util::actor::{ConflictBuilder, Handler};
|
||||||
use crate::util::serde::NoOutput;
|
use crate::util::serde::NoOutput;
|
||||||
|
|
||||||
struct Configure(ConfigureContext);
|
pub(super) struct Configure(ConfigureContext);
|
||||||
impl Handler<Configure> for ServiceActor {
|
impl Handler<Configure> for ServiceActor {
|
||||||
type Response = Result<(), Error>;
|
type Response = Result<(), Error>;
|
||||||
|
fn conflicts_with(_: &Configure) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::everything().except::<DependencyConfig>()
|
||||||
|
}
|
||||||
async fn handle(
|
async fn handle(
|
||||||
&mut self,
|
&mut self,
|
||||||
Configure(ConfigureContext { timeout, config }): Configure,
|
Configure(ConfigureContext { timeout, config }): Configure,
|
||||||
_: &mut BackgroundJobs,
|
_: &BackgroundJobQueue,
|
||||||
) -> Self::Response {
|
) -> Self::Response {
|
||||||
let container = &self.0.persistent_container;
|
let container = &self.0.persistent_container;
|
||||||
let package_id = &self.0.id;
|
let package_id = &self.0.id;
|
||||||
@@ -41,10 +46,13 @@ impl Handler<Configure> for ServiceActor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GetConfig;
|
pub(super) struct GetConfig;
|
||||||
impl Handler<GetConfig> for ServiceActor {
|
impl Handler<GetConfig> for ServiceActor {
|
||||||
type Response = Result<ConfigRes, Error>;
|
type Response = Result<ConfigRes, Error>;
|
||||||
async fn handle(&mut self, _: GetConfig, _: &mut BackgroundJobs) -> Self::Response {
|
fn conflicts_with(_: &GetConfig) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::nothing().except::<Configure>()
|
||||||
|
}
|
||||||
|
async fn handle(&mut self, _: GetConfig, _: &BackgroundJobQueue) -> Self::Response {
|
||||||
let container = &self.0.persistent_container;
|
let container = &self.0.persistent_container;
|
||||||
container
|
container
|
||||||
.execute::<ConfigRes>(
|
.execute::<ConfigRes>(
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::service::config::GetConfig;
|
||||||
|
use crate::service::dependencies::DependencyConfig;
|
||||||
use crate::service::start_stop::StartStop;
|
use crate::service::start_stop::StartStop;
|
||||||
use crate::service::transition::TransitionKind;
|
use crate::service::transition::TransitionKind;
|
||||||
use crate::service::{Service, ServiceActor};
|
use crate::service::{Service, ServiceActor};
|
||||||
use crate::util::actor::{BackgroundJobs, Handler};
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::util::actor::{ConflictBuilder, Handler};
|
||||||
|
|
||||||
struct Start;
|
pub(super) struct Start;
|
||||||
impl Handler<Start> for ServiceActor {
|
impl Handler<Start> for ServiceActor {
|
||||||
type Response = ();
|
type Response = ();
|
||||||
async fn handle(&mut self, _: Start, _: &mut BackgroundJobs) -> Self::Response {
|
fn conflicts_with(_: &Start) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::everything()
|
||||||
|
.except::<GetConfig>()
|
||||||
|
.except::<DependencyConfig>()
|
||||||
|
}
|
||||||
|
async fn handle(&mut self, _: Start, _: &BackgroundJobQueue) -> Self::Response {
|
||||||
self.0.persistent_container.state.send_modify(|x| {
|
self.0.persistent_container.state.send_modify(|x| {
|
||||||
x.desired_state = StartStop::Start;
|
x.desired_state = StartStop::Start;
|
||||||
});
|
});
|
||||||
@@ -23,7 +31,12 @@ impl Service {
|
|||||||
struct Stop;
|
struct Stop;
|
||||||
impl Handler<Stop> for ServiceActor {
|
impl Handler<Stop> for ServiceActor {
|
||||||
type Response = ();
|
type Response = ();
|
||||||
async fn handle(&mut self, _: Stop, _: &mut BackgroundJobs) -> Self::Response {
|
fn conflicts_with(_: &Stop) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::everything()
|
||||||
|
.except::<GetConfig>()
|
||||||
|
.except::<DependencyConfig>()
|
||||||
|
}
|
||||||
|
async fn handle(&mut self, _: Stop, _: &BackgroundJobQueue) -> Self::Response {
|
||||||
let mut transition_state = None;
|
let mut transition_state = None;
|
||||||
self.0.persistent_container.state.send_modify(|x| {
|
self.0.persistent_container.state.send_modify(|x| {
|
||||||
x.desired_state = StartStop::Stop;
|
x.desired_state = StartStop::Stop;
|
||||||
|
|||||||
@@ -5,22 +5,26 @@ use models::{PackageId, ProcedureName};
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::service::{Service, ServiceActor};
|
use crate::service::{Service, ServiceActor};
|
||||||
use crate::util::actor::{BackgroundJobs, Handler};
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::util::actor::{ConflictBuilder, Handler};
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
|
|
||||||
struct DependencyConfig {
|
pub(super) struct DependencyConfig {
|
||||||
dependency_id: PackageId,
|
dependency_id: PackageId,
|
||||||
remote_config: Option<Config>,
|
remote_config: Option<Config>,
|
||||||
}
|
}
|
||||||
impl Handler<DependencyConfig> for ServiceActor {
|
impl Handler<DependencyConfig> for ServiceActor {
|
||||||
type Response = Result<Option<Config>, Error>;
|
type Response = Result<Option<Config>, Error>;
|
||||||
|
fn conflicts_with(_: &DependencyConfig) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::nothing()
|
||||||
|
}
|
||||||
async fn handle(
|
async fn handle(
|
||||||
&mut self,
|
&mut self,
|
||||||
DependencyConfig {
|
DependencyConfig {
|
||||||
dependency_id,
|
dependency_id,
|
||||||
remote_config,
|
remote_config,
|
||||||
}: DependencyConfig,
|
}: DependencyConfig,
|
||||||
_: &mut BackgroundJobs,
|
_: &BackgroundJobQueue,
|
||||||
) -> Self::Response {
|
) -> Self::Response {
|
||||||
let container = &self.0.persistent_container;
|
let container = &self.0.persistent_container;
|
||||||
container
|
container
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ use start_stop::StartStop;
|
|||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::config::action::ConfigRes;
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::core::rpc_continuations::RequestGuid;
|
use crate::core::rpc_continuations::RequestGuid;
|
||||||
use crate::db::model::package::{
|
use crate::db::model::package::{
|
||||||
@@ -28,7 +27,9 @@ use crate::service::service_map::InstallProgressHandles;
|
|||||||
use crate::service::transition::TransitionKind;
|
use crate::service::transition::TransitionKind;
|
||||||
use crate::status::health_check::HealthCheckResult;
|
use crate::status::health_check::HealthCheckResult;
|
||||||
use crate::status::MainStatus;
|
use crate::status::MainStatus;
|
||||||
use crate::util::actor::{Actor, BackgroundJobs, SimpleActor};
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::util::actor::concurrent::ConcurrentActor;
|
||||||
|
use crate::util::actor::Actor;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
use crate::volume::data_dir;
|
use crate::volume::data_dir;
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ pub enum LoadDisposition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
actor: SimpleActor<ServiceActor>,
|
actor: ConcurrentActor<ServiceActor>,
|
||||||
seed: Arc<ServiceActorSeed>,
|
seed: Arc<ServiceActorSeed>,
|
||||||
}
|
}
|
||||||
impl Service {
|
impl Service {
|
||||||
@@ -90,7 +91,7 @@ impl Service {
|
|||||||
.init(Arc::downgrade(&seed))
|
.init(Arc::downgrade(&seed))
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
actor: SimpleActor::new(ServiceActor(seed.clone())),
|
actor: ConcurrentActor::new(ServiceActor(seed.clone())),
|
||||||
seed,
|
seed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -391,10 +392,11 @@ impl ServiceActorSeed {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
struct ServiceActor(Arc<ServiceActorSeed>);
|
struct ServiceActor(Arc<ServiceActorSeed>);
|
||||||
|
|
||||||
impl Actor for ServiceActor {
|
impl Actor for ServiceActor {
|
||||||
fn init(&mut self, jobs: &mut BackgroundJobs) {
|
fn init(&mut self, jobs: &BackgroundJobQueue) {
|
||||||
let seed = self.0.clone();
|
let seed = self.0.clone();
|
||||||
jobs.add_job(async move {
|
jobs.add_job(async move {
|
||||||
let id = seed.id.clone();
|
let id = seed.id.clone();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use clap::Parser;
|
|||||||
use emver::VersionRange;
|
use emver::VersionRange;
|
||||||
use imbl::OrdMap;
|
use imbl::OrdMap;
|
||||||
use imbl_value::{json, InternedString};
|
use imbl_value::{json, InternedString};
|
||||||
use models::{ActionId, HealthCheckId, HostId, ImageId, PackageId, VolumeId};
|
use models::{ActionId, DataUrl, HealthCheckId, HostId, ImageId, PackageId, VolumeId};
|
||||||
use patch_db::json_ptr::JsonPointer;
|
use patch_db::json_ptr::JsonPointer;
|
||||||
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -28,7 +28,9 @@ use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
|
|||||||
use crate::net::host::binding::BindOptions;
|
use crate::net::host::binding::BindOptions;
|
||||||
use crate::net::host::HostKind;
|
use crate::net::host::HostKind;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::s9pk::merkle_archive::source::http::{HttpReader, HttpSource};
|
||||||
use crate::s9pk::rpc::SKIP_ENV;
|
use crate::s9pk::rpc::SKIP_ENV;
|
||||||
|
use crate::s9pk::S9pk;
|
||||||
use crate::service::cli::ContainerCliContext;
|
use crate::service::cli::ContainerCliContext;
|
||||||
use crate::service::ServiceActorSeed;
|
use crate::service::ServiceActorSeed;
|
||||||
use crate::status::health_check::HealthCheckResult;
|
use crate::status::health_check::HealthCheckResult;
|
||||||
@@ -1145,11 +1147,36 @@ async fn set_dependencies(
|
|||||||
version_spec,
|
version_spec,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let icon = todo!();
|
let (icon, title) = match async {
|
||||||
let title = todo!();
|
let remote_s9pk = S9pk::deserialize(
|
||||||
|
&HttpSource::new(
|
||||||
|
ctx.ctx.client.clone(),
|
||||||
|
registry_url
|
||||||
|
.join(&format!("package/v2/{}.s9pk?spec={}", dep_id, version_spec))?,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let icon = remote_s9pk.icon_data_url().await?;
|
||||||
|
|
||||||
|
Ok::<_, Error>((icon, remote_s9pk.as_manifest().title.clone()))
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error fetching remote s9pk: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
(
|
||||||
|
DataUrl::from_slice("image/png", include_bytes!("../install/package-icon.png")),
|
||||||
|
dep_id.to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
let config_satisfied = if let Some(dep_service) = &*ctx.ctx.services.get(&dep_id).await {
|
let config_satisfied = if let Some(dep_service) = &*ctx.ctx.services.get(&dep_id).await {
|
||||||
service
|
service
|
||||||
.dependency_config(dep_id, dep_service.get_config().await?.config)
|
.dependency_config(dep_id.clone(), dep_service.get_config().await?.config)
|
||||||
.await?
|
.await?
|
||||||
.is_none()
|
.is_none()
|
||||||
} else {
|
} else {
|
||||||
@@ -1158,7 +1185,7 @@ async fn set_dependencies(
|
|||||||
deps.insert(
|
deps.insert(
|
||||||
dep_id,
|
dep_id,
|
||||||
CurrentDependencyInfo {
|
CurrentDependencyInfo {
|
||||||
kind: CurrentDependencyKind::Exists,
|
kind,
|
||||||
registry_url,
|
registry_url,
|
||||||
version_spec,
|
version_spec,
|
||||||
icon,
|
icon,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use tokio::sync::watch;
|
|||||||
|
|
||||||
use super::persistent_container::ServiceState;
|
use super::persistent_container::ServiceState;
|
||||||
use crate::service::start_stop::StartStop;
|
use crate::service::start_stop::StartStop;
|
||||||
use crate::util::actor::BackgroundJobs;
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
use crate::util::future::{CancellationHandle, RemoteCancellable};
|
use crate::util::future::{CancellationHandle, RemoteCancellable};
|
||||||
|
|
||||||
pub mod backup;
|
pub mod backup;
|
||||||
@@ -41,7 +41,7 @@ impl TransitionState {
|
|||||||
fn new(
|
fn new(
|
||||||
task: impl Future<Output = ()> + Send + 'static,
|
task: impl Future<Output = ()> + Send + 'static,
|
||||||
kind: TransitionKind,
|
kind: TransitionKind,
|
||||||
jobs: &mut BackgroundJobs,
|
jobs: &BackgroundJobQueue,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let task = RemoteCancellable::new(task);
|
let task = RemoteCancellable::new(task);
|
||||||
let cancel_handle = task.cancellation_handle();
|
let cancel_handle = task.cancellation_handle();
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
use super::TempDesiredState;
|
use super::TempDesiredState;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::service::config::GetConfig;
|
||||||
|
use crate::service::dependencies::DependencyConfig;
|
||||||
use crate::service::transition::{TransitionKind, TransitionState};
|
use crate::service::transition::{TransitionKind, TransitionState};
|
||||||
use crate::service::{Service, ServiceActor};
|
use crate::service::{Service, ServiceActor};
|
||||||
use crate::util::actor::{BackgroundJobs, Handler};
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::util::actor::{ConflictBuilder, Handler};
|
||||||
use crate::util::future::RemoteCancellable;
|
use crate::util::future::RemoteCancellable;
|
||||||
|
|
||||||
struct Restart;
|
pub(super) struct Restart;
|
||||||
impl Handler<Restart> for ServiceActor {
|
impl Handler<Restart> for ServiceActor {
|
||||||
type Response = ();
|
type Response = ();
|
||||||
async fn handle(&mut self, _: Restart, jobs: &mut BackgroundJobs) -> Self::Response {
|
fn conflicts_with(_: &Restart) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::everything()
|
||||||
|
.except::<GetConfig>()
|
||||||
|
.except::<DependencyConfig>()
|
||||||
|
}
|
||||||
|
async fn handle(&mut self, _: Restart, jobs: &BackgroundJobQueue) -> Self::Response {
|
||||||
// So Need a handle to just a single field in the state
|
// So Need a handle to just a single field in the state
|
||||||
let temp = TempDesiredState::new(&self.0.persistent_container.state);
|
let temp = TempDesiredState::new(&self.0.persistent_container.state);
|
||||||
let mut current = self.0.persistent_container.state.subscribe();
|
let mut current = self.0.persistent_container.state.subscribe();
|
||||||
|
|||||||
60
core/startos/src/util/actor/background.rs
Normal file
60
core/startos/src/util/actor/background.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use futures::future::BoxFuture;
|
||||||
|
use futures::{Future, FutureExt};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BackgroundJobQueue(mpsc::UnboundedSender<BoxFuture<'static, ()>>);
|
||||||
|
impl BackgroundJobQueue {
|
||||||
|
pub fn new() -> (Self, BackgroundJobRunner) {
|
||||||
|
let (send, recv) = mpsc::unbounded_channel();
|
||||||
|
(
|
||||||
|
Self(send),
|
||||||
|
BackgroundJobRunner {
|
||||||
|
recv,
|
||||||
|
jobs: Vec::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn add_job(&self, fut: impl Future<Output = ()> + Send + 'static) {
|
||||||
|
let _ = self.0.send(fut.boxed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BackgroundJobRunner {
|
||||||
|
recv: mpsc::UnboundedReceiver<BoxFuture<'static, ()>>,
|
||||||
|
jobs: Vec<BoxFuture<'static, ()>>,
|
||||||
|
}
|
||||||
|
impl BackgroundJobRunner {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.recv.is_empty() && self.jobs.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Future for BackgroundJobRunner {
|
||||||
|
type Output = ();
|
||||||
|
fn poll(
|
||||||
|
mut self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Self::Output> {
|
||||||
|
while let std::task::Poll::Ready(Some(job)) = self.recv.poll_recv(cx) {
|
||||||
|
self.jobs.push(job);
|
||||||
|
}
|
||||||
|
let complete = self
|
||||||
|
.jobs
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, f)| match f.poll_unpin(cx) {
|
||||||
|
std::task::Poll::Pending => None,
|
||||||
|
std::task::Poll::Ready(_) => Some(i),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for idx in complete.into_iter().rev() {
|
||||||
|
#[allow(clippy::let_underscore_future)]
|
||||||
|
let _ = self.jobs.swap_remove(idx);
|
||||||
|
}
|
||||||
|
if self.jobs.is_empty() && self.recv.is_closed() {
|
||||||
|
std::task::Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
std::task::Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
208
core/startos/src/util/actor/concurrent.rs
Normal file
208
core/startos/src/util/actor/concurrent.rs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
use std::any::Any;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use futures::future::{ready, BoxFuture};
|
||||||
|
use futures::{Future, FutureExt, TryFutureExt};
|
||||||
|
use helpers::NonDetachingJoinHandle;
|
||||||
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::util::actor::background::{BackgroundJobQueue, BackgroundJobRunner};
|
||||||
|
use crate::util::actor::{Actor, ConflictFn, Handler, PendingMessageStrategy, Request};
|
||||||
|
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
struct ConcurrentRunner<A> {
|
||||||
|
actor: A,
|
||||||
|
shutdown: Option<oneshot::Receiver<()>>,
|
||||||
|
waiting: Vec<Request<A>>,
|
||||||
|
recv: mpsc::UnboundedReceiver<Request<A>>,
|
||||||
|
handlers: Vec<(
|
||||||
|
Arc<ConflictFn<A>>,
|
||||||
|
oneshot::Sender<Box<dyn Any + Send>>,
|
||||||
|
BoxFuture<'static, Box<dyn Any + Send>>,
|
||||||
|
)>,
|
||||||
|
queue: BackgroundJobQueue,
|
||||||
|
#[pin]
|
||||||
|
bg_runner: BackgroundJobRunner,
|
||||||
|
}
|
||||||
|
impl<A: Actor + Clone> Future for ConcurrentRunner<A> {
|
||||||
|
type Output = ();
|
||||||
|
fn poll(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Self::Output> {
|
||||||
|
let mut this = self.project();
|
||||||
|
*this.shutdown = this.shutdown.take().and_then(|mut s| {
|
||||||
|
if s.poll_unpin(cx).is_pending() {
|
||||||
|
Some(s)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if this.shutdown.is_some() {
|
||||||
|
while let std::task::Poll::Ready(Some((msg, reply))) = this.recv.poll_recv(cx) {
|
||||||
|
if this.handlers.iter().any(|(f, _, _)| f(&*msg)) {
|
||||||
|
this.waiting.push((msg, reply));
|
||||||
|
} else {
|
||||||
|
let mut actor = this.actor.clone();
|
||||||
|
let queue = this.queue.clone();
|
||||||
|
this.handlers.push((
|
||||||
|
msg.conflicts_with(),
|
||||||
|
reply,
|
||||||
|
async move { msg.handle_with(&mut actor, &queue).await }.boxed(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// handlers
|
||||||
|
while {
|
||||||
|
let mut cont = false;
|
||||||
|
let complete = this
|
||||||
|
.handlers
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, (_, _, f))| match f.poll_unpin(cx) {
|
||||||
|
std::task::Poll::Pending => None,
|
||||||
|
std::task::Poll::Ready(res) => Some((i, res)),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for (idx, res) in complete.into_iter().rev() {
|
||||||
|
#[allow(clippy::let_underscore_future)]
|
||||||
|
let (f, reply, _) = this.handlers.swap_remove(idx);
|
||||||
|
let _ = reply.send(res);
|
||||||
|
// TODO: replace with Vec::extract_if once stable
|
||||||
|
if this.shutdown.is_some() {
|
||||||
|
let mut i = 0;
|
||||||
|
while i < this.waiting.len() {
|
||||||
|
if f(&*this.waiting[i].0)
|
||||||
|
&& !this.handlers.iter().any(|(f, _, _)| f(&*this.waiting[i].0))
|
||||||
|
{
|
||||||
|
let (msg, reply) = this.waiting.remove(i);
|
||||||
|
let mut actor = this.actor.clone();
|
||||||
|
let queue = this.queue.clone();
|
||||||
|
this.handlers.push((
|
||||||
|
msg.conflicts_with(),
|
||||||
|
reply,
|
||||||
|
async move { msg.handle_with(&mut actor, &queue).await }.boxed(),
|
||||||
|
));
|
||||||
|
cont = true;
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cont
|
||||||
|
} {}
|
||||||
|
let _ = this.bg_runner.as_mut().poll(cx);
|
||||||
|
if this.waiting.is_empty() && this.handlers.is_empty() && this.bg_runner.is_empty() {
|
||||||
|
std::task::Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
std::task::Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConcurrentActor<A: Actor + Clone> {
|
||||||
|
shutdown: oneshot::Sender<()>,
|
||||||
|
runtime: NonDetachingJoinHandle<()>,
|
||||||
|
messenger: mpsc::UnboundedSender<Request<A>>,
|
||||||
|
}
|
||||||
|
impl<A: Actor + Clone> ConcurrentActor<A> {
|
||||||
|
pub fn new(mut actor: A) -> Self {
|
||||||
|
let (shutdown_send, shutdown_recv) = oneshot::channel();
|
||||||
|
let (messenger_send, messenger_recv) = mpsc::unbounded_channel::<Request<A>>();
|
||||||
|
let runtime = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
||||||
|
let (queue, runner) = BackgroundJobQueue::new();
|
||||||
|
actor.init(&queue);
|
||||||
|
ConcurrentRunner {
|
||||||
|
actor,
|
||||||
|
shutdown: Some(shutdown_recv),
|
||||||
|
waiting: Vec::new(),
|
||||||
|
recv: messenger_recv,
|
||||||
|
handlers: Vec::new(),
|
||||||
|
queue,
|
||||||
|
bg_runner: runner,
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
}));
|
||||||
|
Self {
|
||||||
|
shutdown: shutdown_send,
|
||||||
|
runtime,
|
||||||
|
messenger: messenger_send,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message is guaranteed to be queued immediately
|
||||||
|
pub fn queue<M: Send + 'static>(
|
||||||
|
&self,
|
||||||
|
message: M,
|
||||||
|
) -> impl Future<Output = Result<A::Response, Error>>
|
||||||
|
where
|
||||||
|
A: Handler<M>,
|
||||||
|
{
|
||||||
|
if self.runtime.is_finished() {
|
||||||
|
return futures::future::Either::Left(ready(Err(Error::new(
|
||||||
|
eyre!("actor runtime has exited"),
|
||||||
|
ErrorKind::Unknown,
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
let (reply_send, reply_recv) = oneshot::channel();
|
||||||
|
self.messenger
|
||||||
|
.send((Box::new(message), reply_send))
|
||||||
|
.unwrap();
|
||||||
|
futures::future::Either::Right(
|
||||||
|
reply_recv
|
||||||
|
.map_err(|_| Error::new(eyre!("actor runtime has exited"), ErrorKind::Unknown))
|
||||||
|
.and_then(|a| {
|
||||||
|
ready(
|
||||||
|
a.downcast()
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("received incorrect type in response"),
|
||||||
|
ErrorKind::Incoherent,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|a| *a),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send<M: Send + 'static>(&self, message: M) -> Result<A::Response, Error>
|
||||||
|
where
|
||||||
|
A: Handler<M>,
|
||||||
|
{
|
||||||
|
self.queue(message).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn shutdown(self, strategy: PendingMessageStrategy) {
|
||||||
|
drop(self.messenger);
|
||||||
|
let timeout = match strategy {
|
||||||
|
PendingMessageStrategy::CancelAll => {
|
||||||
|
self.shutdown.send(()).unwrap();
|
||||||
|
Some(Duration::from_secs(0))
|
||||||
|
}
|
||||||
|
PendingMessageStrategy::FinishCurrentCancelPending { timeout } => {
|
||||||
|
self.shutdown.send(()).unwrap();
|
||||||
|
timeout
|
||||||
|
}
|
||||||
|
PendingMessageStrategy::FinishAll { timeout } => timeout,
|
||||||
|
};
|
||||||
|
let aborter = if let Some(timeout) = timeout {
|
||||||
|
let hdl = self.runtime.abort_handle();
|
||||||
|
async move {
|
||||||
|
tokio::time::sleep(timeout).await;
|
||||||
|
hdl.abort();
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
} else {
|
||||||
|
futures::future::pending().boxed()
|
||||||
|
};
|
||||||
|
tokio::select! {
|
||||||
|
_ = aborter => (),
|
||||||
|
_ = self.runtime => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
148
core/startos/src/util/actor/mod.rs
Normal file
148
core/startos/src/util/actor/mod.rs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
use std::any::{Any, TypeId};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use futures::future::BoxFuture;
|
||||||
|
use futures::{Future, FutureExt};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
|
||||||
|
pub mod background;
|
||||||
|
pub mod concurrent;
|
||||||
|
pub mod simple;
|
||||||
|
|
||||||
|
pub trait Actor: Sized + Send + 'static {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn init(&mut self, jobs: &BackgroundJobQueue) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Handler<M: Any + Send>: Actor {
|
||||||
|
type Response: Any + Send;
|
||||||
|
/// DRAGONS: this must be correctly implemented bi-directionally in order to work as expected
|
||||||
|
fn conflicts_with(#[allow(unused_variables)] msg: &M) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::everything()
|
||||||
|
}
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
msg: M,
|
||||||
|
jobs: &BackgroundJobQueue,
|
||||||
|
) -> impl Future<Output = Self::Response> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConflictFn<A> = dyn Fn(&dyn Message<A>) -> bool + Send + Sync;
|
||||||
|
|
||||||
|
trait Message<A>: Send + Any {
|
||||||
|
fn conflicts_with(&self) -> Arc<ConflictFn<A>>;
|
||||||
|
fn handle_with<'a>(
|
||||||
|
self: Box<Self>,
|
||||||
|
actor: &'a mut A,
|
||||||
|
jobs: &'a BackgroundJobQueue,
|
||||||
|
) -> BoxFuture<'a, Box<dyn Any + Send>>;
|
||||||
|
}
|
||||||
|
impl<M: Send + Any, A: Actor> Message<A> for M
|
||||||
|
where
|
||||||
|
A: Handler<M>,
|
||||||
|
{
|
||||||
|
fn conflicts_with(&self) -> Arc<ConflictFn<A>> {
|
||||||
|
A::conflicts_with(self).build()
|
||||||
|
}
|
||||||
|
fn handle_with<'a>(
|
||||||
|
self: Box<Self>,
|
||||||
|
actor: &'a mut A,
|
||||||
|
jobs: &'a BackgroundJobQueue,
|
||||||
|
) -> BoxFuture<'a, Box<dyn Any + Send>> {
|
||||||
|
async move { Box::new(actor.handle(*self, jobs).await) as Box<dyn Any + Send> }.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<A: Actor> dyn Message<A> {
|
||||||
|
#[inline]
|
||||||
|
pub fn is<M: Message<A>>(&self) -> bool {
|
||||||
|
let t = TypeId::of::<M>();
|
||||||
|
let concrete = self.type_id();
|
||||||
|
t == concrete
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn downcast_ref_unchecked<M: Message<A>>(&self) -> &M {
|
||||||
|
debug_assert!(self.is::<M>());
|
||||||
|
unsafe { &*(self as *const dyn Message<A> as *const M) }
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn downcast_ref<M: Message<A>>(&self) -> Option<&M> {
|
||||||
|
if self.is::<M>() {
|
||||||
|
unsafe { Some(self.downcast_ref_unchecked()) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request<A> = (Box<dyn Message<A>>, oneshot::Sender<Box<dyn Any + Send>>);
|
||||||
|
|
||||||
|
pub enum PendingMessageStrategy {
|
||||||
|
CancelAll,
|
||||||
|
FinishCurrentCancelPending { timeout: Option<Duration> },
|
||||||
|
FinishAll { timeout: Option<Duration> },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConflictBuilder<A> {
|
||||||
|
base: bool,
|
||||||
|
except: BTreeMap<TypeId, Option<Box<dyn Fn(&dyn Message<A>) -> bool + Send + Sync>>>,
|
||||||
|
}
|
||||||
|
impl<A: Actor> ConflictBuilder<A> {
|
||||||
|
pub const fn everything() -> Self {
|
||||||
|
Self {
|
||||||
|
base: true,
|
||||||
|
except: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn nothing() -> Self {
|
||||||
|
Self {
|
||||||
|
base: false,
|
||||||
|
except: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn except<M: Any + Send>(mut self) -> Self
|
||||||
|
where
|
||||||
|
A: Handler<M>,
|
||||||
|
{
|
||||||
|
self.except.insert(TypeId::of::<M>(), None);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn except_if<M: Any + Send, F: Fn(&M) -> bool + Send + Sync + 'static>(
|
||||||
|
mut self,
|
||||||
|
f: F,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
A: Handler<M>,
|
||||||
|
{
|
||||||
|
self.except.insert(
|
||||||
|
TypeId::of::<M>(),
|
||||||
|
Some(Box::new(move |m| {
|
||||||
|
if let Some(m) = m.downcast_ref() {
|
||||||
|
f(m)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn build(self) -> Arc<ConflictFn<A>> {
|
||||||
|
Arc::new(move |m| {
|
||||||
|
self.base
|
||||||
|
^ if let Some(entry) = self.except.get(&m.type_id()) {
|
||||||
|
if let Some(f) = entry {
|
||||||
|
f(m)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,85 +1,14 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::future::ready;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use futures::future::BoxFuture;
|
use futures::future::ready;
|
||||||
use futures::{Future, FutureExt, TryFutureExt};
|
use futures::{Future, FutureExt, TryFutureExt};
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
use tokio::sync::oneshot::error::TryRecvError;
|
use tokio::sync::oneshot::error::TryRecvError;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::Never;
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::util::actor::{Actor, Handler, PendingMessageStrategy, Request};
|
||||||
pub trait Actor: Send + 'static {
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn init(&mut self, jobs: &mut BackgroundJobs) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Handler<M>: Actor {
|
|
||||||
type Response: Any + Send;
|
|
||||||
fn handle(
|
|
||||||
&mut self,
|
|
||||||
msg: M,
|
|
||||||
jobs: &mut BackgroundJobs,
|
|
||||||
) -> impl Future<Output = Self::Response> + Send;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
trait Message<A>: Send {
|
|
||||||
async fn handle_with(
|
|
||||||
self: Box<Self>,
|
|
||||||
actor: &mut A,
|
|
||||||
jobs: &mut BackgroundJobs,
|
|
||||||
) -> Box<dyn Any + Send>;
|
|
||||||
}
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<M: Send, A: Actor> Message<A> for M
|
|
||||||
where
|
|
||||||
A: Handler<M>,
|
|
||||||
{
|
|
||||||
async fn handle_with(
|
|
||||||
self: Box<Self>,
|
|
||||||
actor: &mut A,
|
|
||||||
jobs: &mut BackgroundJobs,
|
|
||||||
) -> Box<dyn Any + Send> {
|
|
||||||
Box::new(actor.handle(*self, jobs).await)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Request<A> = (Box<dyn Message<A>>, oneshot::Sender<Box<dyn Any + Send>>);
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct BackgroundJobs {
|
|
||||||
jobs: Vec<BoxFuture<'static, ()>>,
|
|
||||||
}
|
|
||||||
impl BackgroundJobs {
|
|
||||||
pub fn add_job(&mut self, fut: impl Future<Output = ()> + Send + 'static) {
|
|
||||||
self.jobs.push(fut.boxed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Future for BackgroundJobs {
|
|
||||||
type Output = Never;
|
|
||||||
fn poll(
|
|
||||||
mut self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<Self::Output> {
|
|
||||||
let complete = self
|
|
||||||
.jobs
|
|
||||||
.iter_mut()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, f)| match f.poll_unpin(cx) {
|
|
||||||
std::task::Poll::Pending => None,
|
|
||||||
std::task::Poll::Ready(_) => Some(i),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
for idx in complete.into_iter().rev() {
|
|
||||||
#[allow(clippy::let_underscore_future)]
|
|
||||||
let _ = self.jobs.swap_remove(idx);
|
|
||||||
}
|
|
||||||
std::task::Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SimpleActor<A: Actor> {
|
pub struct SimpleActor<A: Actor> {
|
||||||
shutdown: oneshot::Sender<()>,
|
shutdown: oneshot::Sender<()>,
|
||||||
@@ -91,19 +20,17 @@ impl<A: Actor> SimpleActor<A> {
|
|||||||
let (shutdown_send, mut shutdown_recv) = oneshot::channel();
|
let (shutdown_send, mut shutdown_recv) = oneshot::channel();
|
||||||
let (messenger_send, mut messenger_recv) = mpsc::unbounded_channel::<Request<A>>();
|
let (messenger_send, mut messenger_recv) = mpsc::unbounded_channel::<Request<A>>();
|
||||||
let runtime = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
let runtime = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
||||||
let mut bg = BackgroundJobs::default();
|
let (queue, mut runner) = BackgroundJobQueue::new();
|
||||||
actor.init(&mut bg);
|
actor.init(&queue);
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = &mut bg => (),
|
_ = &mut runner => (),
|
||||||
msg = messenger_recv.recv() => match msg {
|
msg = messenger_recv.recv() => match msg {
|
||||||
Some((msg, reply)) if shutdown_recv.try_recv() == Err(TryRecvError::Empty) => {
|
Some((msg, reply)) if shutdown_recv.try_recv() == Err(TryRecvError::Empty) => {
|
||||||
let mut new_bg = BackgroundJobs::default();
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = msg.handle_with(&mut actor, &mut new_bg) => { let _ = reply.send(res); },
|
res = msg.handle_with(&mut actor, &queue) => { let _ = reply.send(res); },
|
||||||
_ = &mut bg => (),
|
_ = &mut runner => (),
|
||||||
}
|
}
|
||||||
bg.jobs.append(&mut new_bg.jobs);
|
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
},
|
},
|
||||||
@@ -189,9 +116,3 @@ impl<A: Actor> SimpleActor<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum PendingMessageStrategy {
|
|
||||||
CancelAll,
|
|
||||||
FinishCurrentCancelPending { timeout: Option<Duration> },
|
|
||||||
FinishAll { timeout: Option<Duration> },
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user