@feltdb/core 0.4.12 → 0.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands.js +5 -2
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +16 -1
- package/dist/create/create.js +30 -16
- package/dist/create/docker-compose-generator.js +48 -9
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +2238 -0
- package/dist/create/server-source/Cargo.toml +7 -0
- package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
- package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
- package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
- package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
- package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
- package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
- package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
- package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
- package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
- package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
- package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
- package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
- package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
- package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
- package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
- package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
- package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
- package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
- package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
- package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
- package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
- package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
- package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
- package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
- package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
- package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
- package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/flowspec.d.ts +2 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +28 -4
- package/dist/state-contract.d.ts +7 -1
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-Q0Nx6gLE.js → components-bHTARBin.js} +156 -46
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-BHOmLZF-.js → index-BqKquLuU.js} +8 -8
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
use crate::Operation;
|
|
2
|
+
/// Execution Engine module for event-driven execution runtime
|
|
3
|
+
///
|
|
4
|
+
/// Execution represents a trigger being fired and a capability being executed.
|
|
5
|
+
/// Executions are durable, identifiable, and resumable.
|
|
6
|
+
use serde::{Deserialize, Serialize};
|
|
7
|
+
use serde_json::Value;
|
|
8
|
+
use std::collections::HashMap;
|
|
9
|
+
|
|
10
|
+
/// Execution status enumeration
|
|
11
|
+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
|
12
|
+
pub enum ExecutionStatus {
|
|
13
|
+
/// Waiting to be executed
|
|
14
|
+
Pending,
|
|
15
|
+
/// Currently executing
|
|
16
|
+
Running,
|
|
17
|
+
/// Completed successfully
|
|
18
|
+
Succeeded,
|
|
19
|
+
/// Failed and may be retried
|
|
20
|
+
Failed,
|
|
21
|
+
/// Scheduled for retry
|
|
22
|
+
RetryScheduled,
|
|
23
|
+
/// Cannot be retried
|
|
24
|
+
DeadLettered,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// A durable execution record
|
|
28
|
+
///
|
|
29
|
+
/// Tracks the execution of a capability triggered by an operation.
|
|
30
|
+
/// Provides idempotency and recoverability.
|
|
31
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
32
|
+
pub struct Execution {
|
|
33
|
+
/// Unique execution identifier
|
|
34
|
+
pub execution_id: String,
|
|
35
|
+
|
|
36
|
+
/// The trigger that caused this execution
|
|
37
|
+
pub trigger_id: String,
|
|
38
|
+
|
|
39
|
+
/// Source operation that triggered this execution
|
|
40
|
+
pub source_operation: Operation,
|
|
41
|
+
|
|
42
|
+
/// Capability to execute
|
|
43
|
+
pub capability: String,
|
|
44
|
+
|
|
45
|
+
/// Attempt number (1-based)
|
|
46
|
+
pub attempt: u32,
|
|
47
|
+
|
|
48
|
+
/// Current status
|
|
49
|
+
pub status: ExecutionStatus,
|
|
50
|
+
|
|
51
|
+
/// Optional result value
|
|
52
|
+
pub result: Option<Value>,
|
|
53
|
+
|
|
54
|
+
/// Optional error message
|
|
55
|
+
pub error: Option<String>,
|
|
56
|
+
|
|
57
|
+
/// Timestamp when execution was created (ms since UNIX epoch)
|
|
58
|
+
pub created_ms: u128,
|
|
59
|
+
|
|
60
|
+
/// Timestamp when execution started (ms since UNIX epoch)
|
|
61
|
+
pub started_ms: Option<u128>,
|
|
62
|
+
|
|
63
|
+
/// Timestamp when execution completed (ms since UNIX epoch)
|
|
64
|
+
pub completed_ms: Option<u128>,
|
|
65
|
+
|
|
66
|
+
/// Claim token for single-owner execution semantics
|
|
67
|
+
pub claimed_by: Option<String>,
|
|
68
|
+
|
|
69
|
+
/// Retry policy settings
|
|
70
|
+
pub retry_policy: RetryPolicy,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Retry policy for executions
|
|
74
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
75
|
+
pub struct RetryPolicy {
|
|
76
|
+
/// Maximum number of retry attempts
|
|
77
|
+
pub max_attempts: u32,
|
|
78
|
+
|
|
79
|
+
/// Initial backoff in milliseconds
|
|
80
|
+
pub initial_backoff_ms: u64,
|
|
81
|
+
|
|
82
|
+
/// Maximum backoff in milliseconds
|
|
83
|
+
pub max_backoff_ms: u64,
|
|
84
|
+
|
|
85
|
+
/// Backoff multiplier for exponential backoff
|
|
86
|
+
pub backoff_multiplier: f64,
|
|
87
|
+
|
|
88
|
+
/// Timeout per attempt in milliseconds
|
|
89
|
+
pub timeout_ms: u64,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl Default for RetryPolicy {
|
|
93
|
+
fn default() -> Self {
|
|
94
|
+
Self {
|
|
95
|
+
max_attempts: 3,
|
|
96
|
+
initial_backoff_ms: 100,
|
|
97
|
+
max_backoff_ms: 30000,
|
|
98
|
+
backoff_multiplier: 2.0,
|
|
99
|
+
timeout_ms: 30000,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl Execution {
|
|
105
|
+
/// Create a new execution
|
|
106
|
+
pub fn new(
|
|
107
|
+
execution_id: String,
|
|
108
|
+
trigger_id: String,
|
|
109
|
+
source_operation: Operation,
|
|
110
|
+
capability: String,
|
|
111
|
+
) -> Self {
|
|
112
|
+
let now_ms = crate::now_ms();
|
|
113
|
+
Self {
|
|
114
|
+
execution_id,
|
|
115
|
+
trigger_id,
|
|
116
|
+
source_operation,
|
|
117
|
+
capability,
|
|
118
|
+
attempt: 1,
|
|
119
|
+
status: ExecutionStatus::Pending,
|
|
120
|
+
result: None,
|
|
121
|
+
error: None,
|
|
122
|
+
created_ms: now_ms,
|
|
123
|
+
started_ms: None,
|
|
124
|
+
completed_ms: None,
|
|
125
|
+
claimed_by: None,
|
|
126
|
+
retry_policy: RetryPolicy::default(),
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Mark execution as running
|
|
131
|
+
pub fn mark_running(&mut self, claimed_by: String) {
|
|
132
|
+
self.status = ExecutionStatus::Running;
|
|
133
|
+
self.started_ms = Some(crate::now_ms());
|
|
134
|
+
self.claimed_by = Some(claimed_by);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Mark execution as succeeded
|
|
138
|
+
pub fn mark_succeeded(&mut self, result: Value) {
|
|
139
|
+
self.status = ExecutionStatus::Succeeded;
|
|
140
|
+
self.result = Some(result);
|
|
141
|
+
self.completed_ms = Some(crate::now_ms());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Mark execution as failed
|
|
145
|
+
pub fn mark_failed(&mut self, error: String) {
|
|
146
|
+
self.status = ExecutionStatus::Failed;
|
|
147
|
+
self.error = Some(error);
|
|
148
|
+
self.completed_ms = Some(crate::now_ms());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// Mark execution for retry
|
|
152
|
+
pub fn mark_retry_scheduled(&mut self) {
|
|
153
|
+
if self.attempt < self.retry_policy.max_attempts {
|
|
154
|
+
self.attempt += 1;
|
|
155
|
+
self.status = ExecutionStatus::RetryScheduled;
|
|
156
|
+
self.claimed_by = None;
|
|
157
|
+
} else {
|
|
158
|
+
self.status = ExecutionStatus::DeadLettered;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Get backoff time for next retry attempt
|
|
163
|
+
pub fn get_backoff_ms(&self) -> u64 {
|
|
164
|
+
let backoff = (self.retry_policy.initial_backoff_ms as f64
|
|
165
|
+
* self
|
|
166
|
+
.retry_policy
|
|
167
|
+
.backoff_multiplier
|
|
168
|
+
.powi((self.attempt - 1) as i32)) as u64;
|
|
169
|
+
backoff.min(self.retry_policy.max_backoff_ms)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// Check if execution can be retried
|
|
173
|
+
pub fn can_retry(&self) -> bool {
|
|
174
|
+
matches!(
|
|
175
|
+
self.status,
|
|
176
|
+
ExecutionStatus::Failed | ExecutionStatus::RetryScheduled
|
|
177
|
+
) && self.attempt < self.retry_policy.max_attempts
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Check if execution is terminal (won't change state)
|
|
181
|
+
pub fn is_terminal(&self) -> bool {
|
|
182
|
+
matches!(
|
|
183
|
+
self.status,
|
|
184
|
+
ExecutionStatus::Succeeded | ExecutionStatus::DeadLettered
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Execution queue - tracks all executions
|
|
190
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
191
|
+
pub struct ExecutionQueue {
|
|
192
|
+
/// All executions by execution_id
|
|
193
|
+
#[serde(default)]
|
|
194
|
+
executions: HashMap<String, Execution>,
|
|
195
|
+
|
|
196
|
+
/// Pending executions queue (FIFO)
|
|
197
|
+
#[serde(default)]
|
|
198
|
+
pending_queue: Vec<String>,
|
|
199
|
+
|
|
200
|
+
/// Idempotency map: (instance_id, sequence) -> execution_id
|
|
201
|
+
/// Prevents executing the same operation multiple times
|
|
202
|
+
/// Serialized as a vector of tuples for JSON compatibility
|
|
203
|
+
#[serde(default, rename = "idempotency_map")]
|
|
204
|
+
idempotency_map_vec: Vec<((String, u64), String)>,
|
|
205
|
+
|
|
206
|
+
#[serde(skip)]
|
|
207
|
+
idempotency_map: HashMap<(String, u64), String>,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
impl ExecutionQueue {
|
|
211
|
+
/// Create a new execution queue
|
|
212
|
+
pub fn new() -> Self {
|
|
213
|
+
Self {
|
|
214
|
+
executions: HashMap::new(),
|
|
215
|
+
pending_queue: Vec::new(),
|
|
216
|
+
idempotency_map_vec: Vec::new(),
|
|
217
|
+
idempotency_map: HashMap::new(),
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
impl Default for ExecutionQueue {
|
|
223
|
+
fn default() -> Self {
|
|
224
|
+
Self::new()
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
impl ExecutionQueue {
|
|
229
|
+
/// Sync the vec-based storage with the hashmap for operations
|
|
230
|
+
fn sync_map_from_vec(&mut self) {
|
|
231
|
+
self.idempotency_map.clear();
|
|
232
|
+
for ((instance_id, sequence), exec_id) in &self.idempotency_map_vec {
|
|
233
|
+
self.idempotency_map
|
|
234
|
+
.insert((instance_id.clone(), *sequence), exec_id.clone());
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// Sync the vec-based storage with the hashmap for serialization
|
|
239
|
+
fn sync_vec_from_map(&mut self) {
|
|
240
|
+
self.idempotency_map_vec = self
|
|
241
|
+
.idempotency_map
|
|
242
|
+
.iter()
|
|
243
|
+
.map(|(k, v)| (k.clone(), v.clone()))
|
|
244
|
+
.collect();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Add an execution to the queue
|
|
248
|
+
pub fn add_execution(&mut self, execution: Execution) -> Result<String, String> {
|
|
249
|
+
// Sync map from vec in case we were deserialized
|
|
250
|
+
self.sync_map_from_vec();
|
|
251
|
+
|
|
252
|
+
let execution_id = execution.execution_id.clone();
|
|
253
|
+
|
|
254
|
+
// Check if this operation has already been executed
|
|
255
|
+
let op_key = (
|
|
256
|
+
execution.source_operation.instance_id.clone(),
|
|
257
|
+
execution.source_operation.sequence,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if let Some(existing_id) = self.idempotency_map.get(&op_key) {
|
|
261
|
+
// Operation already has an execution - return existing execution_id
|
|
262
|
+
return Ok(existing_id.clone());
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Record in idempotency map
|
|
266
|
+
self.idempotency_map.insert(op_key, execution_id.clone());
|
|
267
|
+
|
|
268
|
+
// Add to executions
|
|
269
|
+
self.executions.insert(execution_id.clone(), execution);
|
|
270
|
+
|
|
271
|
+
// Add to pending queue
|
|
272
|
+
self.pending_queue.push(execution_id.clone());
|
|
273
|
+
|
|
274
|
+
// Sync vec for serialization
|
|
275
|
+
self.sync_vec_from_map();
|
|
276
|
+
|
|
277
|
+
Ok(execution_id)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/// Get an execution by ID
|
|
281
|
+
pub fn get_execution(&self, execution_id: &str) -> Option<Execution> {
|
|
282
|
+
self.executions.get(execution_id).cloned()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// Update an execution status
|
|
286
|
+
pub fn update_execution(
|
|
287
|
+
&mut self,
|
|
288
|
+
execution_id: &str,
|
|
289
|
+
execution: Execution,
|
|
290
|
+
) -> Result<(), String> {
|
|
291
|
+
if self.executions.contains_key(execution_id) {
|
|
292
|
+
self.executions.insert(execution_id.to_string(), execution);
|
|
293
|
+
Ok(())
|
|
294
|
+
} else {
|
|
295
|
+
Err(format!("Execution not found: {}", execution_id))
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// Get all pending executions
|
|
300
|
+
pub fn get_pending(&self) -> Vec<Execution> {
|
|
301
|
+
self.pending_queue
|
|
302
|
+
.iter()
|
|
303
|
+
.filter_map(|id| self.executions.get(id).cloned())
|
|
304
|
+
.filter(|e| matches!(e.status, ExecutionStatus::Pending))
|
|
305
|
+
.collect()
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/// Get all executions
|
|
309
|
+
pub fn all(&self) -> Vec<Execution> {
|
|
310
|
+
self.executions.values().cloned().collect()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/// Check if an operation has already triggered an execution
|
|
314
|
+
pub fn has_execution_for_operation(&self, instance_id: &str, sequence: u64) -> bool {
|
|
315
|
+
self.idempotency_map
|
|
316
|
+
.contains_key(&(instance_id.to_string(), sequence))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// Get execution ID for an operation (if it exists)
|
|
320
|
+
pub fn get_execution_for_operation(&self, instance_id: &str, sequence: u64) -> Option<String> {
|
|
321
|
+
self.idempotency_map
|
|
322
|
+
.get(&(instance_id.to_string(), sequence))
|
|
323
|
+
.cloned()
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#[cfg(test)]
|
|
328
|
+
mod tests {
|
|
329
|
+
use super::*;
|
|
330
|
+
|
|
331
|
+
#[test]
|
|
332
|
+
fn test_execution_lifecycle() {
|
|
333
|
+
let mut exec = Execution::new(
|
|
334
|
+
"exec-1".to_string(),
|
|
335
|
+
"trigger-1".to_string(),
|
|
336
|
+
Operation::insert(
|
|
337
|
+
1,
|
|
338
|
+
"inst-a".to_string(),
|
|
339
|
+
1,
|
|
340
|
+
"orders:123".to_string(),
|
|
341
|
+
serde_json::json!({"total": 100}),
|
|
342
|
+
"Order".to_string(),
|
|
343
|
+
"orders".to_string(),
|
|
344
|
+
),
|
|
345
|
+
"process_order".to_string(),
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
assert_eq!(exec.status, ExecutionStatus::Pending);
|
|
349
|
+
assert_eq!(exec.attempt, 1);
|
|
350
|
+
|
|
351
|
+
exec.mark_running("inst-a".to_string());
|
|
352
|
+
assert_eq!(exec.status, ExecutionStatus::Running);
|
|
353
|
+
|
|
354
|
+
exec.mark_succeeded(serde_json::json!({"processed": true}));
|
|
355
|
+
assert_eq!(exec.status, ExecutionStatus::Succeeded);
|
|
356
|
+
assert!(exec.result.is_some());
|
|
357
|
+
assert!(exec.is_terminal());
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#[test]
|
|
361
|
+
fn test_execution_idempotency() {
|
|
362
|
+
let mut queue = ExecutionQueue::new();
|
|
363
|
+
|
|
364
|
+
let op = Operation::insert(
|
|
365
|
+
1,
|
|
366
|
+
"inst-a".to_string(),
|
|
367
|
+
1,
|
|
368
|
+
"orders:123".to_string(),
|
|
369
|
+
serde_json::json!({"total": 100}),
|
|
370
|
+
"Order".to_string(),
|
|
371
|
+
"orders".to_string(),
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
let exec1 = Execution::new(
|
|
375
|
+
"exec-1".to_string(),
|
|
376
|
+
"trigger-1".to_string(),
|
|
377
|
+
op.clone(),
|
|
378
|
+
"process_order".to_string(),
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
let id1 = queue.add_execution(exec1).unwrap();
|
|
382
|
+
|
|
383
|
+
// Try to add same operation again
|
|
384
|
+
let exec2 = Execution::new(
|
|
385
|
+
"exec-2".to_string(),
|
|
386
|
+
"trigger-1".to_string(),
|
|
387
|
+
op.clone(),
|
|
388
|
+
"process_order".to_string(),
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
let id2 = queue.add_execution(exec2).unwrap();
|
|
392
|
+
|
|
393
|
+
// Should return the same execution ID (idempotency)
|
|
394
|
+
assert_eq!(id1, id2);
|
|
395
|
+
assert_eq!(queue.all().len(), 1);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
#[test]
|
|
399
|
+
fn test_retry_backoff() {
|
|
400
|
+
let mut exec = Execution::new(
|
|
401
|
+
"exec-1".to_string(),
|
|
402
|
+
"trigger-1".to_string(),
|
|
403
|
+
Operation::insert(
|
|
404
|
+
1,
|
|
405
|
+
"inst-a".to_string(),
|
|
406
|
+
1,
|
|
407
|
+
"orders:123".to_string(),
|
|
408
|
+
serde_json::json!({"total": 100}),
|
|
409
|
+
"Order".to_string(),
|
|
410
|
+
"orders".to_string(),
|
|
411
|
+
),
|
|
412
|
+
"process_order".to_string(),
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
assert_eq!(exec.get_backoff_ms(), 100); // initial backoff
|
|
416
|
+
|
|
417
|
+
exec.mark_failed("Test error".to_string());
|
|
418
|
+
exec.mark_retry_scheduled();
|
|
419
|
+
assert_eq!(exec.attempt, 2);
|
|
420
|
+
assert_eq!(exec.get_backoff_ms(), 200); // exponential backoff
|
|
421
|
+
|
|
422
|
+
exec.mark_retry_scheduled();
|
|
423
|
+
assert_eq!(exec.attempt, 3);
|
|
424
|
+
assert_eq!(exec.get_backoff_ms(), 400);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/// In-process transport using Tokio channels
|
|
2
|
+
///
|
|
3
|
+
/// Provides fast, deterministic delivery of ReplicationMessage for testing.
|
|
4
|
+
/// No network delay, no packet loss by default.
|
|
5
|
+
///
|
|
6
|
+
/// Transport Guarantees:
|
|
7
|
+
/// - Ordered delivery per connection
|
|
8
|
+
/// - Serialization integrity (bincode framing)
|
|
9
|
+
/// - Bounded channel semantics (messages queued until received)
|
|
10
|
+
/// - Connection lifecycle tracking
|
|
11
|
+
///
|
|
12
|
+
/// Deduplication/idempotency is the protocol's responsibility via DistributedTransactionExecutor.
|
|
13
|
+
|
|
14
|
+
use crate::distributed_transactions::ReplicationMessage;
|
|
15
|
+
use crate::replication_protocol::{ProtocolError, ProtocolMetrics, ProtocolTransport};
|
|
16
|
+
use std::sync::Arc;
|
|
17
|
+
use tokio::sync::mpsc;
|
|
18
|
+
|
|
19
|
+
/// In-process message delivery via Tokio channel
|
|
20
|
+
pub struct InProcessTransport {
|
|
21
|
+
local_node_id: String,
|
|
22
|
+
peer_node_id: String,
|
|
23
|
+
tx: mpsc::UnboundedSender<ReplicationMessage>,
|
|
24
|
+
rx: Arc<tokio::sync::Mutex<mpsc::UnboundedReceiver<ReplicationMessage>>>,
|
|
25
|
+
metrics: Arc<ProtocolMetrics>,
|
|
26
|
+
is_connected: bool,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl InProcessTransport {
|
|
30
|
+
/// Create a new in-process transport pair
|
|
31
|
+
pub fn create_pair(
|
|
32
|
+
local_id: String,
|
|
33
|
+
peer_id: String,
|
|
34
|
+
) -> (Self, mpsc::UnboundedReceiver<ReplicationMessage>) {
|
|
35
|
+
let (tx, rx) = mpsc::unbounded_channel();
|
|
36
|
+
|
|
37
|
+
let transport = Self {
|
|
38
|
+
local_node_id: local_id,
|
|
39
|
+
peer_node_id: peer_id,
|
|
40
|
+
tx,
|
|
41
|
+
rx: Arc::new(tokio::sync::Mutex::new(rx)),
|
|
42
|
+
metrics: Arc::new(ProtocolMetrics::new()),
|
|
43
|
+
is_connected: true,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// For the pair variant, return both the transport and a separate receiver
|
|
47
|
+
// This isn't ideal - use create_bidirectional_pair for proper two-way communication
|
|
48
|
+
let (tx2, rx2) = mpsc::unbounded_channel();
|
|
49
|
+
let transport_with_output = Self {
|
|
50
|
+
local_node_id: transport.local_node_id.clone(),
|
|
51
|
+
peer_node_id: transport.peer_node_id.clone(),
|
|
52
|
+
tx: tx2,
|
|
53
|
+
rx: transport.rx.clone(),
|
|
54
|
+
metrics: transport.metrics.clone(),
|
|
55
|
+
is_connected: transport.is_connected,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
(transport_with_output, rx2)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Create a bidirectional pair of transports
|
|
62
|
+
pub fn create_bidirectional_pair(
|
|
63
|
+
local_id: String,
|
|
64
|
+
peer_id: String,
|
|
65
|
+
) -> (Self, Self) {
|
|
66
|
+
let (local_tx, local_rx) = mpsc::unbounded_channel();
|
|
67
|
+
let (peer_tx, peer_rx) = mpsc::unbounded_channel();
|
|
68
|
+
|
|
69
|
+
let local = Self {
|
|
70
|
+
local_node_id: local_id.clone(),
|
|
71
|
+
peer_node_id: peer_id.clone(),
|
|
72
|
+
tx: peer_tx,
|
|
73
|
+
rx: Arc::new(tokio::sync::Mutex::new(local_rx)),
|
|
74
|
+
metrics: Arc::new(ProtocolMetrics::new()),
|
|
75
|
+
is_connected: true,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
let peer = Self {
|
|
79
|
+
local_node_id: peer_id,
|
|
80
|
+
peer_node_id: local_id,
|
|
81
|
+
tx: local_tx,
|
|
82
|
+
rx: Arc::new(tokio::sync::Mutex::new(peer_rx)),
|
|
83
|
+
metrics: Arc::new(ProtocolMetrics::new()),
|
|
84
|
+
is_connected: true,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
(local, peer)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
pub fn metrics(&self) -> &ProtocolMetrics {
|
|
91
|
+
&self.metrics
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#[async_trait::async_trait]
|
|
97
|
+
impl ProtocolTransport for InProcessTransport {
|
|
98
|
+
async fn send(&self, message: ReplicationMessage) -> Result<(), ProtocolError> {
|
|
99
|
+
// Attempt serialization for metrics, but don't fail if it doesn't work
|
|
100
|
+
if let Ok(serialized) = bincode::serialize(&message) {
|
|
101
|
+
self.metrics.record_send(serialized.len());
|
|
102
|
+
} else {
|
|
103
|
+
// Estimate size as fallback
|
|
104
|
+
self.metrics.record_send(1024);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
self.tx
|
|
108
|
+
.send(message)
|
|
109
|
+
.map_err(|_| ProtocolError::TransportError("channel closed".to_string()))?;
|
|
110
|
+
|
|
111
|
+
Ok(())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async fn receive(&self) -> Result<Vec<ReplicationMessage>, ProtocolError> {
|
|
115
|
+
// This is a non-blocking receive - collect all available messages
|
|
116
|
+
let mut messages = Vec::new();
|
|
117
|
+
let mut rx = self.rx.lock().await;
|
|
118
|
+
|
|
119
|
+
loop {
|
|
120
|
+
match rx.try_recv() {
|
|
121
|
+
Ok(msg) => {
|
|
122
|
+
// Attempt serialization for metrics, but don't fail if it doesn't work
|
|
123
|
+
if let Ok(serialized) = bincode::serialize(&msg) {
|
|
124
|
+
self.metrics.record_receive(serialized.len());
|
|
125
|
+
} else {
|
|
126
|
+
self.metrics.record_receive(1024);
|
|
127
|
+
}
|
|
128
|
+
messages.push(msg);
|
|
129
|
+
}
|
|
130
|
+
Err(mpsc::error::TryRecvError::Empty) => break,
|
|
131
|
+
Err(mpsc::error::TryRecvError::Disconnected) => {
|
|
132
|
+
return Err(ProtocolError::TransportError(
|
|
133
|
+
"channel disconnected".to_string(),
|
|
134
|
+
))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Ok(messages)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async fn connect(&mut self, _peer_addr: &str) -> Result<(), ProtocolError> {
|
|
143
|
+
// In-process: connection is already established
|
|
144
|
+
self.is_connected = true;
|
|
145
|
+
self.metrics.record_connection_state_change();
|
|
146
|
+
Ok(())
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async fn disconnect(&mut self) -> Result<(), ProtocolError> {
|
|
150
|
+
self.is_connected = false;
|
|
151
|
+
self.metrics.record_connection_state_change();
|
|
152
|
+
Ok(())
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fn is_connected(&self) -> bool {
|
|
156
|
+
self.is_connected
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#[cfg(test)]
|
|
161
|
+
mod tests {
|
|
162
|
+
use super::*;
|
|
163
|
+
|
|
164
|
+
#[tokio::test]
|
|
165
|
+
async fn in_process_bidirectional_sends_and_receives() {
|
|
166
|
+
let (local, peer) =
|
|
167
|
+
InProcessTransport::create_bidirectional_pair("local".to_string(), "peer".to_string());
|
|
168
|
+
|
|
169
|
+
let msg = ReplicationMessage::new(
|
|
170
|
+
crate::distributed_transactions::TransactionEnvelope::new(
|
|
171
|
+
"local".to_string(),
|
|
172
|
+
1,
|
|
173
|
+
"tx-1".to_string(),
|
|
174
|
+
vec![],
|
|
175
|
+
crate::transactions::ConsistencyContract::local(),
|
|
176
|
+
),
|
|
177
|
+
"local".to_string(),
|
|
178
|
+
"peer".to_string(),
|
|
179
|
+
1,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
local.send(msg).await.expect("send");
|
|
183
|
+
|
|
184
|
+
let received = peer.receive().await.expect("receive");
|
|
185
|
+
assert_eq!(received.len(), 1);
|
|
186
|
+
assert_eq!(peer.metrics().get_messages_received(), 1);
|
|
187
|
+
assert_eq!(local.metrics().get_messages_sent(), 1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#[tokio::test]
|
|
191
|
+
async fn metrics_track_connection_state() {
|
|
192
|
+
let (mut transport, _) =
|
|
193
|
+
InProcessTransport::create_bidirectional_pair("local".to_string(), "peer".to_string());
|
|
194
|
+
|
|
195
|
+
let initial_state = transport.is_connected();
|
|
196
|
+
assert!(initial_state);
|
|
197
|
+
|
|
198
|
+
// Disconnect and check metrics
|
|
199
|
+
transport.disconnect().await.expect("disconnect");
|
|
200
|
+
let metrics = transport.metrics();
|
|
201
|
+
assert_eq!(metrics.get_connection_state_changes(), 1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[tokio::test]
|
|
205
|
+
async fn connection_status_tracked() {
|
|
206
|
+
let (mut transport, _) =
|
|
207
|
+
InProcessTransport::create_bidirectional_pair("local".to_string(), "peer".to_string());
|
|
208
|
+
|
|
209
|
+
assert!(transport.is_connected());
|
|
210
|
+
|
|
211
|
+
transport.disconnect().await.expect("disconnect");
|
|
212
|
+
assert!(!transport.is_connected());
|
|
213
|
+
|
|
214
|
+
transport.connect("fake://addr").await.expect("connect");
|
|
215
|
+
assert!(transport.is_connected());
|
|
216
|
+
|
|
217
|
+
assert_eq!(transport.metrics().get_connection_state_changes(), 2);
|
|
218
|
+
}
|
|
219
|
+
}
|