first pass of merging in warp (doesn't build)
This commit is contained in:
+212
-136
@@ -1,53 +1,53 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
// Re-exported from cloud_objects.
|
||||
pub use cloud_objects::cloud_object::SerializedModel;
|
||||
use derivative::Derivative;
|
||||
use galaxy_graphql::scalars::time::ServerTimestamp;
|
||||
use galaxyui::{
|
||||
r#async::FutureId, Entity, ModelContext, RequestState, RetryOption, SingletonEntity,
|
||||
};
|
||||
use http::StatusCode;
|
||||
use std::borrow::Cow;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use uuid::Uuid;
|
||||
use warp_graphql::scalars::time::ServerTimestamp;
|
||||
use warpui::r#async::FutureId;
|
||||
use warpui::{Entity, ModelContext, RequestState, RetryOption, SingletonEntity};
|
||||
|
||||
use super::{
|
||||
graphql::GraphQLError,
|
||||
ids::{ClientId, HashableId, ObjectUid, ServerId, SyncId, ToServerId},
|
||||
server_api::{auth::UserAuthenticationError, object::ObjectClient},
|
||||
};
|
||||
|
||||
use super::graphql::GraphQLError;
|
||||
use super::ids::{ClientId, HashableId, ObjectUid, ServerId, SyncId, ToServerId};
|
||||
use super::server_api::auth::UserAuthenticationError;
|
||||
use super::server_api::object::ObjectClient;
|
||||
use crate::ai::ambient_agents::scheduled::CloudScheduledAmbientAgentModel;
|
||||
use crate::ai::cloud_agent_config::CloudAgentConfigModel;
|
||||
use crate::ai::cloud_environments::CloudAmbientAgentEnvironmentModel;
|
||||
use crate::ai::execution_profiles::CloudAIExecutionProfileModel;
|
||||
use crate::ai::facts::CloudAIFactModel;
|
||||
use crate::ai::mcp::templatable::CloudTemplatableMCPServerModel;
|
||||
use crate::server::cloud_objects::update_manager::InitiatedBy;
|
||||
use crate::{
|
||||
ai::cloud_agent_config::CloudAgentConfigModel,
|
||||
ai::cloud_environments::CloudAmbientAgentEnvironmentModel,
|
||||
ai::{
|
||||
ambient_agents::scheduled::CloudScheduledAmbientAgentModel,
|
||||
execution_profiles::CloudAIExecutionProfileModel, facts::CloudAIFactModel,
|
||||
mcp::CloudMCPServerModel,
|
||||
},
|
||||
cloud_object::{
|
||||
model::{
|
||||
actions::{ObjectAction, ObjectActionHistory, ObjectActionSubtype, ObjectActionType},
|
||||
generic_string_model::GenericStringObjectId,
|
||||
},
|
||||
BulkCreateCloudObjectResult, BulkCreateGenericStringObjectsRequest, CloudModelType,
|
||||
CloudObject, CloudObjectEventEntrypoint, CreateCloudObjectResult, CreateObjectRequest,
|
||||
GenericCloudObject, GenericStringObjectFormat, GenericStringObjectUniqueKey,
|
||||
JsonObjectType, ObjectType, Owner, Revision, RevisionAndLastEditor, ServerCloudObject,
|
||||
ServerCreationInfo, UpdateCloudObjectResult,
|
||||
},
|
||||
drive::{folders::CloudFolderModel, CloudObjectTypeAndId},
|
||||
env_vars::CloudEnvVarCollectionModel,
|
||||
notebooks::CloudNotebookModel,
|
||||
settings::cloud_preferences::CloudPreferenceModel,
|
||||
workflows::{workflow_enum::CloudWorkflowEnumModel, CloudWorkflowModel},
|
||||
use crate::ai::mcp::CloudMCPServerModel;
|
||||
use crate::cloud_object::model::actions::{
|
||||
ObjectAction, ObjectActionHistory, ObjectActionSubtype, ObjectActionType,
|
||||
};
|
||||
use crate::cloud_object::model::generic_string_model::GenericStringObjectId;
|
||||
use crate::cloud_object::{
|
||||
BulkCreateCloudObjectResult, BulkCreateGenericStringObjectsRequest, CloudModelType,
|
||||
CloudObject, CloudObjectEventEntrypoint, CreateCloudObjectResult, CreateObjectRequest,
|
||||
GenericCloudObject, GenericStringObjectFormat, GenericStringObjectUniqueKey, JsonObjectType,
|
||||
ObjectType, Owner, Revision, RevisionAndLastEditor, ServerCloudObject, ServerCreationInfo,
|
||||
UpdateCloudObjectResult,
|
||||
};
|
||||
use crate::drive::folders::CloudFolderModel;
|
||||
use crate::drive::CloudObjectTypeAndId;
|
||||
use crate::env_vars::CloudEnvVarCollectionModel;
|
||||
use crate::notebooks::CloudNotebookModel;
|
||||
use crate::server::cloud_objects::update_manager::InitiatedBy;
|
||||
use crate::settings::cloud_preferences::CloudPreferenceModel;
|
||||
use crate::workflows::workflow_enum::CloudWorkflowEnumModel;
|
||||
use crate::workflows::CloudWorkflowModel;
|
||||
|
||||
lazy_static! {
|
||||
static ref DEFAULT_RETRY_OPTION: RetryOption =
|
||||
@@ -93,30 +93,6 @@ enum UpdateResponseType {
|
||||
Rejected { object: Box<ServerCloudObject> },
|
||||
}
|
||||
|
||||
// A newtype for a serialized model that wraps a plain string.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SerializedModel(String);
|
||||
|
||||
impl SerializedModel {
|
||||
pub fn new(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
|
||||
pub fn model_as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn take(self) -> String {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SerializedModel {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct GenericStringObjectToCreate {
|
||||
pub id: ClientId,
|
||||
@@ -138,6 +114,15 @@ impl QueueItemId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum QueueDependency {
|
||||
QueueItem(QueueItemId),
|
||||
BulkCreateGenericStringObject {
|
||||
queue_item_id: QueueItemId,
|
||||
client_id: ClientId,
|
||||
},
|
||||
}
|
||||
|
||||
/// Item that sync queue supports.
|
||||
#[derive(Derivative, Debug)]
|
||||
#[derivative(PartialEq, Eq, Clone)]
|
||||
@@ -170,7 +155,7 @@ pub enum QueueItem {
|
||||
objects: Vec<GenericStringObjectToCreate>,
|
||||
},
|
||||
// Note, we continue to do UpdateXXX items per object type here
|
||||
// beause it's the most type safe way to handle the different id types
|
||||
// because it's the most type safe way to handle the different id types
|
||||
// and the different update payloads. Most of the logic is still shared
|
||||
// via a single update_object method.
|
||||
UpdateNotebook {
|
||||
@@ -360,6 +345,7 @@ pub enum SyncQueueEvent {
|
||||
pub struct SyncQueue {
|
||||
queue: Vec<(QueueItemId, QueueItem)>,
|
||||
waiting_response: HashMap<String, HashSet<QueueItemId>>,
|
||||
in_flight_bulk_create_objects: HashMap<QueueItemId, HashSet<ClientId>>,
|
||||
object_client: Arc<dyn ObjectClient>,
|
||||
client_id_to_server: HashMap<ClientId, String>,
|
||||
server_id_to_client_hash: HashMap<String, String>,
|
||||
@@ -368,8 +354,8 @@ pub struct SyncQueue {
|
||||
// calls to `ctx#spawn` have finished before asserting.
|
||||
spawned_futures: Vec<FutureId>,
|
||||
|
||||
// For each QueueItem, store the set of QueueItems it depends upon finishing first
|
||||
queue_dependencies: HashMap<QueueItemId, HashSet<QueueItemId>>,
|
||||
// For each QueueItem, store the set of queue dependencies it depends upon finishing first.
|
||||
queue_dependencies: HashMap<QueueItemId, HashSet<QueueDependency>>,
|
||||
}
|
||||
|
||||
impl SyncQueue {
|
||||
@@ -395,6 +381,7 @@ impl SyncQueue {
|
||||
.map(|queue_item| (QueueItemId::new(), queue_item))
|
||||
.collect(),
|
||||
waiting_response: Default::default(),
|
||||
in_flight_bulk_create_objects: Default::default(),
|
||||
object_client,
|
||||
client_id_to_server: Default::default(),
|
||||
server_id_to_client_hash: Default::default(),
|
||||
@@ -424,6 +411,7 @@ impl SyncQueue {
|
||||
pub fn clear(&mut self) {
|
||||
self.queue.clear();
|
||||
self.waiting_response.clear();
|
||||
self.in_flight_bulk_create_objects.clear();
|
||||
self.queue_dependencies.clear();
|
||||
self.client_id_to_server.clear();
|
||||
self.server_id_to_client_hash.clear();
|
||||
@@ -454,14 +442,18 @@ impl SyncQueue {
|
||||
) {
|
||||
let mut dependencies = match item {
|
||||
// Update requests will depend on any existing create/updates to the same object
|
||||
QueueItem::UpdateNotebook { id, .. } => self.get_update_dependencies(id),
|
||||
QueueItem::UpdateFolder { id, .. } => self.get_update_dependencies(id),
|
||||
QueueItem::UpdateCloudPreferences { id, .. } => self.get_update_dependencies(id),
|
||||
QueueItem::UpdateEnvVarCollection { id, .. } => self.get_update_dependencies(id),
|
||||
QueueItem::UpdateWorkflowEnum { id, .. } => self.get_update_dependencies(id),
|
||||
QueueItem::UpdateCloudEnvironment { id, .. } => self.get_update_dependencies(id),
|
||||
QueueItem::UpdateScheduledAmbientAgent { id, .. } => self.get_update_dependencies(id),
|
||||
QueueItem::UpdateCloudAgentConfig { id, .. } => self.get_update_dependencies(id),
|
||||
QueueItem::UpdateNotebook { id, .. }
|
||||
| QueueItem::UpdateFolder { id, .. }
|
||||
| QueueItem::UpdateCloudPreferences { id, .. }
|
||||
| QueueItem::UpdateEnvVarCollection { id, .. }
|
||||
| QueueItem::UpdateWorkflowEnum { id, .. }
|
||||
| QueueItem::UpdateAIFact { id, .. }
|
||||
| QueueItem::UpdateMCPServer { id, .. }
|
||||
| QueueItem::UpdateAIExecutionProfile { id, .. }
|
||||
| QueueItem::UpdateTemplatableMCPServer { id, .. }
|
||||
| QueueItem::UpdateCloudEnvironment { id, .. }
|
||||
| QueueItem::UpdateScheduledAmbientAgent { id, .. }
|
||||
| QueueItem::UpdateCloudAgentConfig { id, .. } => self.get_update_dependencies(id),
|
||||
|
||||
// Update workflow requests should depend on existing requests to that object, as well as
|
||||
// any enums or env vars they reference.
|
||||
@@ -513,12 +505,14 @@ impl SyncQueue {
|
||||
dependencies
|
||||
}
|
||||
|
||||
// Other queue item types don't have dependencies supported right now
|
||||
_ => HashSet::new(),
|
||||
// These queue item types do not have inferred dependencies.
|
||||
QueueItem::CreateObject { .. }
|
||||
| QueueItem::BulkCreateGenericStringObjects { .. }
|
||||
| QueueItem::RecordObjectAction { .. } => HashSet::new(),
|
||||
};
|
||||
|
||||
// We never want an object to be dependent on itself, which is possible when we infer dependencies on startup from SQLite
|
||||
dependencies.remove(item_id);
|
||||
dependencies.remove(&QueueDependency::QueueItem(*item_id));
|
||||
|
||||
self.queue_dependencies.insert(*item_id, dependencies);
|
||||
}
|
||||
@@ -534,7 +528,7 @@ impl SyncQueue {
|
||||
}
|
||||
|
||||
/// Given an object ID, return the set of queue IDs of all queue items that operate on that object
|
||||
fn get_items_with_object_id(&self, item_id: String) -> HashSet<QueueItemId> {
|
||||
fn get_items_with_object_id(&self, item_id: String) -> HashSet<QueueDependency> {
|
||||
// Get a list of in flight QueueItems that operate on objects with `item_id`
|
||||
// We still want to map these in our dependencies, so that when they succeed or fail, we can respond accordingly
|
||||
// even if a subsequent request was enqueued after the relevant request was dequeued and is already in flight
|
||||
@@ -543,39 +537,75 @@ impl SyncQueue {
|
||||
.get(&item_id)
|
||||
.into_iter()
|
||||
.flat_map(|dependencies| dependencies.iter())
|
||||
.copied();
|
||||
.copied()
|
||||
.map(|queue_item_id| self.dependency_for_queue_item_id(queue_item_id, &item_id));
|
||||
|
||||
self.queue
|
||||
.iter()
|
||||
.filter(|(_, queue_item)| match queue_item {
|
||||
QueueItem::CreateObject { id, .. } => id.to_string() == item_id,
|
||||
QueueItem::CreateWorkflow { id, .. } => id.to_string() == item_id,
|
||||
QueueItem::BulkCreateGenericStringObjects { objects, .. } => {
|
||||
objects.iter().any(|data| data.id.to_string() == item_id)
|
||||
.filter_map(|(queue_item_id, queue_item)| match queue_item {
|
||||
QueueItem::CreateObject { id, .. } if id.to_string() == item_id => {
|
||||
Some(QueueDependency::QueueItem(*queue_item_id))
|
||||
}
|
||||
QueueItem::UpdateCloudPreferences { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateNotebook { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateWorkflow { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateFolder { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateEnvVarCollection { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateWorkflowEnum { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateAIFact { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateMCPServer { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateAIExecutionProfile { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateTemplatableMCPServer { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateCloudEnvironment { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateScheduledAmbientAgent { id, .. } => id.uid() == item_id,
|
||||
QueueItem::UpdateCloudAgentConfig { id, .. } => id.uid() == item_id,
|
||||
// We don't depend on object actions, since they don't affect an object's own content or metadata
|
||||
QueueItem::RecordObjectAction { .. } => false,
|
||||
QueueItem::CreateWorkflow { id, .. } if id.to_string() == item_id => {
|
||||
Some(QueueDependency::QueueItem(*queue_item_id))
|
||||
}
|
||||
QueueItem::BulkCreateGenericStringObjects { objects, .. } => {
|
||||
objects.iter().find_map(|data| {
|
||||
(data.id.to_string() == item_id).then_some(
|
||||
QueueDependency::BulkCreateGenericStringObject {
|
||||
queue_item_id: *queue_item_id,
|
||||
client_id: data.id,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
QueueItem::UpdateCloudPreferences { id, .. }
|
||||
| QueueItem::UpdateNotebook { id, .. }
|
||||
| QueueItem::UpdateWorkflow { id, .. }
|
||||
| QueueItem::UpdateFolder { id, .. }
|
||||
| QueueItem::UpdateEnvVarCollection { id, .. }
|
||||
| QueueItem::UpdateWorkflowEnum { id, .. }
|
||||
| QueueItem::UpdateAIFact { id, .. }
|
||||
| QueueItem::UpdateMCPServer { id, .. }
|
||||
| QueueItem::UpdateAIExecutionProfile { id, .. }
|
||||
| QueueItem::UpdateTemplatableMCPServer { id, .. }
|
||||
| QueueItem::UpdateCloudEnvironment { id, .. }
|
||||
| QueueItem::UpdateScheduledAmbientAgent { id, .. }
|
||||
| QueueItem::UpdateCloudAgentConfig { id, .. }
|
||||
if id.uid() == item_id =>
|
||||
{
|
||||
Some(QueueDependency::QueueItem(*queue_item_id))
|
||||
}
|
||||
// We don't depend on object actions, since they don't affect an object's own content or metadata.
|
||||
_ => None,
|
||||
})
|
||||
.map(|(queue_item_id, _)| *queue_item_id)
|
||||
.chain(in_flight_dependencies)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn dependency_for_queue_item_id(
|
||||
&self,
|
||||
queue_item_id: QueueItemId,
|
||||
item_id: &str,
|
||||
) -> QueueDependency {
|
||||
if let Some(client_id) = ClientId::from_hash(item_id) {
|
||||
if self
|
||||
.in_flight_bulk_create_objects
|
||||
.get(&queue_item_id)
|
||||
.is_some_and(|client_ids| client_ids.contains(&client_id))
|
||||
{
|
||||
return QueueDependency::BulkCreateGenericStringObject {
|
||||
queue_item_id,
|
||||
client_id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
QueueDependency::QueueItem(queue_item_id)
|
||||
}
|
||||
|
||||
/// Get dependencies for an update request, which should be every create or update request to the same object that is already enqueued
|
||||
fn get_update_dependencies(&self, id: &SyncId) -> HashSet<QueueItemId> {
|
||||
fn get_update_dependencies(&self, id: &SyncId) -> HashSet<QueueDependency> {
|
||||
// Get all objects with the same ID as the update request
|
||||
let mut dependencies = self.get_items_with_object_id(id.uid());
|
||||
|
||||
@@ -602,7 +632,7 @@ impl SyncQueue {
|
||||
workflow_id: SyncId,
|
||||
item_id: &QueueItemId,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) -> anyhow::Result<HashSet<QueueItemId>> {
|
||||
) -> anyhow::Result<HashSet<QueueDependency>> {
|
||||
let mut dependencies = HashSet::new();
|
||||
|
||||
let mut object_ids = workflow_model.data.get_enum_ids();
|
||||
@@ -622,7 +652,7 @@ impl SyncQueue {
|
||||
workflow_id.uid(),
|
||||
*item_id,
|
||||
// When adding the initiated_by parameter to this function call, InitiatedBy::User was set as a default value.
|
||||
// It can be changed to propogate initiated_by value from the queue object in the future if desired.
|
||||
// It can be changed to propagate initiated_by value from the queue object in the future if desired.
|
||||
InitiatedBy::User,
|
||||
ctx,
|
||||
);
|
||||
@@ -639,7 +669,18 @@ impl SyncQueue {
|
||||
fn update_items_with_new_revision(&mut self, server_id: &str, new_revision: Revision) {
|
||||
for (_item_id, item) in &mut self.queue {
|
||||
match item {
|
||||
QueueItem::UpdateNotebook { revision, id, .. } => {
|
||||
QueueItem::UpdateNotebook { id, revision, .. }
|
||||
| QueueItem::UpdateWorkflow { id, revision, .. }
|
||||
| QueueItem::UpdateCloudPreferences { id, revision, .. }
|
||||
| QueueItem::UpdateEnvVarCollection { id, revision, .. }
|
||||
| QueueItem::UpdateWorkflowEnum { id, revision, .. }
|
||||
| QueueItem::UpdateAIFact { id, revision, .. }
|
||||
| QueueItem::UpdateMCPServer { id, revision, .. }
|
||||
| QueueItem::UpdateAIExecutionProfile { id, revision, .. }
|
||||
| QueueItem::UpdateTemplatableMCPServer { id, revision, .. }
|
||||
| QueueItem::UpdateCloudEnvironment { id, revision, .. }
|
||||
| QueueItem::UpdateScheduledAmbientAgent { id, revision, .. }
|
||||
| QueueItem::UpdateCloudAgentConfig { id, revision, .. } => {
|
||||
Self::maybe_update_queue_item_with_new_revision(
|
||||
&self.client_id_to_server,
|
||||
id,
|
||||
@@ -648,17 +689,11 @@ impl SyncQueue {
|
||||
&new_revision,
|
||||
);
|
||||
}
|
||||
QueueItem::UpdateWorkflow { id, revision, .. } => {
|
||||
Self::maybe_update_queue_item_with_new_revision(
|
||||
&self.client_id_to_server,
|
||||
id,
|
||||
server_id,
|
||||
revision,
|
||||
&new_revision,
|
||||
);
|
||||
}
|
||||
// TODO: should we support this for generic string objects?
|
||||
_ => {}
|
||||
QueueItem::CreateObject { .. }
|
||||
| QueueItem::CreateWorkflow { .. }
|
||||
| QueueItem::BulkCreateGenericStringObjects { .. }
|
||||
| QueueItem::UpdateFolder { .. }
|
||||
| QueueItem::RecordObjectAction { .. } => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -913,7 +948,7 @@ impl SyncQueue {
|
||||
} => {
|
||||
let serialized_model = serialized_model
|
||||
.as_ref()
|
||||
.map(|data| SerializedModel(data.model_as_str().to_owned()));
|
||||
.map(|data| SerializedModel::new(data.model_as_str().to_owned()));
|
||||
|
||||
self.create_object(
|
||||
object_type,
|
||||
@@ -930,6 +965,9 @@ impl SyncQueue {
|
||||
);
|
||||
}
|
||||
QueueItem::BulkCreateGenericStringObjects { owner, objects } => {
|
||||
let in_flight_client_ids = objects.iter().map(|data| data.id).collect();
|
||||
self.in_flight_bulk_create_objects
|
||||
.insert(dequeued_item_id, in_flight_client_ids);
|
||||
for data in &objects {
|
||||
self.waiting_response
|
||||
.entry(data.id.to_string())
|
||||
@@ -964,7 +1002,7 @@ impl SyncQueue {
|
||||
BulkCreateGenericStringObjectsRequest {
|
||||
id: data.id,
|
||||
format: data.format,
|
||||
serialized_model: SerializedModel(
|
||||
serialized_model: SerializedModel::new(
|
||||
data.serialized_model
|
||||
.as_ref()
|
||||
.model_as_str()
|
||||
@@ -1297,7 +1335,7 @@ impl SyncQueue {
|
||||
|
||||
// After an object is created, go through any objects dependent on them and update the associated queue items accordingly.
|
||||
me.update_dependencies_on_creation(
|
||||
&queue_item_id,
|
||||
&QueueDependency::QueueItem(queue_item_id),
|
||||
id,
|
||||
created_cloud_object.server_id_and_type.id,
|
||||
object_type,
|
||||
@@ -1500,7 +1538,7 @@ impl SyncQueue {
|
||||
dependencies.remove(&queue_item_id);
|
||||
}
|
||||
|
||||
self.handle_dependency_success(&queue_item_id);
|
||||
self.handle_dependency_success(&QueueDependency::QueueItem(queue_item_id));
|
||||
|
||||
self.update_items_with_new_revision(uid, revision_and_editor.revision.clone());
|
||||
|
||||
@@ -1520,7 +1558,7 @@ impl SyncQueue {
|
||||
if let Some(dependencies) = self.waiting_response.get_mut(&uid.clone()) {
|
||||
dependencies.remove(&queue_item_id);
|
||||
}
|
||||
self.handle_dependency_success(&queue_item_id);
|
||||
self.handle_dependency_success(&QueueDependency::QueueItem(queue_item_id));
|
||||
}
|
||||
ResponseType::Creation {
|
||||
creation_result:
|
||||
@@ -1540,7 +1578,8 @@ impl SyncQueue {
|
||||
if let Some(dependencies) = self.waiting_response.get_mut(&client_id.to_string()) {
|
||||
dependencies.remove(&queue_item_id);
|
||||
}
|
||||
self.handle_dependency_success(&queue_item_id);
|
||||
let dependency = self.creation_dependency_for_client_id(queue_item_id, client_id);
|
||||
self.handle_dependency_success(&dependency);
|
||||
|
||||
self.update_items_with_new_revision(
|
||||
&server_creation_info.server_id_and_type.id.uid(),
|
||||
@@ -1560,7 +1599,8 @@ impl SyncQueue {
|
||||
if let Some(dependencies) = self.waiting_response.get_mut(uid) {
|
||||
dependencies.remove(&queue_item_id);
|
||||
}
|
||||
self.handle_dependency_success(&queue_item_id);
|
||||
let dependency = self.creation_dependency_for_id(queue_item_id, uid);
|
||||
self.handle_dependency_success(&dependency);
|
||||
ctx.emit(SyncQueueEvent::ObjectCreationFailure {
|
||||
reason: CreationFailureReason::Denied {
|
||||
message,
|
||||
@@ -1576,7 +1616,7 @@ impl SyncQueue {
|
||||
if let Some(dependencies) = self.waiting_response.get_mut(uid) {
|
||||
dependencies.remove(&queue_item_id);
|
||||
}
|
||||
self.handle_dependency_success(&queue_item_id);
|
||||
self.handle_dependency_success(&QueueDependency::QueueItem(queue_item_id));
|
||||
ctx.emit(SyncQueueEvent::ReportObjectActionSucceeded {
|
||||
uid: uid.to_string(),
|
||||
action_timestamp,
|
||||
@@ -1623,7 +1663,8 @@ impl SyncQueue {
|
||||
if let Some(dependencies) = self.waiting_response.get_mut(id) {
|
||||
dependencies.remove(&queue_item_id);
|
||||
}
|
||||
self.handle_dependency_failure(&queue_item_id, ctx);
|
||||
let dependency = self.creation_dependency_for_id(queue_item_id, id);
|
||||
self.handle_dependency_failure(&dependency, ctx);
|
||||
}
|
||||
|
||||
fn handle_update_failure_response(
|
||||
@@ -1641,7 +1682,7 @@ impl SyncQueue {
|
||||
if let Some(dependencies) = self.waiting_response.get_mut(&updated_sync_id.uid()) {
|
||||
dependencies.remove(&queue_item_id);
|
||||
}
|
||||
self.handle_dependency_failure(&queue_item_id, ctx);
|
||||
self.handle_dependency_failure(&QueueDependency::QueueItem(queue_item_id), ctx);
|
||||
ctx.emit(SyncQueueEvent::ObjectUpdateFailure {
|
||||
id: updated_sync_id,
|
||||
})
|
||||
@@ -1657,7 +1698,7 @@ impl SyncQueue {
|
||||
if let Some(dependencies) = self.waiting_response.get_mut(&uid) {
|
||||
dependencies.remove(&queue_item_id);
|
||||
}
|
||||
self.handle_dependency_failure(&queue_item_id, ctx);
|
||||
self.handle_dependency_failure(&QueueDependency::QueueItem(queue_item_id), ctx);
|
||||
ctx.emit(SyncQueueEvent::ReportObjectActionFailed {
|
||||
uid,
|
||||
action_timestamp: timestamp,
|
||||
@@ -1706,10 +1747,45 @@ impl SyncQueue {
|
||||
}
|
||||
}
|
||||
|
||||
fn creation_dependency_for_id(
|
||||
&mut self,
|
||||
queue_item_id: QueueItemId,
|
||||
id: &str,
|
||||
) -> QueueDependency {
|
||||
if let Some(client_id) = ClientId::from_hash(id) {
|
||||
return self.creation_dependency_for_client_id(queue_item_id, client_id);
|
||||
}
|
||||
|
||||
QueueDependency::QueueItem(queue_item_id)
|
||||
}
|
||||
|
||||
fn creation_dependency_for_client_id(
|
||||
&mut self,
|
||||
queue_item_id: QueueItemId,
|
||||
client_id: ClientId,
|
||||
) -> QueueDependency {
|
||||
let Some(client_ids) = self.in_flight_bulk_create_objects.get_mut(&queue_item_id) else {
|
||||
return QueueDependency::QueueItem(queue_item_id);
|
||||
};
|
||||
|
||||
if !client_ids.remove(&client_id) {
|
||||
return QueueDependency::QueueItem(queue_item_id);
|
||||
}
|
||||
|
||||
if client_ids.is_empty() {
|
||||
self.in_flight_bulk_create_objects.remove(&queue_item_id);
|
||||
}
|
||||
|
||||
QueueDependency::BulkCreateGenericStringObject {
|
||||
queue_item_id,
|
||||
client_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a successful object creation, update any dependent objects to refer to the object's new server ID
|
||||
fn update_dependencies_on_creation(
|
||||
&mut self,
|
||||
queue_item_id: &QueueItemId,
|
||||
dependency: &QueueDependency,
|
||||
client_id: ClientId,
|
||||
server_id: ServerId,
|
||||
object_type: ObjectType,
|
||||
@@ -1723,11 +1799,11 @@ impl SyncQueue {
|
||||
for (item_id, queue_item) in self.queue.iter_mut() {
|
||||
match queue_item {
|
||||
QueueItem::CreateWorkflow { model, .. } => {
|
||||
// Only update the workflow if it depends on `queue_item_id`
|
||||
// Only update the workflow if it depends on this dependency.
|
||||
if self
|
||||
.queue_dependencies
|
||||
.get(item_id)
|
||||
.is_some_and(|deps| deps.contains(queue_item_id))
|
||||
.is_some_and(|deps| deps.contains(dependency))
|
||||
{
|
||||
let workflow_model = Arc::make_mut(model);
|
||||
workflow_model.data.replace_object_id(
|
||||
@@ -1737,11 +1813,11 @@ impl SyncQueue {
|
||||
}
|
||||
}
|
||||
QueueItem::UpdateWorkflow { model, .. } => {
|
||||
// Only update the workflow if it depends on `queue_item_id`
|
||||
// Only update the workflow if it depends on this dependency.
|
||||
if self
|
||||
.queue_dependencies
|
||||
.get(item_id)
|
||||
.is_some_and(|deps| deps.contains(queue_item_id))
|
||||
.is_some_and(|deps| deps.contains(dependency))
|
||||
{
|
||||
let workflow_model = Arc::make_mut(model);
|
||||
workflow_model.data.replace_object_id(
|
||||
@@ -1757,16 +1833,16 @@ impl SyncQueue {
|
||||
}
|
||||
|
||||
/// If a request succeeds, update the dependency map by removing it from all dependency sets
|
||||
fn handle_dependency_success(&mut self, item_id: &QueueItemId) {
|
||||
fn handle_dependency_success(&mut self, dependency: &QueueDependency) {
|
||||
for (_, dependencies) in self.queue_dependencies.iter_mut() {
|
||||
dependencies.remove(item_id);
|
||||
dependencies.remove(dependency);
|
||||
}
|
||||
}
|
||||
|
||||
/// If a request fails, emit a failure for every dependent QueueItem
|
||||
fn handle_dependency_failure(
|
||||
&mut self,
|
||||
queue_item_id: &QueueItemId,
|
||||
failed_dependency: &QueueDependency,
|
||||
ctx: &mut ModelContext<SyncQueue>,
|
||||
) {
|
||||
// Filter the queue to get a list of all items dependent on this queue item
|
||||
@@ -1776,7 +1852,7 @@ impl SyncQueue {
|
||||
.filter(|(item_id, _)| {
|
||||
self.queue_dependencies
|
||||
.get(item_id)
|
||||
.is_some_and(|dependency_set| dependency_set.contains(queue_item_id))
|
||||
.is_some_and(|dependency_set| dependency_set.contains(failed_dependency))
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
@@ -1894,7 +1970,7 @@ impl SyncQueue {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn queue_dependencies(&self) -> &HashMap<QueueItemId, HashSet<QueueItemId>> {
|
||||
fn queue_dependencies(&self) -> &HashMap<QueueItemId, HashSet<QueueDependency>> {
|
||||
&self.queue_dependencies
|
||||
}
|
||||
|
||||
@@ -1911,5 +1987,5 @@ impl Entity for SyncQueue {
|
||||
impl SingletonEntity for SyncQueue {}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "sync_queue_test.rs"]
|
||||
#[path = "sync_queue_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
Reference in New Issue
Block a user