@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,2214 @@
|
|
|
1
|
+
/// Causal Consistency Dependency Barrier (Gate 4)
|
|
2
|
+
///
|
|
3
|
+
/// Implements the critical invariant: No operation becomes visible at a replica
|
|
4
|
+
/// until all causal dependencies encoded by its StateVersion are satisfied.
|
|
5
|
+
///
|
|
6
|
+
/// Explicit state machine:
|
|
7
|
+
///
|
|
8
|
+
/// received operation O
|
|
9
|
+
/// ↓
|
|
10
|
+
/// check_dependencies(O)
|
|
11
|
+
/// ↓
|
|
12
|
+
/// dependencies_satisfied?
|
|
13
|
+
/// / \
|
|
14
|
+
/// yes no
|
|
15
|
+
/// ↓ ↓
|
|
16
|
+
/// READY WAITING_FOR_CAUSAL_DEPENDENCIES
|
|
17
|
+
/// ↓ ↓
|
|
18
|
+
/// APPLIED (buffered, waiting)
|
|
19
|
+
/// ↓
|
|
20
|
+
/// (when new op arrives that unblocks O)
|
|
21
|
+
/// ↓
|
|
22
|
+
/// re-check_dependencies(O)
|
|
23
|
+
/// ↓
|
|
24
|
+
/// READY
|
|
25
|
+
/// ↓
|
|
26
|
+
/// APPLIED
|
|
27
|
+
|
|
28
|
+
use crate::convergence::VectorClock;
|
|
29
|
+
use std::collections::HashMap;
|
|
30
|
+
|
|
31
|
+
/// Metrics for the causal dependency barrier (Phase 7.0.1 audit instrumentation)
|
|
32
|
+
#[derive(Clone, Default, Debug)]
|
|
33
|
+
pub struct BarrierMetrics {
|
|
34
|
+
/// Number of times frontier was checked for readiness
|
|
35
|
+
pub frontier_checks: u64,
|
|
36
|
+
/// Number of times pending buffer was scanned
|
|
37
|
+
pub pending_scans: u64,
|
|
38
|
+
/// Total entries examined during scans
|
|
39
|
+
pub pending_entries_examined: u64,
|
|
40
|
+
/// Operations released due to frontier advancement
|
|
41
|
+
pub operations_released: u64,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Phase 7.1-opt: Dependency index for selective candidate discovery
|
|
45
|
+
/// Maps (node, timestamp) → list of operation IDs waiting for that dependency
|
|
46
|
+
#[derive(Clone, Default, Debug)]
|
|
47
|
+
pub struct DependencyIndex {
|
|
48
|
+
/// For each (node, timestamp) in a required vector clock,
|
|
49
|
+
/// track which pending operations depend on it
|
|
50
|
+
pub waiters: HashMap<(String, u64), Vec<String>>,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl DependencyIndex {
|
|
54
|
+
/// Get the number of index entries
|
|
55
|
+
pub fn entry_count(&self) -> usize {
|
|
56
|
+
self.waiters.len()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl BarrierMetrics {
|
|
61
|
+
pub fn reset(&mut self) {
|
|
62
|
+
self.frontier_checks = 0;
|
|
63
|
+
self.pending_scans = 0;
|
|
64
|
+
self.pending_entries_examined = 0;
|
|
65
|
+
self.operations_released = 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// State of an operation in the dependency barrier
|
|
70
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
71
|
+
pub enum OperationState {
|
|
72
|
+
/// Operation has been received
|
|
73
|
+
Received,
|
|
74
|
+
/// Operation is waiting for causal dependencies to be satisfied
|
|
75
|
+
WaitingForDependencies {
|
|
76
|
+
required_clock: VectorClock,
|
|
77
|
+
missing_dependencies: Vec<String>, // human-readable list
|
|
78
|
+
},
|
|
79
|
+
/// All dependencies satisfied, ready to apply
|
|
80
|
+
Ready,
|
|
81
|
+
/// Operation has been applied
|
|
82
|
+
Applied,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Represents a pending operation waiting for dependencies
|
|
86
|
+
#[derive(Clone, Debug)]
|
|
87
|
+
pub struct PendingOperation {
|
|
88
|
+
pub operation_id: String,
|
|
89
|
+
pub required_dependencies: VectorClock,
|
|
90
|
+
pub state: OperationState,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
impl PendingOperation {
|
|
94
|
+
pub fn new(operation_id: String, required_dependencies: VectorClock) -> Self {
|
|
95
|
+
Self {
|
|
96
|
+
operation_id,
|
|
97
|
+
required_dependencies,
|
|
98
|
+
state: OperationState::Received,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Causal dependency barrier manager
|
|
104
|
+
pub struct CausalDependencyBarrier {
|
|
105
|
+
/// Current replica's vector clock frontier
|
|
106
|
+
local_frontier: VectorClock,
|
|
107
|
+
/// Previous frontier (used to detect which dimensions advanced)
|
|
108
|
+
prev_frontier: VectorClock,
|
|
109
|
+
/// Operations waiting for dependencies
|
|
110
|
+
pending: HashMap<String, PendingOperation>,
|
|
111
|
+
/// Operations that have been applied
|
|
112
|
+
applied: Vec<String>,
|
|
113
|
+
/// Metrics for Phase 7.0.1 audit and benchmarking
|
|
114
|
+
metrics: BarrierMetrics,
|
|
115
|
+
/// Phase 7.1-opt: Dependency index for selective candidate discovery
|
|
116
|
+
dependency_index: DependencyIndex,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
impl CausalDependencyBarrier {
|
|
120
|
+
pub fn new() -> Self {
|
|
121
|
+
Self {
|
|
122
|
+
local_frontier: VectorClock::new(),
|
|
123
|
+
prev_frontier: VectorClock::new(),
|
|
124
|
+
pending: HashMap::new(),
|
|
125
|
+
applied: Vec::new(),
|
|
126
|
+
metrics: BarrierMetrics::default(),
|
|
127
|
+
dependency_index: DependencyIndex::default(),
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Get current metrics (for testing and benchmarking)
|
|
132
|
+
pub fn metrics(&self) -> BarrierMetrics {
|
|
133
|
+
self.metrics.clone()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Reset metrics (for testing and benchmarking)
|
|
137
|
+
pub fn clear_metrics(&mut self) {
|
|
138
|
+
self.metrics.reset();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Get current metrics snapshot (for testing and benchmarking)
|
|
142
|
+
pub fn get_metrics(&self) -> BarrierMetrics {
|
|
143
|
+
self.metrics.clone()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// Get reference to dependency index (for testing and benchmarking)
|
|
147
|
+
pub fn get_dependency_index(&self) -> &DependencyIndex {
|
|
148
|
+
&self.dependency_index
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// Get previous frontier (for benchmarking frontier management)
|
|
152
|
+
pub fn get_prev_frontier_mut(&mut self) -> &mut VectorClock {
|
|
153
|
+
&mut self.prev_frontier
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// Get current frontier (for testing)
|
|
157
|
+
pub fn get_frontier_mut(&mut self) -> &mut VectorClock {
|
|
158
|
+
&mut self.local_frontier
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// Receive an operation and check if its dependencies are satisfied
|
|
162
|
+
pub fn receive_operation(
|
|
163
|
+
&mut self,
|
|
164
|
+
operation_id: String,
|
|
165
|
+
required_dependencies: VectorClock,
|
|
166
|
+
) -> ReceiveResult {
|
|
167
|
+
// Track frontier check (Phase 7.0.1 instrumentation)
|
|
168
|
+
#[cfg(test)]
|
|
169
|
+
{
|
|
170
|
+
self.metrics.frontier_checks += 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check if dependencies are already satisfied
|
|
174
|
+
if self.local_frontier.happens_before(&required_dependencies) {
|
|
175
|
+
// Dependencies not yet satisfied, buffer the operation
|
|
176
|
+
let missing = self.compute_missing_dependencies(&required_dependencies);
|
|
177
|
+
let pending_op = PendingOperation {
|
|
178
|
+
operation_id: operation_id.clone(),
|
|
179
|
+
required_dependencies: required_dependencies.clone(),
|
|
180
|
+
state: OperationState::WaitingForDependencies {
|
|
181
|
+
required_clock: required_dependencies.clone(),
|
|
182
|
+
missing_dependencies: missing,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
self.pending.insert(operation_id.clone(), pending_op);
|
|
186
|
+
|
|
187
|
+
// Phase 7.1-opt: Index this operation for later candidate discovery
|
|
188
|
+
#[cfg(test)]
|
|
189
|
+
self.index_operation(&operation_id, &required_dependencies);
|
|
190
|
+
|
|
191
|
+
ReceiveResult::BufferedWaitingForDependencies { operation_id }
|
|
192
|
+
} else {
|
|
193
|
+
// Dependencies are satisfied, operation can be applied immediately
|
|
194
|
+
ReceiveResult::ReadyToApply { operation_id }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Apply a ready operation and advance the frontier
|
|
199
|
+
pub fn apply_operation(
|
|
200
|
+
&mut self,
|
|
201
|
+
operation_id: &str,
|
|
202
|
+
operation_clock: &VectorClock,
|
|
203
|
+
) -> ApplyResult {
|
|
204
|
+
// Remove from pending if present
|
|
205
|
+
if let Some(pending_op) = self.pending.remove(operation_id) {
|
|
206
|
+
// Phase 7.1-opt: Unindex when operation is removed
|
|
207
|
+
#[cfg(test)]
|
|
208
|
+
self.unindex_operation(operation_id, &pending_op.required_dependencies);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Advance the frontier
|
|
212
|
+
#[cfg(test)]
|
|
213
|
+
{
|
|
214
|
+
self.prev_frontier.clone_from(&self.local_frontier);
|
|
215
|
+
}
|
|
216
|
+
self.local_frontier.merge(operation_clock);
|
|
217
|
+
|
|
218
|
+
// Record as applied
|
|
219
|
+
self.applied.push(operation_id.to_string());
|
|
220
|
+
|
|
221
|
+
// Check if this unblocks any waiting operations
|
|
222
|
+
let newly_ready = self.check_unblocked_operations();
|
|
223
|
+
|
|
224
|
+
ApplyResult {
|
|
225
|
+
operation_id: operation_id.to_string(),
|
|
226
|
+
newly_unblocked: newly_ready,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Get current state of an operation
|
|
231
|
+
pub fn get_operation_state(&self, operation_id: &str) -> Option<OperationState> {
|
|
232
|
+
if self.applied.contains(&operation_id.to_string()) {
|
|
233
|
+
Some(OperationState::Applied)
|
|
234
|
+
} else if let Some(pending) = self.pending.get(operation_id) {
|
|
235
|
+
Some(pending.state.clone())
|
|
236
|
+
} else {
|
|
237
|
+
None
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Get all pending operations
|
|
242
|
+
pub fn get_pending_operations(&self) -> Vec<PendingOperation> {
|
|
243
|
+
self.pending.values().cloned().collect()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Get count of operations in each state
|
|
247
|
+
pub fn get_state_counts(&self) -> StateCounters {
|
|
248
|
+
let waiting_count = self.pending.len();
|
|
249
|
+
let applied_count = self.applied.len();
|
|
250
|
+
|
|
251
|
+
StateCounters {
|
|
252
|
+
received: 0,
|
|
253
|
+
waiting_for_dependencies: waiting_count,
|
|
254
|
+
ready: 0,
|
|
255
|
+
applied: applied_count,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Reference implementation: Check all pending operations (Phase 7.1 baseline)
|
|
260
|
+
/// Full-scan implementation: examines every pending operation
|
|
261
|
+
/// Used as correctness oracle for differential testing
|
|
262
|
+
pub fn check_unblocked_operations_full_scan(&mut self) -> Vec<String> {
|
|
263
|
+
// Track pending scan (Phase 7.0.1 instrumentation)
|
|
264
|
+
if !self.pending.is_empty() {
|
|
265
|
+
self.metrics.pending_scans += 1;
|
|
266
|
+
self.metrics.pending_entries_examined += self.pending.len() as u64;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let mut newly_ready = Vec::new();
|
|
270
|
+
let mut to_ready = Vec::new();
|
|
271
|
+
|
|
272
|
+
for (op_id, pending_op) in self.pending.iter() {
|
|
273
|
+
if !self.local_frontier.happens_before(&pending_op.required_dependencies) {
|
|
274
|
+
to_ready.push(op_id.clone());
|
|
275
|
+
newly_ready.push(op_id.clone());
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Track operations released (Phase 7.0.1 instrumentation)
|
|
280
|
+
self.metrics.operations_released += newly_ready.len() as u64;
|
|
281
|
+
|
|
282
|
+
for op_id in to_ready {
|
|
283
|
+
if let Some(pending) = self.pending.get_mut(&op_id) {
|
|
284
|
+
pending.state = OperationState::Ready;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
newly_ready
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/// Indexed implementation: selective candidate discovery via dependency index
|
|
292
|
+
/// Phase 7.1-opt: Only examines operations whose dependencies changed
|
|
293
|
+
/// Production release path (validated faster in all tested scenarios)
|
|
294
|
+
pub fn check_unblocked_operations_indexed(&mut self) -> Vec<String> {
|
|
295
|
+
let mut newly_ready = Vec::new();
|
|
296
|
+
let mut candidates = std::collections::HashSet::new();
|
|
297
|
+
|
|
298
|
+
// Find operations affected by frontier advancement
|
|
299
|
+
for (node, old_ts) in &self.prev_frontier.clocks {
|
|
300
|
+
let new_ts = self.local_frontier.get(node);
|
|
301
|
+
if new_ts > *old_ts {
|
|
302
|
+
// Frontier advanced for this node
|
|
303
|
+
// Operations waiting for timestamps in (old_ts, new_ts] are candidates
|
|
304
|
+
for ts in (*old_ts + 1)..=new_ts {
|
|
305
|
+
if let Some(waiters) = self.dependency_index.waiters.get(&(node.clone(), ts)) {
|
|
306
|
+
for op_id in waiters {
|
|
307
|
+
candidates.insert(op_id.clone());
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Also check for new nodes that appeared in frontier
|
|
315
|
+
for (node, new_ts) in &self.local_frontier.clocks {
|
|
316
|
+
if !self.prev_frontier.clocks.contains_key(node) {
|
|
317
|
+
// New node appeared, check all dependencies on it
|
|
318
|
+
for ts in 1..=*new_ts {
|
|
319
|
+
if let Some(waiters) = self.dependency_index.waiters.get(&(node.clone(), ts)) {
|
|
320
|
+
for op_id in waiters {
|
|
321
|
+
candidates.insert(op_id.clone());
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Track candidate count for instrumentation
|
|
329
|
+
let candidate_count = candidates.len();
|
|
330
|
+
|
|
331
|
+
// Test candidates using readiness theorem
|
|
332
|
+
let mut to_ready = Vec::new();
|
|
333
|
+
for op_id in candidates {
|
|
334
|
+
if let Some(pending_op) = self.pending.get(&op_id) {
|
|
335
|
+
if !self.local_frontier.happens_before(&pending_op.required_dependencies) {
|
|
336
|
+
to_ready.push(op_id.clone());
|
|
337
|
+
newly_ready.push(op_id);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Track operations released (Phase 7.0.1 instrumentation)
|
|
343
|
+
self.metrics.operations_released += newly_ready.len() as u64;
|
|
344
|
+
if candidate_count > 0 {
|
|
345
|
+
self.metrics.pending_scans += 1;
|
|
346
|
+
self.metrics.pending_entries_examined += candidate_count as u64;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Update state (keep in index for discovery purposes)
|
|
350
|
+
for op_id in to_ready {
|
|
351
|
+
if let Some(pending) = self.pending.get_mut(&op_id) {
|
|
352
|
+
pending.state = OperationState::Ready;
|
|
353
|
+
// Note: Don't unindex here - operation remains in index for potential re-discovery
|
|
354
|
+
// Unindex only happens when operation is actually removed from pending via apply_operation
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
newly_ready
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/// Production dispatch: Check which pending operations have had their dependencies satisfied
|
|
362
|
+
/// Phase 7.1.3 crossover analysis shows indexed is faster in all tested scenarios
|
|
363
|
+
/// Routes to indexed implementation for selective candidate discovery
|
|
364
|
+
pub fn check_unblocked_operations(&mut self) -> Vec<String> {
|
|
365
|
+
self.check_unblocked_operations_indexed()
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/// Compute which nodes/clocks are missing to satisfy dependencies
|
|
369
|
+
fn compute_missing_dependencies(&self, required: &VectorClock) -> Vec<String> {
|
|
370
|
+
let mut missing = Vec::new();
|
|
371
|
+
|
|
372
|
+
for (node, required_ts) in &required.clocks {
|
|
373
|
+
let current_ts = self.local_frontier.get(node);
|
|
374
|
+
if current_ts < *required_ts {
|
|
375
|
+
missing.push(format!(
|
|
376
|
+
"{}:{} (have {})",
|
|
377
|
+
node, required_ts, current_ts
|
|
378
|
+
));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
missing
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/// Get the current frontier
|
|
386
|
+
pub fn get_frontier(&self) -> &VectorClock {
|
|
387
|
+
&self.local_frontier
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/// Manually advance frontier (for testing or external coordination)
|
|
391
|
+
pub fn advance_frontier(&mut self, clock: &VectorClock) {
|
|
392
|
+
#[cfg(test)]
|
|
393
|
+
{
|
|
394
|
+
self.prev_frontier.clone_from(&self.local_frontier);
|
|
395
|
+
}
|
|
396
|
+
self.local_frontier.merge(clock);
|
|
397
|
+
self.check_unblocked_operations();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/// Phase 7.1-opt: Index an operation by its required dependencies
|
|
401
|
+
#[cfg(test)]
|
|
402
|
+
fn index_operation(&mut self, op_id: &str, required: &VectorClock) {
|
|
403
|
+
for (node, timestamp) in &required.clocks {
|
|
404
|
+
self.dependency_index.waiters
|
|
405
|
+
.entry((node.clone(), *timestamp))
|
|
406
|
+
.or_insert_with(Vec::new)
|
|
407
|
+
.push(op_id.to_string());
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/// Phase 7.1-opt: Remove an operation from the dependency index
|
|
412
|
+
#[cfg(test)]
|
|
413
|
+
fn unindex_operation(&mut self, op_id: &str, required: &VectorClock) {
|
|
414
|
+
for (node, timestamp) in &required.clocks {
|
|
415
|
+
if let Some(waiters) = self.dependency_index.waiters.get_mut(&(node.clone(), *timestamp)) {
|
|
416
|
+
waiters.retain(|id| id != op_id);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/// Check if operation is ready based on frontier alone (Phase 7.0.1: readiness theorem)
|
|
422
|
+
/// Returns true if required_vc <= applied_frontier
|
|
423
|
+
pub fn is_ready_frontier(&self, required: &VectorClock) -> bool {
|
|
424
|
+
// Operation is ready if its required dependencies don't happen-before our frontier
|
|
425
|
+
!self.local_frontier.happens_before(required)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/// Check if operation is ready using current buffer-based logic (Phase 7.0.1: readiness theorem)
|
|
429
|
+
/// This is the reference implementation to compare against frontier check
|
|
430
|
+
pub fn is_ready_buffer(&self, required: &VectorClock) -> bool {
|
|
431
|
+
// Current implementation: same as is_ready_frontier
|
|
432
|
+
// This method exists to allow comparing future optimizations
|
|
433
|
+
!self.local_frontier.happens_before(required)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
impl Default for CausalDependencyBarrier {
|
|
438
|
+
fn default() -> Self {
|
|
439
|
+
Self::new()
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/// Result of receiving an operation
|
|
444
|
+
#[derive(Clone, Debug)]
|
|
445
|
+
pub enum ReceiveResult {
|
|
446
|
+
/// Operation is buffered, waiting for dependencies
|
|
447
|
+
BufferedWaitingForDependencies { operation_id: String },
|
|
448
|
+
/// Operation can be applied immediately
|
|
449
|
+
ReadyToApply { operation_id: String },
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/// Result of applying an operation
|
|
453
|
+
#[derive(Clone, Debug)]
|
|
454
|
+
pub struct ApplyResult {
|
|
455
|
+
pub operation_id: String,
|
|
456
|
+
pub newly_unblocked: Vec<String>,
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/// Counters for operation states
|
|
460
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
461
|
+
pub struct StateCounters {
|
|
462
|
+
pub received: usize,
|
|
463
|
+
pub waiting_for_dependencies: usize,
|
|
464
|
+
pub ready: usize,
|
|
465
|
+
pub applied: usize,
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#[cfg(test)]
|
|
469
|
+
mod tests {
|
|
470
|
+
use super::*;
|
|
471
|
+
|
|
472
|
+
#[test]
|
|
473
|
+
fn test_barrier_creation() {
|
|
474
|
+
let barrier = CausalDependencyBarrier::new();
|
|
475
|
+
assert_eq!(barrier.get_pending_operations().len(), 0);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/// Phase 7.0.2: Verify readiness theorem and buffer independence
|
|
479
|
+
/// This test confirms that receive_operation on a satisfied dependency
|
|
480
|
+
/// produces consistent latency regardless of pending buffer size.
|
|
481
|
+
#[test]
|
|
482
|
+
fn phase_7_0_2_readiness_independent_of_pending_buffer() {
|
|
483
|
+
// Theorem: ready(op) ⟺ required_vc <= applied_frontier
|
|
484
|
+
// Should be true regardless of buffer contents
|
|
485
|
+
|
|
486
|
+
let required = {
|
|
487
|
+
let mut vc = VectorClock::new();
|
|
488
|
+
vc.increment("node_a");
|
|
489
|
+
vc.increment("node_b");
|
|
490
|
+
vc
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// Test with different buffer sizes
|
|
494
|
+
for buffer_size in [0, 10, 100] {
|
|
495
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
496
|
+
barrier.clear_metrics();
|
|
497
|
+
|
|
498
|
+
// Advance frontier to satisfy requirement
|
|
499
|
+
barrier.advance_frontier(&required);
|
|
500
|
+
barrier.clear_metrics(); // Reset metrics after setup
|
|
501
|
+
|
|
502
|
+
// Pre-fill buffer with unrelated operations
|
|
503
|
+
for i in 0..buffer_size {
|
|
504
|
+
let mut unrelated = VectorClock::new();
|
|
505
|
+
unrelated.increment(&format!("other_{}", i));
|
|
506
|
+
let _ = barrier.receive_operation(format!("op_buffer_{}", i), unrelated);
|
|
507
|
+
}
|
|
508
|
+
barrier.clear_metrics(); // Reset metrics after filling
|
|
509
|
+
|
|
510
|
+
// Critical test: receive satisfied operation
|
|
511
|
+
// Expected: Should be ready, metrics should show frontier check not buffer scan
|
|
512
|
+
let result = barrier.receive_operation(
|
|
513
|
+
"op_test".to_string(),
|
|
514
|
+
required.clone(),
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
let metrics = barrier.metrics();
|
|
518
|
+
|
|
519
|
+
// Check result
|
|
520
|
+
match result {
|
|
521
|
+
ReceiveResult::ReadyToApply { .. } => {
|
|
522
|
+
// Correct: dependencies satisfied
|
|
523
|
+
assert!(
|
|
524
|
+
true,
|
|
525
|
+
"Operation should be ReadyToApply when frontier satisfies"
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
ReceiveResult::BufferedWaitingForDependencies { .. } => {
|
|
529
|
+
panic!(
|
|
530
|
+
"Operation should be ready! Frontier was advanced with requirement: {:?}",
|
|
531
|
+
required
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Check instrumentation
|
|
537
|
+
// Should have 1 frontier check, 0 pending scans (on receive path)
|
|
538
|
+
println!("Buffer size: {}", buffer_size);
|
|
539
|
+
println!(
|
|
540
|
+
" frontier_checks: {} (expected 1)",
|
|
541
|
+
metrics.frontier_checks
|
|
542
|
+
);
|
|
543
|
+
println!(
|
|
544
|
+
" pending_scans: {} (expected 0 on receive path)",
|
|
545
|
+
metrics.pending_scans
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
// The key invariant: pending_scans should be 0 on receive_operation
|
|
549
|
+
// If this fails, it means receive_operation is scanning the buffer
|
|
550
|
+
assert_eq!(
|
|
551
|
+
metrics.pending_scans, 0,
|
|
552
|
+
"receive_operation should not scan pending buffer; \
|
|
553
|
+
buffer-size-dependent cost would indicate buffer scanning on readiness path"
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
#[test]
|
|
559
|
+
fn test_operation_with_no_dependencies() {
|
|
560
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
561
|
+
let op_clock = VectorClock::new();
|
|
562
|
+
|
|
563
|
+
let result = barrier.receive_operation("op1".to_string(), op_clock);
|
|
564
|
+
assert!(matches!(result, ReceiveResult::ReadyToApply { .. }));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
#[test]
|
|
568
|
+
fn test_operation_buffered_when_dependencies_missing() {
|
|
569
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
570
|
+
|
|
571
|
+
let mut required = VectorClock::new();
|
|
572
|
+
required.increment("node_a");
|
|
573
|
+
required.increment("node_a"); // {node_a: 2}
|
|
574
|
+
|
|
575
|
+
let result = barrier.receive_operation("op1".to_string(), required);
|
|
576
|
+
assert!(matches!(result, ReceiveResult::BufferedWaitingForDependencies { .. }));
|
|
577
|
+
assert_eq!(barrier.get_pending_operations().len(), 1);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
#[test]
|
|
581
|
+
fn test_unblocking_when_dependency_satisfied() {
|
|
582
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
583
|
+
|
|
584
|
+
// Operation requires {node_a: 2}
|
|
585
|
+
let mut required = VectorClock::new();
|
|
586
|
+
required.increment("node_a");
|
|
587
|
+
required.increment("node_a"); // {node_a: 2}
|
|
588
|
+
|
|
589
|
+
let result = barrier.receive_operation("op1".to_string(), required);
|
|
590
|
+
assert!(matches!(result, ReceiveResult::BufferedWaitingForDependencies { .. }));
|
|
591
|
+
assert_eq!(barrier.get_pending_operations().len(), 1);
|
|
592
|
+
|
|
593
|
+
// Advance frontier to satisfy dependency
|
|
594
|
+
let mut satisfying_clock = VectorClock::new();
|
|
595
|
+
satisfying_clock.increment("node_a");
|
|
596
|
+
satisfying_clock.increment("node_a");
|
|
597
|
+
|
|
598
|
+
barrier.advance_frontier(&satisfying_clock);
|
|
599
|
+
|
|
600
|
+
// Now op1 should be ready
|
|
601
|
+
let pending = barrier.get_pending_operations();
|
|
602
|
+
assert_eq!(pending.len(), 1);
|
|
603
|
+
assert_eq!(pending[0].state, OperationState::Ready);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
#[test]
|
|
607
|
+
fn test_out_of_order_arrival_buffering() {
|
|
608
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
609
|
+
|
|
610
|
+
// Op2 depends on Op1 having been applied
|
|
611
|
+
// Op1 has no dependencies
|
|
612
|
+
let op1_required = VectorClock::new(); // Op1 requires nothing
|
|
613
|
+
|
|
614
|
+
let mut op2_required = VectorClock::new();
|
|
615
|
+
op2_required.increment("node_a"); // Op2 requires {node_a: 1}
|
|
616
|
+
|
|
617
|
+
// Receive Op2 before Op1
|
|
618
|
+
let result = barrier.receive_operation("op2".to_string(), op2_required.clone());
|
|
619
|
+
assert!(matches!(result, ReceiveResult::BufferedWaitingForDependencies { .. }));
|
|
620
|
+
|
|
621
|
+
// Receive Op1 (which has no dependencies)
|
|
622
|
+
let result = barrier.receive_operation("op1".to_string(), op1_required.clone());
|
|
623
|
+
assert!(matches!(result, ReceiveResult::ReadyToApply { .. }));
|
|
624
|
+
|
|
625
|
+
// Apply Op1, which contributes {node_a: 1} to the frontier
|
|
626
|
+
let mut op1_contribution = VectorClock::new();
|
|
627
|
+
op1_contribution.increment("node_a");
|
|
628
|
+
barrier.apply_operation("op1", &op1_contribution);
|
|
629
|
+
|
|
630
|
+
// Now Op2 should be unblocked because frontier is {node_a: 1}
|
|
631
|
+
let pending = barrier.get_pending_operations();
|
|
632
|
+
assert_eq!(pending.len(), 1);
|
|
633
|
+
assert_eq!(pending[0].operation_id, "op2");
|
|
634
|
+
assert_eq!(pending[0].state, OperationState::Ready);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
#[test]
|
|
638
|
+
fn test_dependency_cascade() {
|
|
639
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
640
|
+
|
|
641
|
+
// Op1 has no dependencies
|
|
642
|
+
let op1_clock = VectorClock::new();
|
|
643
|
+
|
|
644
|
+
// Op2 depends on Op1: {node_a: 1}
|
|
645
|
+
let mut op2_required = VectorClock::new();
|
|
646
|
+
op2_required.increment("node_a");
|
|
647
|
+
|
|
648
|
+
// Op3 depends on Op2: {node_a: 2}
|
|
649
|
+
let mut op3_required = VectorClock::new();
|
|
650
|
+
op3_required.increment("node_a");
|
|
651
|
+
op3_required.increment("node_a");
|
|
652
|
+
|
|
653
|
+
// Receive in order: Op3, Op2, Op1
|
|
654
|
+
barrier.receive_operation("op3".to_string(), op3_required);
|
|
655
|
+
barrier.receive_operation("op2".to_string(), op2_required);
|
|
656
|
+
barrier.receive_operation("op1".to_string(), op1_clock.clone());
|
|
657
|
+
|
|
658
|
+
// All should be buffered except Op1 (which can apply immediately)
|
|
659
|
+
assert_eq!(barrier.get_pending_operations().len(), 2); // Op2 and Op3
|
|
660
|
+
|
|
661
|
+
// Apply Op1
|
|
662
|
+
let mut apply_clock = VectorClock::new();
|
|
663
|
+
apply_clock.increment("node_a");
|
|
664
|
+
barrier.apply_operation("op1", &apply_clock);
|
|
665
|
+
|
|
666
|
+
// Now Op2 should be ready
|
|
667
|
+
let pending = barrier.get_pending_operations();
|
|
668
|
+
let op2_state = pending.iter().find(|p| p.operation_id == "op2").unwrap();
|
|
669
|
+
assert_eq!(op2_state.state, OperationState::Ready);
|
|
670
|
+
|
|
671
|
+
// Apply Op2
|
|
672
|
+
let mut apply_clock2 = VectorClock::new();
|
|
673
|
+
apply_clock2.increment("node_a");
|
|
674
|
+
apply_clock2.increment("node_a");
|
|
675
|
+
barrier.apply_operation("op2", &apply_clock2);
|
|
676
|
+
|
|
677
|
+
// Now Op3 should be ready
|
|
678
|
+
let pending = barrier.get_pending_operations();
|
|
679
|
+
let op3_state = pending.iter().find(|p| p.operation_id == "op3").unwrap();
|
|
680
|
+
assert_eq!(op3_state.state, OperationState::Ready);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
#[test]
|
|
684
|
+
fn test_state_counts() {
|
|
685
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
686
|
+
|
|
687
|
+
let mut required = VectorClock::new();
|
|
688
|
+
required.increment("node_a");
|
|
689
|
+
|
|
690
|
+
barrier.receive_operation("op1".to_string(), required.clone());
|
|
691
|
+
barrier.receive_operation("op2".to_string(), required);
|
|
692
|
+
|
|
693
|
+
let counts = barrier.get_state_counts();
|
|
694
|
+
assert_eq!(counts.waiting_for_dependencies, 2);
|
|
695
|
+
assert_eq!(counts.applied, 0);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
#[test]
|
|
699
|
+
fn test_causal_invariant_preserved() {
|
|
700
|
+
// Verify that operations are applied in causal order
|
|
701
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
702
|
+
|
|
703
|
+
// Op1 has no dependencies
|
|
704
|
+
let op1_required = VectorClock::new();
|
|
705
|
+
|
|
706
|
+
// Op2 requires {a: 2}
|
|
707
|
+
let mut op2_required = VectorClock::new();
|
|
708
|
+
op2_required.increment("a");
|
|
709
|
+
op2_required.increment("a");
|
|
710
|
+
|
|
711
|
+
// Receive Op2 before Op1 (out of order)
|
|
712
|
+
barrier.receive_operation("op2".to_string(), op2_required.clone());
|
|
713
|
+
|
|
714
|
+
// Op1 should be ready immediately (no dependencies)
|
|
715
|
+
let result = barrier.receive_operation("op1".to_string(), op1_required);
|
|
716
|
+
assert!(matches!(result, ReceiveResult::ReadyToApply { .. }));
|
|
717
|
+
|
|
718
|
+
// After applying Op1, it contributes {a: 1} to frontier
|
|
719
|
+
let mut op1_contribution = VectorClock::new();
|
|
720
|
+
op1_contribution.increment("a");
|
|
721
|
+
barrier.apply_operation("op1", &op1_contribution);
|
|
722
|
+
|
|
723
|
+
// Op2 still waiting (needs a:2, but frontier is only a:1)
|
|
724
|
+
let pending = barrier.get_pending_operations();
|
|
725
|
+
let op2_state = pending.iter().find(|p| p.operation_id == "op2").unwrap();
|
|
726
|
+
assert!(matches!(
|
|
727
|
+
op2_state.state,
|
|
728
|
+
OperationState::WaitingForDependencies { .. }
|
|
729
|
+
));
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
#[test]
|
|
733
|
+
fn test_readiness_theorem_equivalence() {
|
|
734
|
+
// Phase 7.0.1: Prove that frontier-based and buffer-based readiness checks are equivalent
|
|
735
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
736
|
+
|
|
737
|
+
// Create a required vector clock
|
|
738
|
+
let mut required = VectorClock::new();
|
|
739
|
+
required.increment("node_a");
|
|
740
|
+
required.increment("node_b");
|
|
741
|
+
|
|
742
|
+
// Before frontier is advanced, operation should NOT be ready
|
|
743
|
+
assert!(!barrier.is_ready_frontier(&required));
|
|
744
|
+
assert!(!barrier.is_ready_buffer(&required));
|
|
745
|
+
|
|
746
|
+
// Advance frontier to satisfy dependencies
|
|
747
|
+
let mut satisfying = VectorClock::new();
|
|
748
|
+
satisfying.increment("node_a");
|
|
749
|
+
satisfying.increment("node_b");
|
|
750
|
+
barrier.advance_frontier(&satisfying);
|
|
751
|
+
|
|
752
|
+
// Now both methods should agree: operation IS ready
|
|
753
|
+
assert!(barrier.is_ready_frontier(&required));
|
|
754
|
+
assert!(barrier.is_ready_buffer(&required));
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
#[test]
|
|
758
|
+
fn test_instrumentation_frontier_checks() {
|
|
759
|
+
// Phase 7.0.1: Verify frontier_checks counter is incremented
|
|
760
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
761
|
+
assert_eq!(barrier.metrics().frontier_checks, 0);
|
|
762
|
+
|
|
763
|
+
let required = VectorClock::new();
|
|
764
|
+
barrier.receive_operation("op1".to_string(), required.clone());
|
|
765
|
+
assert_eq!(barrier.metrics().frontier_checks, 1);
|
|
766
|
+
|
|
767
|
+
barrier.receive_operation("op2".to_string(), required);
|
|
768
|
+
assert_eq!(barrier.metrics().frontier_checks, 2);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
#[test]
|
|
772
|
+
fn test_instrumentation_pending_scans() {
|
|
773
|
+
// Phase 7.0.1: Verify pending_scans counter when checking unblocked operations
|
|
774
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
775
|
+
barrier.clear_metrics();
|
|
776
|
+
|
|
777
|
+
// Add buffered operation
|
|
778
|
+
let mut required = VectorClock::new();
|
|
779
|
+
required.increment("node_a");
|
|
780
|
+
barrier.receive_operation("op_buffered".to_string(), required);
|
|
781
|
+
|
|
782
|
+
assert_eq!(barrier.metrics().pending_scans, 0);
|
|
783
|
+
assert_eq!(barrier.metrics().pending_entries_examined, 0);
|
|
784
|
+
|
|
785
|
+
// Advance frontier to trigger check_unblocked_operations
|
|
786
|
+
let mut satisfying = VectorClock::new();
|
|
787
|
+
satisfying.increment("node_a");
|
|
788
|
+
barrier.advance_frontier(&satisfying);
|
|
789
|
+
|
|
790
|
+
// Should have scanned pending buffer
|
|
791
|
+
assert_eq!(barrier.metrics().pending_scans, 1);
|
|
792
|
+
assert_eq!(barrier.metrics().pending_entries_examined, 1);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
#[test]
|
|
796
|
+
fn test_instrumentation_operations_released() {
|
|
797
|
+
// Phase 7.0.1: Verify operations_released counter
|
|
798
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
799
|
+
barrier.clear_metrics();
|
|
800
|
+
|
|
801
|
+
// Add multiple buffered operations
|
|
802
|
+
let mut required = VectorClock::new();
|
|
803
|
+
required.increment("node_a");
|
|
804
|
+
|
|
805
|
+
for i in 0..3 {
|
|
806
|
+
barrier.receive_operation(format!("op_{}", i), required.clone());
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
assert_eq!(barrier.metrics().operations_released, 0);
|
|
810
|
+
|
|
811
|
+
// Advance frontier to release operations
|
|
812
|
+
let mut satisfying = VectorClock::new();
|
|
813
|
+
satisfying.increment("node_a");
|
|
814
|
+
barrier.advance_frontier(&satisfying);
|
|
815
|
+
|
|
816
|
+
// All 3 should be released
|
|
817
|
+
assert_eq!(barrier.metrics().operations_released, 3);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
#[test]
|
|
821
|
+
fn test_instrumentation_buffer_scanning_scales_with_size() {
|
|
822
|
+
// Phase 7.0.1: Demonstrate that buffer scanning scales with buffer size
|
|
823
|
+
// This test uses full-scan oracle to validate full-scan instrumentation behavior
|
|
824
|
+
// Production dispatch uses indexed path; this tests the oracle implementation
|
|
825
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
826
|
+
barrier.clear_metrics();
|
|
827
|
+
|
|
828
|
+
// Create a requirement for {node_a: 5}
|
|
829
|
+
let mut required = VectorClock::new();
|
|
830
|
+
required.increment("node_a");
|
|
831
|
+
required.increment("node_a");
|
|
832
|
+
required.increment("node_a");
|
|
833
|
+
required.increment("node_a");
|
|
834
|
+
required.increment("node_a");
|
|
835
|
+
|
|
836
|
+
// Add 10 operations that can't be satisfied yet
|
|
837
|
+
for i in 0..10 {
|
|
838
|
+
barrier.receive_operation(format!("op_{}", i), required.clone());
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
barrier.clear_metrics();
|
|
842
|
+
|
|
843
|
+
// Trigger full-scan directly: should see all 10 items
|
|
844
|
+
let mut partial = VectorClock::new();
|
|
845
|
+
partial.increment("node_a");
|
|
846
|
+
partial.increment("node_a"); // Only {node_a: 2}, not enough for requirement
|
|
847
|
+
{
|
|
848
|
+
let current = barrier.get_frontier().clone();
|
|
849
|
+
barrier.get_prev_frontier_mut().clone_from(¤t);
|
|
850
|
+
}
|
|
851
|
+
barrier.get_frontier_mut().merge(&partial);
|
|
852
|
+
let _ = barrier.check_unblocked_operations_full_scan();
|
|
853
|
+
|
|
854
|
+
let first_scan_entries = barrier.metrics().pending_entries_examined;
|
|
855
|
+
assert_eq!(first_scan_entries, 10);
|
|
856
|
+
|
|
857
|
+
barrier.clear_metrics();
|
|
858
|
+
|
|
859
|
+
// Add 5 more operations
|
|
860
|
+
for i in 10..15 {
|
|
861
|
+
barrier.receive_operation(format!("op_{}", i), required.clone());
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Now there should be 15 items in pending
|
|
865
|
+
let pending_count = barrier.get_pending_operations().len();
|
|
866
|
+
assert_eq!(pending_count, 15);
|
|
867
|
+
|
|
868
|
+
// Trigger full-scan again: should see all 15 items
|
|
869
|
+
let mut more_partial = VectorClock::new();
|
|
870
|
+
more_partial.increment("node_a");
|
|
871
|
+
more_partial.increment("node_a");
|
|
872
|
+
more_partial.increment("node_a"); // Only {node_a: 3}, still not enough
|
|
873
|
+
{
|
|
874
|
+
let current = barrier.get_frontier().clone();
|
|
875
|
+
barrier.get_prev_frontier_mut().clone_from(¤t);
|
|
876
|
+
}
|
|
877
|
+
barrier.get_frontier_mut().merge(&more_partial);
|
|
878
|
+
let _ = barrier.check_unblocked_operations_full_scan();
|
|
879
|
+
|
|
880
|
+
let second_scan_entries = barrier.metrics().pending_entries_examined;
|
|
881
|
+
assert_eq!(second_scan_entries, 15);
|
|
882
|
+
|
|
883
|
+
// Verify that entries_examined scales with buffer size
|
|
884
|
+
assert!(second_scan_entries > first_scan_entries);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// ============================================================================
|
|
888
|
+
// LIFECYCLE INVARIANT TESTS (Phase 7.0.1: Prove theorem over actual states)
|
|
889
|
+
// ============================================================================
|
|
890
|
+
|
|
891
|
+
#[test]
|
|
892
|
+
fn lifecycle_invariant_out_of_order_operations() {
|
|
893
|
+
// Phase 7.0.1: Proof that frontier theorem holds with out-of-order arrivals
|
|
894
|
+
// Sequence: A1 → A3 → A2
|
|
895
|
+
// Expected: A1 applies, A3 buffers, then when A2 applies, A3 can be checked
|
|
896
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
897
|
+
|
|
898
|
+
// A1: no dependencies (ready immediately)
|
|
899
|
+
let a1_required = VectorClock::new();
|
|
900
|
+
let result_a1 = barrier.receive_operation("A1".to_string(), a1_required.clone());
|
|
901
|
+
assert!(matches!(result_a1, ReceiveResult::ReadyToApply { .. }));
|
|
902
|
+
|
|
903
|
+
// Apply A1 with {node_a: 1}
|
|
904
|
+
let mut a1_contribution = VectorClock::new();
|
|
905
|
+
a1_contribution.increment("node_a");
|
|
906
|
+
barrier.apply_operation("A1", &a1_contribution);
|
|
907
|
+
assert_eq!(barrier.get_frontier().get("node_a"), 1);
|
|
908
|
+
|
|
909
|
+
// A3: requires {node_a: 3}
|
|
910
|
+
let mut a3_required = VectorClock::new();
|
|
911
|
+
a3_required.increment("node_a");
|
|
912
|
+
a3_required.increment("node_a");
|
|
913
|
+
a3_required.increment("node_a");
|
|
914
|
+
let result_a3 = barrier.receive_operation("A3".to_string(), a3_required.clone());
|
|
915
|
+
assert!(matches!(result_a3, ReceiveResult::BufferedWaitingForDependencies { .. }));
|
|
916
|
+
|
|
917
|
+
// A2: requires {node_a: 2}
|
|
918
|
+
let mut a2_required = VectorClock::new();
|
|
919
|
+
a2_required.increment("node_a");
|
|
920
|
+
a2_required.increment("node_a");
|
|
921
|
+
let result_a2 = barrier.receive_operation("A2".to_string(), a2_required.clone());
|
|
922
|
+
assert!(matches!(result_a2, ReceiveResult::BufferedWaitingForDependencies { .. }));
|
|
923
|
+
|
|
924
|
+
// Now frontier is only {node_a: 1}, both A2 and A3 are buffered
|
|
925
|
+
assert_eq!(barrier.get_pending_operations().len(), 2);
|
|
926
|
+
|
|
927
|
+
// Apply A2 with {node_a: 2}
|
|
928
|
+
let mut a2_contribution = VectorClock::new();
|
|
929
|
+
a2_contribution.increment("node_a");
|
|
930
|
+
a2_contribution.increment("node_a");
|
|
931
|
+
barrier.apply_operation("A2", &a2_contribution);
|
|
932
|
+
|
|
933
|
+
// Now frontier is {node_a: 2}, A2 should be applied, A3 still waiting
|
|
934
|
+
assert_eq!(barrier.get_frontier().get("node_a"), 2);
|
|
935
|
+
let pending = barrier.get_pending_operations();
|
|
936
|
+
assert_eq!(pending.len(), 1);
|
|
937
|
+
assert_eq!(pending[0].operation_id, "A3");
|
|
938
|
+
assert!(matches!(pending[0].state, OperationState::WaitingForDependencies { .. }));
|
|
939
|
+
|
|
940
|
+
// Apply A3 with {node_a: 3}
|
|
941
|
+
let mut a3_contribution = VectorClock::new();
|
|
942
|
+
a3_contribution.increment("node_a");
|
|
943
|
+
a3_contribution.increment("node_a");
|
|
944
|
+
a3_contribution.increment("node_a");
|
|
945
|
+
barrier.apply_operation("A3", &a3_contribution);
|
|
946
|
+
|
|
947
|
+
// Now all should be applied
|
|
948
|
+
assert_eq!(barrier.get_frontier().get("node_a"), 3);
|
|
949
|
+
assert_eq!(barrier.get_pending_operations().len(), 0);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
#[test]
|
|
953
|
+
fn lifecycle_invariant_multiple_dependencies() {
|
|
954
|
+
// Phase 7.0.1: Proof with multiple independent dependency chains
|
|
955
|
+
// A5 depends on: B3, C7, D2
|
|
956
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
957
|
+
|
|
958
|
+
// Build the requirement: {B: 3, C: 7, D: 2}
|
|
959
|
+
let mut a5_required = VectorClock::new();
|
|
960
|
+
a5_required.increment("B");
|
|
961
|
+
a5_required.increment("B");
|
|
962
|
+
a5_required.increment("B"); // {B: 3}
|
|
963
|
+
for _ in 0..7 {
|
|
964
|
+
a5_required.increment("C");
|
|
965
|
+
} // {C: 7}
|
|
966
|
+
a5_required.increment("D");
|
|
967
|
+
a5_required.increment("D"); // {D: 2}
|
|
968
|
+
|
|
969
|
+
// Try to receive A5 before any dependencies
|
|
970
|
+
let result = barrier.receive_operation("A5".to_string(), a5_required.clone());
|
|
971
|
+
assert!(matches!(result, ReceiveResult::BufferedWaitingForDependencies { .. }));
|
|
972
|
+
|
|
973
|
+
// Partially satisfy: apply B3
|
|
974
|
+
let mut b3_clock = VectorClock::new();
|
|
975
|
+
b3_clock.increment("B");
|
|
976
|
+
b3_clock.increment("B");
|
|
977
|
+
b3_clock.increment("B");
|
|
978
|
+
barrier.receive_operation("B3".to_string(), b3_clock.clone());
|
|
979
|
+
barrier.apply_operation("B3", &b3_clock);
|
|
980
|
+
assert_eq!(barrier.get_frontier().get("B"), 3);
|
|
981
|
+
|
|
982
|
+
// A5 still waiting (needs C7 and D2)
|
|
983
|
+
assert!(matches!(
|
|
984
|
+
barrier.get_operation_state("A5"),
|
|
985
|
+
Some(OperationState::WaitingForDependencies { .. })
|
|
986
|
+
));
|
|
987
|
+
|
|
988
|
+
// Satisfy C7
|
|
989
|
+
let mut c7_clock = VectorClock::new();
|
|
990
|
+
for _ in 0..7 {
|
|
991
|
+
c7_clock.increment("C");
|
|
992
|
+
}
|
|
993
|
+
barrier.receive_operation("C7".to_string(), c7_clock.clone());
|
|
994
|
+
barrier.apply_operation("C7", &c7_clock);
|
|
995
|
+
assert_eq!(barrier.get_frontier().get("C"), 7);
|
|
996
|
+
|
|
997
|
+
// A5 still waiting (needs D2)
|
|
998
|
+
assert!(matches!(
|
|
999
|
+
barrier.get_operation_state("A5"),
|
|
1000
|
+
Some(OperationState::WaitingForDependencies { .. })
|
|
1001
|
+
));
|
|
1002
|
+
|
|
1003
|
+
// Satisfy D2
|
|
1004
|
+
let mut d2_clock = VectorClock::new();
|
|
1005
|
+
d2_clock.increment("D");
|
|
1006
|
+
d2_clock.increment("D");
|
|
1007
|
+
barrier.receive_operation("D2".to_string(), d2_clock.clone());
|
|
1008
|
+
barrier.apply_operation("D2", &d2_clock);
|
|
1009
|
+
assert_eq!(barrier.get_frontier().get("D"), 2);
|
|
1010
|
+
|
|
1011
|
+
// Now A5 should be ready (frontier has all dependencies)
|
|
1012
|
+
let pending = barrier.get_pending_operations();
|
|
1013
|
+
let a5_state = pending.iter().find(|p| p.operation_id == "A5");
|
|
1014
|
+
assert!(a5_state.is_some());
|
|
1015
|
+
assert!(matches!(a5_state.unwrap().state, OperationState::Ready));
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
#[test]
|
|
1019
|
+
fn lifecycle_invariant_buffered_but_unapplied() {
|
|
1020
|
+
// Phase 7.0.1: Critical case - operation received but not yet applied
|
|
1021
|
+
// This tests the distinction between observed_frontier and applied_frontier
|
|
1022
|
+
// Scenario: B1 is applied first (frontier = {B: 1}),
|
|
1023
|
+
// then we receive B3 (but don't apply it),
|
|
1024
|
+
// then A5 that requires {B: 3}
|
|
1025
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1026
|
+
|
|
1027
|
+
// First apply B1 to establish frontier
|
|
1028
|
+
let mut b1_clock = VectorClock::new();
|
|
1029
|
+
b1_clock.increment("B");
|
|
1030
|
+
barrier.receive_operation("B1".to_string(), b1_clock.clone());
|
|
1031
|
+
barrier.apply_operation("B1", &b1_clock);
|
|
1032
|
+
assert_eq!(barrier.get_frontier().get("B"), 1);
|
|
1033
|
+
|
|
1034
|
+
// Receive B3 (requires {B: 3} but frontier only has {B: 1}, so it gets buffered)
|
|
1035
|
+
let mut b3_required = VectorClock::new();
|
|
1036
|
+
b3_required.increment("B");
|
|
1037
|
+
b3_required.increment("B");
|
|
1038
|
+
b3_required.increment("B");
|
|
1039
|
+
let result_b3 = barrier.receive_operation("B3".to_string(), b3_required.clone());
|
|
1040
|
+
assert!(matches!(result_b3, ReceiveResult::BufferedWaitingForDependencies { .. }));
|
|
1041
|
+
|
|
1042
|
+
// At this point:
|
|
1043
|
+
// - applied_frontier[B] = 1 (only B1 applied)
|
|
1044
|
+
// - We have B3 in the buffer (received but not applied)
|
|
1045
|
+
// - A5 requires {B: 3}
|
|
1046
|
+
|
|
1047
|
+
// A5 should be WAITING because applied_frontier < required
|
|
1048
|
+
let mut a5_required = VectorClock::new();
|
|
1049
|
+
a5_required.increment("B");
|
|
1050
|
+
a5_required.increment("B");
|
|
1051
|
+
a5_required.increment("B");
|
|
1052
|
+
let result_a5 = barrier.receive_operation("A5".to_string(), a5_required.clone());
|
|
1053
|
+
assert!(matches!(result_a5, ReceiveResult::BufferedWaitingForDependencies { .. }));
|
|
1054
|
+
|
|
1055
|
+
// NOW apply B3
|
|
1056
|
+
barrier.apply_operation("B3", &b3_required);
|
|
1057
|
+
assert_eq!(barrier.get_frontier().get("B"), 3);
|
|
1058
|
+
|
|
1059
|
+
// THEN A5 can be ready
|
|
1060
|
+
let pending = barrier.get_pending_operations();
|
|
1061
|
+
let a5_state = pending.iter().find(|p| p.operation_id == "A5");
|
|
1062
|
+
assert!(a5_state.is_some());
|
|
1063
|
+
assert!(matches!(a5_state.unwrap().state, OperationState::Ready));
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
#[test]
|
|
1067
|
+
fn lifecycle_invariant_multiple_buffered_become_ready() {
|
|
1068
|
+
// Phase 7.0.1: Release mechanism test
|
|
1069
|
+
// Multiple operations in buffer all become ready when frontier advances
|
|
1070
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1071
|
+
|
|
1072
|
+
// Buffer 5 operations, all requiring {node_a: 3}
|
|
1073
|
+
let mut required = VectorClock::new();
|
|
1074
|
+
required.increment("node_a");
|
|
1075
|
+
required.increment("node_a");
|
|
1076
|
+
required.increment("node_a");
|
|
1077
|
+
|
|
1078
|
+
for i in 0..5 {
|
|
1079
|
+
let result = barrier.receive_operation(format!("op_{}", i), required.clone());
|
|
1080
|
+
assert!(matches!(result, ReceiveResult::BufferedWaitingForDependencies { .. }));
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
assert_eq!(barrier.get_pending_operations().len(), 5);
|
|
1084
|
+
|
|
1085
|
+
// Clear metrics and check that no operations are marked Ready yet
|
|
1086
|
+
barrier.clear_metrics();
|
|
1087
|
+
for i in 0..5 {
|
|
1088
|
+
let state = barrier.get_operation_state(&format!("op_{}", i));
|
|
1089
|
+
assert!(matches!(
|
|
1090
|
+
state,
|
|
1091
|
+
Some(OperationState::WaitingForDependencies { .. })
|
|
1092
|
+
));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Advance frontier to {node_a: 3}
|
|
1096
|
+
let mut satisfying = VectorClock::new();
|
|
1097
|
+
satisfying.increment("node_a");
|
|
1098
|
+
satisfying.increment("node_a");
|
|
1099
|
+
satisfying.increment("node_a");
|
|
1100
|
+
barrier.advance_frontier(&satisfying);
|
|
1101
|
+
|
|
1102
|
+
// Check that:
|
|
1103
|
+
// 1. All 5 operations were examined in the scan
|
|
1104
|
+
// 2. All 5 operations were released
|
|
1105
|
+
assert_eq!(barrier.metrics().pending_entries_examined, 5);
|
|
1106
|
+
assert_eq!(barrier.metrics().operations_released, 5);
|
|
1107
|
+
|
|
1108
|
+
// 3. All 5 operations now marked Ready
|
|
1109
|
+
for i in 0..5 {
|
|
1110
|
+
let state = barrier.get_operation_state(&format!("op_{}", i));
|
|
1111
|
+
assert!(matches!(state, Some(OperationState::Ready)));
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
#[test]
|
|
1116
|
+
fn phase_7_0_2_adversarial_satisfied_with_arbitrary_pending() {
|
|
1117
|
+
// Phase 7.0.2: Readiness Theorem Adversarial Test
|
|
1118
|
+
// Hypothesis: ready(op) ⟺ required_vc(op) <= applied_frontier
|
|
1119
|
+
// This should be true REGARDLESS of what's in the pending buffer
|
|
1120
|
+
//
|
|
1121
|
+
// Test: Apply frontier -> fill pending -> check clearly satisfied op
|
|
1122
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1123
|
+
|
|
1124
|
+
// 1. Set applied frontier to {A: 10, B: 10, C: 10}
|
|
1125
|
+
let mut frontier = VectorClock::new();
|
|
1126
|
+
frontier.increment("node_a");
|
|
1127
|
+
for _ in 0..9 {
|
|
1128
|
+
frontier.increment("node_a");
|
|
1129
|
+
}
|
|
1130
|
+
frontier.increment("node_b");
|
|
1131
|
+
for _ in 0..9 {
|
|
1132
|
+
frontier.increment("node_b");
|
|
1133
|
+
}
|
|
1134
|
+
frontier.increment("node_c");
|
|
1135
|
+
for _ in 0..9 {
|
|
1136
|
+
frontier.increment("node_c");
|
|
1137
|
+
}
|
|
1138
|
+
barrier.advance_frontier(&frontier);
|
|
1139
|
+
|
|
1140
|
+
// 2. Fill pending with high-timestamp operations
|
|
1141
|
+
// These are NOT applied yet, just buffered
|
|
1142
|
+
for i in 0..1000 {
|
|
1143
|
+
let mut high_ts = VectorClock::new();
|
|
1144
|
+
for j in 0..10 {
|
|
1145
|
+
high_ts.increment(&format!("node_a"));
|
|
1146
|
+
}
|
|
1147
|
+
high_ts.increment(&format!("pending_{}", i));
|
|
1148
|
+
let _ = barrier.receive_operation(format!("pending_{}", i), high_ts);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// 3. Check a CLEARLY satisfied operation
|
|
1152
|
+
// Required: {A: 5, B: 7, C: 3} which is clearly less than frontier {A:10, B:10, C:10}
|
|
1153
|
+
let mut required = VectorClock::new();
|
|
1154
|
+
for _ in 0..5 {
|
|
1155
|
+
required.increment("node_a");
|
|
1156
|
+
}
|
|
1157
|
+
for _ in 0..7 {
|
|
1158
|
+
required.increment("node_b");
|
|
1159
|
+
}
|
|
1160
|
+
for _ in 0..3 {
|
|
1161
|
+
required.increment("node_c");
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Expected: READY, because frontier >= required
|
|
1165
|
+
let result = barrier.receive_operation("clearly_satisfied".to_string(), required);
|
|
1166
|
+
|
|
1167
|
+
// Verify it's ready
|
|
1168
|
+
assert!(
|
|
1169
|
+
matches!(result, ReceiveResult::ReadyToApply { .. }),
|
|
1170
|
+
"Operation with satisfied frontier should be ready even with 1000 pending"
|
|
1171
|
+
);
|
|
1172
|
+
|
|
1173
|
+
// Verify no buffer scan happened
|
|
1174
|
+
let metrics = barrier.metrics();
|
|
1175
|
+
assert_eq!(
|
|
1176
|
+
metrics.pending_scans, 0,
|
|
1177
|
+
"readiness check should not scan pending buffer; frontier alone is sufficient"
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
#[test]
|
|
1182
|
+
fn phase_7_0_2_adversarial_pending_not_applied() {
|
|
1183
|
+
// Phase 7.0.2: Critical Distinction Test
|
|
1184
|
+
// Ensure we distinguish between:
|
|
1185
|
+
// - observed_frontier (max timestamp we've SEEN)
|
|
1186
|
+
// - applied_frontier (max timestamp we've APPLIED)
|
|
1187
|
+
//
|
|
1188
|
+
// Test: Buffer a high-timestamp operation, then check a requirement
|
|
1189
|
+
// that depends on that timestamp. Should NOT be ready until it's actually applied.
|
|
1190
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1191
|
+
|
|
1192
|
+
// 1. Start with applied frontier at {A: 5}
|
|
1193
|
+
let mut frontier = VectorClock::new();
|
|
1194
|
+
for _ in 0..5 {
|
|
1195
|
+
frontier.increment("node_a");
|
|
1196
|
+
}
|
|
1197
|
+
barrier.advance_frontier(&frontier);
|
|
1198
|
+
|
|
1199
|
+
// 2. Receive operation A:10 (not yet applied)
|
|
1200
|
+
// This increments observed_frontier but not applied_frontier
|
|
1201
|
+
let mut high_ts = VectorClock::new();
|
|
1202
|
+
for _ in 0..10 {
|
|
1203
|
+
high_ts.increment("node_a");
|
|
1204
|
+
}
|
|
1205
|
+
let result = barrier.receive_operation("unmet_op".to_string(), high_ts.clone());
|
|
1206
|
+
|
|
1207
|
+
// Should be buffered (A:10 not yet applied)
|
|
1208
|
+
assert!(
|
|
1209
|
+
matches!(result, ReceiveResult::BufferedWaitingForDependencies { .. }),
|
|
1210
|
+
"Operation A:10 should be buffered since frontier is only at A:5"
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
// 3. Now try to apply a NEW operation that requires A:10
|
|
1214
|
+
// This new operation's dependencies are NOT satisfied
|
|
1215
|
+
let result2 = barrier.receive_operation("test_op".to_string(), high_ts.clone());
|
|
1216
|
+
|
|
1217
|
+
assert!(
|
|
1218
|
+
matches!(result2, ReceiveResult::BufferedWaitingForDependencies { .. }),
|
|
1219
|
+
"Operation requiring A:10 should NOT be ready even though A:10 exists in pending buffer; \
|
|
1220
|
+
applied_frontier is still at A:5"
|
|
1221
|
+
);
|
|
1222
|
+
|
|
1223
|
+
// Verify instrumentation shows the semantic logic is working
|
|
1224
|
+
let metrics = barrier.metrics();
|
|
1225
|
+
println!(
|
|
1226
|
+
"Metrics: frontier_checks={}, pending_scans={}",
|
|
1227
|
+
metrics.frontier_checks, metrics.pending_scans
|
|
1228
|
+
);
|
|
1229
|
+
|
|
1230
|
+
// Should have done 2 frontier checks (for the two receive_operation calls)
|
|
1231
|
+
// Should NOT have done any scans (readiness is frontier-only)
|
|
1232
|
+
assert!(
|
|
1233
|
+
metrics.frontier_checks >= 2,
|
|
1234
|
+
"Should have checked frontier at least twice"
|
|
1235
|
+
);
|
|
1236
|
+
assert_eq!(
|
|
1237
|
+
metrics.pending_scans, 0,
|
|
1238
|
+
"Should not need buffer scans for readiness - frontier is sufficient"
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// ============================================================================
|
|
1243
|
+
// PHASE 7.1-OPT: DIFFERENTIAL TESTING (Index vs Reference)
|
|
1244
|
+
// ============================================================================
|
|
1245
|
+
|
|
1246
|
+
#[test]
|
|
1247
|
+
fn phase_7_1_opt_index_correctness_candidate_discovery() {
|
|
1248
|
+
// Test that index correctly identifies which operations are affected by frontier advancement
|
|
1249
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1250
|
+
|
|
1251
|
+
// Create operations with clear dependency patterns on same node
|
|
1252
|
+
let mut req_a1 = VectorClock::new();
|
|
1253
|
+
req_a1.clocks.insert("node_a".to_string(), 1);
|
|
1254
|
+
barrier.receive_operation("op_a1".to_string(), req_a1);
|
|
1255
|
+
|
|
1256
|
+
let mut req_a2 = VectorClock::new();
|
|
1257
|
+
req_a2.clocks.insert("node_a".to_string(), 2);
|
|
1258
|
+
barrier.receive_operation("op_a2".to_string(), req_a2);
|
|
1259
|
+
|
|
1260
|
+
let mut req_a3 = VectorClock::new();
|
|
1261
|
+
req_a3.clocks.insert("node_a".to_string(), 3);
|
|
1262
|
+
barrier.receive_operation("op_a3".to_string(), req_a3);
|
|
1263
|
+
|
|
1264
|
+
// Verify index is correct
|
|
1265
|
+
let a1_waiters = barrier.dependency_index.waiters.get(&("node_a".to_string(), 1))
|
|
1266
|
+
.map(|w| w.clone())
|
|
1267
|
+
.unwrap_or_default();
|
|
1268
|
+
assert!(a1_waiters.contains(&"op_a1".to_string()));
|
|
1269
|
+
|
|
1270
|
+
let a2_waiters = barrier.dependency_index.waiters.get(&("node_a".to_string(), 2))
|
|
1271
|
+
.map(|w| w.clone())
|
|
1272
|
+
.unwrap_or_default();
|
|
1273
|
+
assert!(a2_waiters.contains(&"op_a2".to_string()));
|
|
1274
|
+
|
|
1275
|
+
// Advance frontier to A:2
|
|
1276
|
+
let mut frontier = VectorClock::new();
|
|
1277
|
+
frontier.clocks.insert("node_a".to_string(), 2);
|
|
1278
|
+
barrier.advance_frontier(&frontier);
|
|
1279
|
+
|
|
1280
|
+
// Check which operations are Ready
|
|
1281
|
+
let pending = barrier.get_pending_operations();
|
|
1282
|
+
let ready: Vec<_> = pending.iter()
|
|
1283
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1284
|
+
.map(|p| p.operation_id.clone())
|
|
1285
|
+
.collect();
|
|
1286
|
+
|
|
1287
|
+
// op_a1 and op_a2 should be Ready
|
|
1288
|
+
assert_eq!(ready.len(), 2);
|
|
1289
|
+
assert!(ready.contains(&"op_a1".to_string()));
|
|
1290
|
+
assert!(ready.contains(&"op_a2".to_string()));
|
|
1291
|
+
|
|
1292
|
+
// op_a3 should still be Waiting (needs A:3)
|
|
1293
|
+
let waiting: Vec<_> = pending.iter()
|
|
1294
|
+
.filter(|p| matches!(p.state, OperationState::WaitingForDependencies { .. }))
|
|
1295
|
+
.map(|p| p.operation_id.clone())
|
|
1296
|
+
.collect();
|
|
1297
|
+
assert_eq!(waiting.len(), 1);
|
|
1298
|
+
assert!(waiting.contains(&"op_a3".to_string()));
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// ============================================================================
|
|
1302
|
+
// PHASE 7.1-OPT: INDEX INVARIANT TESTS
|
|
1303
|
+
// ============================================================================
|
|
1304
|
+
|
|
1305
|
+
#[test]
|
|
1306
|
+
fn phase_7_1_opt_index_invariant_all_operations_discoverable() {
|
|
1307
|
+
// Invariant: Every pending operation must be discoverable through index
|
|
1308
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1309
|
+
|
|
1310
|
+
// Add 20 operations with varied dependencies
|
|
1311
|
+
for i in 0..20 {
|
|
1312
|
+
let mut required = VectorClock::new();
|
|
1313
|
+
required.clocks.insert("node_a".to_string(), ((i % 5) + 1) as u64);
|
|
1314
|
+
if i % 3 == 0 {
|
|
1315
|
+
required.clocks.insert("node_b".to_string(), ((i / 3) + 1) as u64);
|
|
1316
|
+
}
|
|
1317
|
+
barrier.receive_operation(format!("op_{}", i), required);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Verify each pending operation is in index for all its dependencies
|
|
1321
|
+
for (op_id, pending_op) in &barrier.pending {
|
|
1322
|
+
for (node, ts) in &pending_op.required_dependencies.clocks {
|
|
1323
|
+
let key = (node.clone(), *ts);
|
|
1324
|
+
assert!(
|
|
1325
|
+
barrier.dependency_index.waiters.get(&key)
|
|
1326
|
+
.map(|waiters| waiters.contains(op_id))
|
|
1327
|
+
.unwrap_or(false),
|
|
1328
|
+
"Operation {} not found in index for ({}, {})",
|
|
1329
|
+
op_id, node, ts
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
#[test]
|
|
1336
|
+
fn phase_7_1_opt_index_invariant_removed_on_apply() {
|
|
1337
|
+
// Invariant: When operation is applied, it must be removed from index
|
|
1338
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1339
|
+
|
|
1340
|
+
// Add operation
|
|
1341
|
+
let mut required = VectorClock::new();
|
|
1342
|
+
required.clocks.insert("node_a".to_string(), 1);
|
|
1343
|
+
let result = barrier.receive_operation("op_1".to_string(), required.clone());
|
|
1344
|
+
|
|
1345
|
+
// Verify it's indexed
|
|
1346
|
+
assert!(
|
|
1347
|
+
barrier.dependency_index.waiters.get(&("node_a".to_string(), 1))
|
|
1348
|
+
.map(|w| w.contains(&"op_1".to_string()))
|
|
1349
|
+
.unwrap_or(false)
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1352
|
+
// Apply operation (moving it from pending to applied)
|
|
1353
|
+
barrier.apply_operation("op_1", &required);
|
|
1354
|
+
|
|
1355
|
+
// Verify it's removed from index (because apply calls unindex when removing from pending)
|
|
1356
|
+
for waiters in barrier.dependency_index.waiters.values() {
|
|
1357
|
+
assert!(
|
|
1358
|
+
!waiters.contains(&"op_1".to_string()),
|
|
1359
|
+
"Applied operation should be removed from all index entries"
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
#[test]
|
|
1365
|
+
fn phase_7_1_opt_index_invariant_duplicate_dependencies() {
|
|
1366
|
+
// Invariant: Handle operations with duplicate node dependencies
|
|
1367
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1368
|
+
|
|
1369
|
+
// Create operation with same node in required_vc (vector clock deduplicates)
|
|
1370
|
+
let mut required = VectorClock::new();
|
|
1371
|
+
required.clocks.insert("node_a".to_string(), 5);
|
|
1372
|
+
barrier.receive_operation("op_dup".to_string(), required.clone());
|
|
1373
|
+
|
|
1374
|
+
// Should be indexed exactly once for (node_a, 5)
|
|
1375
|
+
let waiters_at_5 = barrier.dependency_index.waiters.get(&("node_a".to_string(), 5))
|
|
1376
|
+
.map(|w| w.iter().filter(|id| id == &"op_dup").count())
|
|
1377
|
+
.unwrap_or(0);
|
|
1378
|
+
|
|
1379
|
+
// VectorClock naturally deduplicates, so should only appear once
|
|
1380
|
+
assert_eq!(waiters_at_5, 1, "Operation should appear once per unique dependency");
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
#[test]
|
|
1384
|
+
fn phase_7_1_opt_index_invariant_multiple_nodes() {
|
|
1385
|
+
// Invariant: Correctly handle multi-node dependencies
|
|
1386
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1387
|
+
|
|
1388
|
+
let mut required = VectorClock::new();
|
|
1389
|
+
required.clocks.insert("node_a".to_string(), 2);
|
|
1390
|
+
required.clocks.insert("node_b".to_string(), 3);
|
|
1391
|
+
required.clocks.insert("node_c".to_string(), 1);
|
|
1392
|
+
|
|
1393
|
+
barrier.receive_operation("op_multi".to_string(), required);
|
|
1394
|
+
|
|
1395
|
+
// Should be findable via any dependency
|
|
1396
|
+
assert!(barrier.dependency_index.waiters.get(&("node_a".to_string(), 2))
|
|
1397
|
+
.map(|w| w.contains(&"op_multi".to_string()))
|
|
1398
|
+
.unwrap_or(false));
|
|
1399
|
+
|
|
1400
|
+
assert!(barrier.dependency_index.waiters.get(&("node_b".to_string(), 3))
|
|
1401
|
+
.map(|w| w.contains(&"op_multi".to_string()))
|
|
1402
|
+
.unwrap_or(false));
|
|
1403
|
+
|
|
1404
|
+
assert!(barrier.dependency_index.waiters.get(&("node_c".to_string(), 1))
|
|
1405
|
+
.map(|w| w.contains(&"op_multi".to_string()))
|
|
1406
|
+
.unwrap_or(false));
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// ============================================================================
|
|
1410
|
+
// PHASE 7.1-OPT: CORE FUNCTIONALITY TESTS
|
|
1411
|
+
// ============================================================================
|
|
1412
|
+
|
|
1413
|
+
#[test]
|
|
1414
|
+
fn phase_7_1_opt_core_sequential_release() {
|
|
1415
|
+
// Core test: Sequential operations released (marked Ready) in correct order
|
|
1416
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1417
|
+
|
|
1418
|
+
// Add 5 operations with sequential dependencies
|
|
1419
|
+
for i in 1..=5 {
|
|
1420
|
+
let mut required = VectorClock::new();
|
|
1421
|
+
required.clocks.insert("node_a".to_string(), i as u64);
|
|
1422
|
+
barrier.receive_operation(format!("op_{}", i), required);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
assert_eq!(barrier.get_pending_operations().len(), 5);
|
|
1426
|
+
|
|
1427
|
+
// Advance to 3
|
|
1428
|
+
let mut frontier = VectorClock::new();
|
|
1429
|
+
frontier.clocks.insert("node_a".to_string(), 3);
|
|
1430
|
+
barrier.advance_frontier(&frontier);
|
|
1431
|
+
|
|
1432
|
+
// Check which operations are Ready vs still Waiting
|
|
1433
|
+
let pending = barrier.get_pending_operations();
|
|
1434
|
+
let ready_ops: Vec<_> = pending.iter()
|
|
1435
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1436
|
+
.map(|p| p.operation_id.clone())
|
|
1437
|
+
.collect();
|
|
1438
|
+
let waiting_ops: Vec<_> = pending.iter()
|
|
1439
|
+
.filter(|p| matches!(p.state, OperationState::WaitingForDependencies { .. }))
|
|
1440
|
+
.map(|p| p.operation_id.clone())
|
|
1441
|
+
.collect();
|
|
1442
|
+
|
|
1443
|
+
// op_1, op_2, op_3 should be Ready
|
|
1444
|
+
assert_eq!(ready_ops.len(), 3);
|
|
1445
|
+
assert!(ready_ops.contains(&"op_1".to_string()));
|
|
1446
|
+
assert!(ready_ops.contains(&"op_2".to_string()));
|
|
1447
|
+
assert!(ready_ops.contains(&"op_3".to_string()));
|
|
1448
|
+
|
|
1449
|
+
// op_4, op_5 should still be Waiting
|
|
1450
|
+
assert_eq!(waiting_ops.len(), 2);
|
|
1451
|
+
assert!(waiting_ops.contains(&"op_4".to_string()));
|
|
1452
|
+
assert!(waiting_ops.contains(&"op_5".to_string()));
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
#[test]
|
|
1456
|
+
fn phase_7_1_opt_core_index_maintained_on_release() {
|
|
1457
|
+
// Core test: Index is properly maintained when operations are released
|
|
1458
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1459
|
+
|
|
1460
|
+
// Add operations
|
|
1461
|
+
let mut req_1 = VectorClock::new();
|
|
1462
|
+
req_1.clocks.insert("node_a".to_string(), 1);
|
|
1463
|
+
barrier.receive_operation("op_1".to_string(), req_1);
|
|
1464
|
+
|
|
1465
|
+
let mut req_2 = VectorClock::new();
|
|
1466
|
+
req_2.clocks.insert("node_a".to_string(), 2);
|
|
1467
|
+
barrier.receive_operation("op_2".to_string(), req_2);
|
|
1468
|
+
|
|
1469
|
+
// Index should have entries
|
|
1470
|
+
assert!(barrier.dependency_index.waiters.get(&("node_a".to_string(), 1))
|
|
1471
|
+
.map(|w| w.contains(&"op_1".to_string()))
|
|
1472
|
+
.unwrap_or(false));
|
|
1473
|
+
|
|
1474
|
+
// Advance frontier
|
|
1475
|
+
let mut frontier = VectorClock::new();
|
|
1476
|
+
frontier.clocks.insert("node_a".to_string(), 1);
|
|
1477
|
+
barrier.advance_frontier(&frontier);
|
|
1478
|
+
|
|
1479
|
+
// After releasing op_1, it should still be in pending but marked Ready
|
|
1480
|
+
// and it should be removed from the index if using optimized version
|
|
1481
|
+
// (but for now, just verify it's marked as Ready)
|
|
1482
|
+
let pending = barrier.get_pending_operations();
|
|
1483
|
+
let op_1 = pending.iter().find(|p| p.operation_id == "op_1").unwrap();
|
|
1484
|
+
assert!(matches!(op_1.state, OperationState::Ready));
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
#[test]
|
|
1488
|
+
fn phase_7_1_opt_core_index_maintains_consistency() {
|
|
1489
|
+
// Core test: Index stays consistent with pending operations
|
|
1490
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1491
|
+
|
|
1492
|
+
// Add mix of operations
|
|
1493
|
+
for i in 0..5 {
|
|
1494
|
+
let mut required = VectorClock::new();
|
|
1495
|
+
required.clocks.insert("node_a".to_string(), ((i % 3) + 1) as u64);
|
|
1496
|
+
barrier.receive_operation(format!("op_{}", i), required);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Verify all pending ops are in index
|
|
1500
|
+
for (op_id, pending_op) in &barrier.pending {
|
|
1501
|
+
for (node, ts) in &pending_op.required_dependencies.clocks {
|
|
1502
|
+
assert!(
|
|
1503
|
+
barrier.dependency_index.waiters.get(&(node.clone(), *ts))
|
|
1504
|
+
.map(|w| w.contains(op_id))
|
|
1505
|
+
.unwrap_or(false),
|
|
1506
|
+
"Operation {} not indexed for ({}, {})",
|
|
1507
|
+
op_id, node, ts
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// Advance and check that released ops are removed from index
|
|
1513
|
+
let mut frontier = VectorClock::new();
|
|
1514
|
+
frontier.clocks.insert("node_a".to_string(), 2);
|
|
1515
|
+
barrier.advance_frontier(&frontier);
|
|
1516
|
+
|
|
1517
|
+
// Verify remaining pending ops are still in index
|
|
1518
|
+
for (op_id, pending_op) in &barrier.pending {
|
|
1519
|
+
for (node, ts) in &pending_op.required_dependencies.clocks {
|
|
1520
|
+
assert!(
|
|
1521
|
+
barrier.dependency_index.waiters.get(&(node.clone(), *ts))
|
|
1522
|
+
.map(|w| w.contains(op_id))
|
|
1523
|
+
.unwrap_or(false),
|
|
1524
|
+
"Remaining operation {} should still be in index",
|
|
1525
|
+
op_id
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// ============================================================================
|
|
1532
|
+
// PHASE 7.1.1: COMPREHENSIVE DIFFERENTIAL VALIDATION
|
|
1533
|
+
// ============================================================================
|
|
1534
|
+
// Permanent infrastructure for regression testing.
|
|
1535
|
+
// Ensures optimized and reference implementations remain semantically equivalent.
|
|
1536
|
+
|
|
1537
|
+
/// Scenario generator for differential testing
|
|
1538
|
+
#[derive(Clone, Debug)]
|
|
1539
|
+
struct DifferentialScenario {
|
|
1540
|
+
seed: u64,
|
|
1541
|
+
initial_frontier: VectorClock,
|
|
1542
|
+
pending_operations: Vec<(String, VectorClock)>,
|
|
1543
|
+
frontier_advances: Vec<VectorClock>,
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
impl DifferentialScenario {
|
|
1547
|
+
fn random_with_seed(seed: u64) -> Self {
|
|
1548
|
+
use std::collections::hash_map::DefaultHasher;
|
|
1549
|
+
use std::hash::{Hash, Hasher};
|
|
1550
|
+
|
|
1551
|
+
let mut hasher = DefaultHasher::new();
|
|
1552
|
+
seed.hash(&mut hasher);
|
|
1553
|
+
let mut rng_seed = hasher.finish();
|
|
1554
|
+
|
|
1555
|
+
fn next_rng(seed: &mut u64) -> u64 {
|
|
1556
|
+
*seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
|
1557
|
+
*seed
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// Generate random frontier
|
|
1561
|
+
let mut initial_frontier = VectorClock::new();
|
|
1562
|
+
let node_count = (next_rng(&mut rng_seed) % 5) as usize + 2; // 2-6 nodes
|
|
1563
|
+
for i in 0..node_count {
|
|
1564
|
+
let ts = (next_rng(&mut rng_seed) % 20) as u64;
|
|
1565
|
+
if ts > 0 {
|
|
1566
|
+
initial_frontier.clocks.insert(format!("node_{}", i), ts);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// Generate random pending operations
|
|
1571
|
+
let pending_count = (next_rng(&mut rng_seed) % 100) as usize + 10; // 10-109
|
|
1572
|
+
let mut pending_operations = Vec::new();
|
|
1573
|
+
for i in 0..pending_count {
|
|
1574
|
+
let mut required = VectorClock::new();
|
|
1575
|
+
let req_node_count = (next_rng(&mut rng_seed) % node_count as u64) as usize + 1;
|
|
1576
|
+
for j in 0..req_node_count {
|
|
1577
|
+
let node_idx = next_rng(&mut rng_seed) % node_count as u64;
|
|
1578
|
+
let ts = (next_rng(&mut rng_seed) % 100) as u64 + 1;
|
|
1579
|
+
required.clocks.insert(format!("node_{}", node_idx), ts);
|
|
1580
|
+
}
|
|
1581
|
+
pending_operations.push((format!("op_{}", i), required));
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// Generate random frontier advances
|
|
1585
|
+
let advance_count = (next_rng(&mut rng_seed) % 20) as usize + 5; // 5-24
|
|
1586
|
+
let mut frontier_advances = Vec::new();
|
|
1587
|
+
for _ in 0..advance_count {
|
|
1588
|
+
let mut advance = initial_frontier.clone();
|
|
1589
|
+
let node_to_advance = next_rng(&mut rng_seed) % node_count as u64;
|
|
1590
|
+
let node_key = format!("node_{}", node_to_advance);
|
|
1591
|
+
let new_ts = (next_rng(&mut rng_seed) % 100) as u64 + 1;
|
|
1592
|
+
advance.clocks.insert(node_key, new_ts);
|
|
1593
|
+
frontier_advances.push(advance);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
Self {
|
|
1597
|
+
seed,
|
|
1598
|
+
initial_frontier,
|
|
1599
|
+
pending_operations,
|
|
1600
|
+
frontier_advances,
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
#[test]
|
|
1606
|
+
fn phase_7_1_1_differential_random_scenarios_seed_based() {
|
|
1607
|
+
// Test 1: Random scenarios with deterministic seed
|
|
1608
|
+
// This ensures reproducibility and comprehensive coverage
|
|
1609
|
+
|
|
1610
|
+
for seed in 0..50 {
|
|
1611
|
+
let scenario = DifferentialScenario::random_with_seed(seed);
|
|
1612
|
+
|
|
1613
|
+
// Reference implementation
|
|
1614
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
1615
|
+
for (op_id, required) in &scenario.pending_operations {
|
|
1616
|
+
barrier_ref.receive_operation(op_id.clone(), required.clone());
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// Optimized implementation
|
|
1620
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
1621
|
+
for (op_id, required) in &scenario.pending_operations {
|
|
1622
|
+
barrier_opt.receive_operation(op_id.clone(), required.clone());
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// Run through advances
|
|
1626
|
+
for advance in &scenario.frontier_advances {
|
|
1627
|
+
barrier_ref.clear_metrics();
|
|
1628
|
+
barrier_opt.clear_metrics();
|
|
1629
|
+
|
|
1630
|
+
// Reference
|
|
1631
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
1632
|
+
barrier_ref.local_frontier.merge(advance);
|
|
1633
|
+
let _ref_ready = barrier_ref.check_unblocked_operations_full_scan();
|
|
1634
|
+
|
|
1635
|
+
// Optimized
|
|
1636
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
1637
|
+
barrier_opt.local_frontier.merge(advance);
|
|
1638
|
+
let _opt_ready = barrier_opt.check_unblocked_operations_indexed();
|
|
1639
|
+
|
|
1640
|
+
// Compare results
|
|
1641
|
+
let ref_pending_count = barrier_ref.get_pending_operations().len();
|
|
1642
|
+
let opt_pending_count = barrier_opt.get_pending_operations().len();
|
|
1643
|
+
|
|
1644
|
+
assert_eq!(
|
|
1645
|
+
ref_pending_count, opt_pending_count,
|
|
1646
|
+
"Seed {}: Pending count mismatch after advance",
|
|
1647
|
+
seed
|
|
1648
|
+
);
|
|
1649
|
+
|
|
1650
|
+
// Verify index consistency in optimized version
|
|
1651
|
+
for (op_id, pending_op) in &barrier_opt.pending {
|
|
1652
|
+
for (node, ts) in &pending_op.required_dependencies.clocks {
|
|
1653
|
+
let key = (node.clone(), *ts);
|
|
1654
|
+
if let Some(waiters) = barrier_opt.dependency_index.waiters.get(&key) {
|
|
1655
|
+
assert!(
|
|
1656
|
+
waiters.contains(op_id),
|
|
1657
|
+
"Seed {}: Operation {} not in index for {:?}",
|
|
1658
|
+
seed, op_id, key
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
#[test]
|
|
1668
|
+
fn phase_7_1_1_differential_adversarial_multiple_ops_same_dependency() {
|
|
1669
|
+
// Adversarial: Multiple operations depending on same (node, ts)
|
|
1670
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
1671
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
1672
|
+
|
|
1673
|
+
// Create 10 operations all requiring A:5
|
|
1674
|
+
for i in 0..10 {
|
|
1675
|
+
let mut required = VectorClock::new();
|
|
1676
|
+
required.clocks.insert("node_a".to_string(), 5);
|
|
1677
|
+
barrier_ref.receive_operation(format!("op_{}", i), required.clone());
|
|
1678
|
+
barrier_opt.receive_operation(format!("op_{}", i), required);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// Advance to A:5
|
|
1682
|
+
let mut frontier = VectorClock::new();
|
|
1683
|
+
frontier.clocks.insert("node_a".to_string(), 5);
|
|
1684
|
+
|
|
1685
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
1686
|
+
barrier_ref.local_frontier.merge(&frontier);
|
|
1687
|
+
barrier_ref.check_unblocked_operations_full_scan();
|
|
1688
|
+
|
|
1689
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
1690
|
+
barrier_opt.local_frontier.merge(&frontier);
|
|
1691
|
+
barrier_opt.check_unblocked_operations_indexed();
|
|
1692
|
+
|
|
1693
|
+
// Both should have released all 10
|
|
1694
|
+
let ref_ready = barrier_ref.get_pending_operations()
|
|
1695
|
+
.iter()
|
|
1696
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1697
|
+
.count();
|
|
1698
|
+
let opt_ready = barrier_opt.get_pending_operations()
|
|
1699
|
+
.iter()
|
|
1700
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1701
|
+
.count();
|
|
1702
|
+
|
|
1703
|
+
assert_eq!(ref_ready, opt_ready);
|
|
1704
|
+
assert_eq!(ref_ready, 10);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
#[test]
|
|
1708
|
+
fn phase_7_1_1_differential_adversarial_frontier_jump() {
|
|
1709
|
+
// Adversarial: Large frontier jump
|
|
1710
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
1711
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
1712
|
+
|
|
1713
|
+
for i in 1..=10 {
|
|
1714
|
+
let mut req = VectorClock::new();
|
|
1715
|
+
req.clocks.insert("node_a".to_string(), i as u64);
|
|
1716
|
+
barrier_ref.receive_operation(format!("op_{}", i), req.clone());
|
|
1717
|
+
barrier_opt.receive_operation(format!("op_{}", i), req);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// Jump to A:7
|
|
1721
|
+
let mut frontier = VectorClock::new();
|
|
1722
|
+
frontier.clocks.insert("node_a".to_string(), 7);
|
|
1723
|
+
|
|
1724
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
1725
|
+
barrier_ref.local_frontier.merge(&frontier);
|
|
1726
|
+
barrier_ref.check_unblocked_operations_full_scan();
|
|
1727
|
+
|
|
1728
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
1729
|
+
barrier_opt.local_frontier.merge(&frontier);
|
|
1730
|
+
barrier_opt.check_unblocked_operations_indexed();
|
|
1731
|
+
|
|
1732
|
+
// Both should have released op_1 through op_7
|
|
1733
|
+
let ref_ready = barrier_ref.get_pending_operations()
|
|
1734
|
+
.iter()
|
|
1735
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1736
|
+
.count();
|
|
1737
|
+
let opt_ready = barrier_opt.get_pending_operations()
|
|
1738
|
+
.iter()
|
|
1739
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1740
|
+
.count();
|
|
1741
|
+
|
|
1742
|
+
assert_eq!(ref_ready, opt_ready);
|
|
1743
|
+
assert_eq!(ref_ready, 7);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
#[test]
|
|
1747
|
+
fn phase_7_1_1_differential_adversarial_simultaneous_release() {
|
|
1748
|
+
// Adversarial: All operations ready at same frontier advance
|
|
1749
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
1750
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
1751
|
+
|
|
1752
|
+
for i in 0..20 {
|
|
1753
|
+
let mut req = VectorClock::new();
|
|
1754
|
+
req.clocks.insert("node_a".to_string(), 5);
|
|
1755
|
+
barrier_ref.receive_operation(format!("op_{}", i), req.clone());
|
|
1756
|
+
barrier_opt.receive_operation(format!("op_{}", i), req);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
let mut frontier = VectorClock::new();
|
|
1760
|
+
frontier.clocks.insert("node_a".to_string(), 5);
|
|
1761
|
+
|
|
1762
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
1763
|
+
barrier_ref.local_frontier.merge(&frontier);
|
|
1764
|
+
barrier_ref.check_unblocked_operations_full_scan();
|
|
1765
|
+
|
|
1766
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
1767
|
+
barrier_opt.local_frontier.merge(&frontier);
|
|
1768
|
+
barrier_opt.check_unblocked_operations_indexed();
|
|
1769
|
+
|
|
1770
|
+
let ref_ready = barrier_ref.get_pending_operations()
|
|
1771
|
+
.iter()
|
|
1772
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1773
|
+
.count();
|
|
1774
|
+
let opt_ready = barrier_opt.get_pending_operations()
|
|
1775
|
+
.iter()
|
|
1776
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1777
|
+
.count();
|
|
1778
|
+
|
|
1779
|
+
assert_eq!(ref_ready, opt_ready);
|
|
1780
|
+
assert_eq!(ref_ready, 20);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
#[test]
|
|
1784
|
+
fn phase_7_1_1_differential_adversarial_empty_buffer() {
|
|
1785
|
+
// Adversarial: Empty pending buffer
|
|
1786
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
1787
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
1788
|
+
|
|
1789
|
+
let mut frontier = VectorClock::new();
|
|
1790
|
+
frontier.clocks.insert("node_a".to_string(), 10);
|
|
1791
|
+
|
|
1792
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
1793
|
+
barrier_ref.local_frontier.merge(&frontier);
|
|
1794
|
+
let ref_ready = barrier_ref.check_unblocked_operations_full_scan();
|
|
1795
|
+
|
|
1796
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
1797
|
+
barrier_opt.local_frontier.merge(&frontier);
|
|
1798
|
+
let opt_ready = barrier_opt.check_unblocked_operations_indexed();
|
|
1799
|
+
|
|
1800
|
+
assert_eq!(ref_ready.len(), 0);
|
|
1801
|
+
assert_eq!(opt_ready.len(), 0);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
#[test]
|
|
1805
|
+
fn phase_7_1_1_index_invariant_no_orphaned_entries() {
|
|
1806
|
+
// Verify index never references non-existent operations
|
|
1807
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
1808
|
+
|
|
1809
|
+
for i in 0..50 {
|
|
1810
|
+
let mut req = VectorClock::new();
|
|
1811
|
+
req.clocks.insert("node_a".to_string(), ((i % 10) + 1) as u64);
|
|
1812
|
+
barrier.receive_operation(format!("op_{}", i), req);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// Release some operations
|
|
1816
|
+
let mut frontier = VectorClock::new();
|
|
1817
|
+
frontier.clocks.insert("node_a".to_string(), 5);
|
|
1818
|
+
barrier.prev_frontier.clone_from(&barrier.local_frontier);
|
|
1819
|
+
barrier.local_frontier.merge(&frontier);
|
|
1820
|
+
barrier.check_unblocked_operations();
|
|
1821
|
+
|
|
1822
|
+
// Verify all index entries reference operations in pending
|
|
1823
|
+
for waiters in barrier.dependency_index.waiters.values() {
|
|
1824
|
+
for op_id in waiters {
|
|
1825
|
+
assert!(
|
|
1826
|
+
barrier.pending.contains_key(op_id),
|
|
1827
|
+
"Index entry references non-existent operation {}",
|
|
1828
|
+
op_id
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
#[test]
|
|
1835
|
+
fn phase_7_1_1_recovery_index_reconstruction() {
|
|
1836
|
+
// Verify index can be reconstructed from pending operations
|
|
1837
|
+
let mut barrier1 = CausalDependencyBarrier::new();
|
|
1838
|
+
|
|
1839
|
+
// Build up some state
|
|
1840
|
+
for i in 0..20 {
|
|
1841
|
+
let mut req = VectorClock::new();
|
|
1842
|
+
req.clocks.insert("node_a".to_string(), ((i % 5) + 1) as u64);
|
|
1843
|
+
barrier1.receive_operation(format!("op_{}", i), req);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
let mut frontier = VectorClock::new();
|
|
1847
|
+
frontier.clocks.insert("node_a".to_string(), 2);
|
|
1848
|
+
barrier1.advance_frontier(&frontier);
|
|
1849
|
+
|
|
1850
|
+
// Simulate recovery: extract pending, rebuild index
|
|
1851
|
+
let pending_snapshot: Vec<_> = barrier1.pending.values().cloned().collect();
|
|
1852
|
+
|
|
1853
|
+
let mut barrier2 = CausalDependencyBarrier::new();
|
|
1854
|
+
for pending_op in pending_snapshot {
|
|
1855
|
+
barrier2.pending.insert(
|
|
1856
|
+
pending_op.operation_id.clone(),
|
|
1857
|
+
pending_op.clone(),
|
|
1858
|
+
);
|
|
1859
|
+
barrier2.index_operation(
|
|
1860
|
+
&pending_op.operation_id,
|
|
1861
|
+
&pending_op.required_dependencies,
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
// Verify index is equivalent
|
|
1866
|
+
assert_eq!(
|
|
1867
|
+
barrier1.dependency_index.waiters.len(),
|
|
1868
|
+
barrier2.dependency_index.waiters.len()
|
|
1869
|
+
);
|
|
1870
|
+
|
|
1871
|
+
for (key, waiters1) in &barrier1.dependency_index.waiters {
|
|
1872
|
+
let waiters2 = barrier2.dependency_index.waiters.get(key);
|
|
1873
|
+
assert!(waiters2.is_some(), "Recovered index missing key {:?}", key);
|
|
1874
|
+
let mut w1 = waiters1.clone();
|
|
1875
|
+
let mut w2 = waiters2.unwrap().clone();
|
|
1876
|
+
w1.sort();
|
|
1877
|
+
w2.sort();
|
|
1878
|
+
assert_eq!(w1, w2, "Index mismatch for key {:?}", key);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
#[test]
|
|
1883
|
+
fn phase_7_1_1_state_hash_equivalence_simple() {
|
|
1884
|
+
// Simpler state equivalence test: verify both paths mark same ops as Ready
|
|
1885
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
1886
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
1887
|
+
|
|
1888
|
+
// Add 10 operations with varied dependencies
|
|
1889
|
+
for i in 0..10 {
|
|
1890
|
+
let mut req = VectorClock::new();
|
|
1891
|
+
req.clocks.insert("node_a".to_string(), (i + 1) as u64);
|
|
1892
|
+
barrier_ref.receive_operation(format!("op_{}", i), req.clone());
|
|
1893
|
+
barrier_opt.receive_operation(format!("op_{}", i), req);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// Advance frontier to release first 5 operations
|
|
1897
|
+
let mut frontier = VectorClock::new();
|
|
1898
|
+
frontier.clocks.insert("node_a".to_string(), 5);
|
|
1899
|
+
|
|
1900
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
1901
|
+
barrier_ref.local_frontier.merge(&frontier);
|
|
1902
|
+
let _ref_ready = barrier_ref.check_unblocked_operations_full_scan();
|
|
1903
|
+
|
|
1904
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
1905
|
+
barrier_opt.local_frontier.merge(&frontier);
|
|
1906
|
+
let _opt_ready = barrier_opt.check_unblocked_operations_indexed();
|
|
1907
|
+
|
|
1908
|
+
// Compare operation states
|
|
1909
|
+
let ref_pending = barrier_ref.get_pending_operations();
|
|
1910
|
+
let opt_pending = barrier_opt.get_pending_operations();
|
|
1911
|
+
|
|
1912
|
+
assert_eq!(ref_pending.len(), opt_pending.len());
|
|
1913
|
+
|
|
1914
|
+
for ref_op in ref_pending.iter() {
|
|
1915
|
+
let opt_op = opt_pending.iter().find(|op| op.operation_id == ref_op.operation_id)
|
|
1916
|
+
.expect(&format!("Operation {} missing in optimized", ref_op.operation_id));
|
|
1917
|
+
|
|
1918
|
+
assert_eq!(
|
|
1919
|
+
std::mem::discriminant(&ref_op.state),
|
|
1920
|
+
std::mem::discriminant(&opt_op.state),
|
|
1921
|
+
"State mismatch for operation {}",
|
|
1922
|
+
ref_op.operation_id
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
#[test]
|
|
1928
|
+
fn phase_7_1_1_property_multidimensional_frontier_satisfaction() {
|
|
1929
|
+
// Property test: Multi-node frontier satisfaction
|
|
1930
|
+
// Operations requiring multiple nodes should only release when ALL dimensions satisfied
|
|
1931
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
1932
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
1933
|
+
|
|
1934
|
+
// Create operations with multi-node dependencies
|
|
1935
|
+
// op_all_three: requires A:5, B:3, C:2
|
|
1936
|
+
let mut req_all = VectorClock::new();
|
|
1937
|
+
req_all.clocks.insert("node_a".to_string(), 5);
|
|
1938
|
+
req_all.clocks.insert("node_b".to_string(), 3);
|
|
1939
|
+
req_all.clocks.insert("node_c".to_string(), 2);
|
|
1940
|
+
|
|
1941
|
+
barrier_ref.receive_operation("op_all_three".to_string(), req_all.clone());
|
|
1942
|
+
barrier_opt.receive_operation("op_all_three".to_string(), req_all);
|
|
1943
|
+
|
|
1944
|
+
// Partial advance: only A:5 satisfied
|
|
1945
|
+
let mut partial = VectorClock::new();
|
|
1946
|
+
partial.clocks.insert("node_a".to_string(), 5);
|
|
1947
|
+
partial.clocks.insert("node_b".to_string(), 1);
|
|
1948
|
+
partial.clocks.insert("node_c".to_string(), 1);
|
|
1949
|
+
|
|
1950
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
1951
|
+
barrier_ref.local_frontier.merge(&partial);
|
|
1952
|
+
barrier_ref.check_unblocked_operations_full_scan();
|
|
1953
|
+
|
|
1954
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
1955
|
+
barrier_opt.local_frontier.merge(&partial);
|
|
1956
|
+
barrier_opt.check_unblocked_operations_indexed();
|
|
1957
|
+
|
|
1958
|
+
// op_all_three should still be waiting
|
|
1959
|
+
let ref_waiting = barrier_ref.get_pending_operations()
|
|
1960
|
+
.iter()
|
|
1961
|
+
.filter(|p| matches!(p.state, OperationState::WaitingForDependencies { .. }))
|
|
1962
|
+
.count();
|
|
1963
|
+
let opt_waiting = barrier_opt.get_pending_operations()
|
|
1964
|
+
.iter()
|
|
1965
|
+
.filter(|p| matches!(p.state, OperationState::WaitingForDependencies { .. }))
|
|
1966
|
+
.count();
|
|
1967
|
+
|
|
1968
|
+
assert_eq!(ref_waiting, 1);
|
|
1969
|
+
assert_eq!(opt_waiting, 1);
|
|
1970
|
+
|
|
1971
|
+
// Full advance: all dimensions satisfied
|
|
1972
|
+
let mut full = VectorClock::new();
|
|
1973
|
+
full.clocks.insert("node_a".to_string(), 5);
|
|
1974
|
+
full.clocks.insert("node_b".to_string(), 3);
|
|
1975
|
+
full.clocks.insert("node_c".to_string(), 2);
|
|
1976
|
+
|
|
1977
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
1978
|
+
barrier_ref.local_frontier.merge(&full);
|
|
1979
|
+
barrier_ref.check_unblocked_operations_full_scan();
|
|
1980
|
+
|
|
1981
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
1982
|
+
barrier_opt.local_frontier.merge(&full);
|
|
1983
|
+
barrier_opt.check_unblocked_operations_indexed();
|
|
1984
|
+
|
|
1985
|
+
// Now op_all_three should be ready
|
|
1986
|
+
let ref_ready = barrier_ref.get_pending_operations()
|
|
1987
|
+
.iter()
|
|
1988
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1989
|
+
.count();
|
|
1990
|
+
let opt_ready = barrier_opt.get_pending_operations()
|
|
1991
|
+
.iter()
|
|
1992
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
1993
|
+
.count();
|
|
1994
|
+
|
|
1995
|
+
assert_eq!(ref_ready, 1);
|
|
1996
|
+
assert_eq!(opt_ready, 1);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
#[test]
|
|
2000
|
+
fn phase_7_1_1_adversarial_coordinate_by_coordinate_satisfaction() {
|
|
2001
|
+
// Adversarial: Dependencies satisfied one coordinate at a time
|
|
2002
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
2003
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
2004
|
+
|
|
2005
|
+
for i in 0..5 {
|
|
2006
|
+
let mut req = VectorClock::new();
|
|
2007
|
+
req.clocks.insert("node_a".to_string(), 10 + i as u64);
|
|
2008
|
+
req.clocks.insert("node_b".to_string(), 20 + i as u64);
|
|
2009
|
+
req.clocks.insert("node_c".to_string(), 30 + i as u64);
|
|
2010
|
+
barrier_ref.receive_operation(format!("op_{}", i), req.clone());
|
|
2011
|
+
barrier_opt.receive_operation(format!("op_{}", i), req);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// Satisfy one coordinate at a time for each operation
|
|
2015
|
+
for coord in vec![("node_a", 14), ("node_b", 24), ("node_c", 34)] {
|
|
2016
|
+
let mut frontier = VectorClock::new();
|
|
2017
|
+
for (n, ts) in vec![("node_a", 14u64), ("node_b", 24u64), ("node_c", 34u64)] {
|
|
2018
|
+
frontier.clocks.insert(n.to_string(), ts);
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
2022
|
+
barrier_ref.local_frontier.merge(&frontier);
|
|
2023
|
+
barrier_ref.check_unblocked_operations_full_scan();
|
|
2024
|
+
|
|
2025
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
2026
|
+
barrier_opt.local_frontier.merge(&frontier);
|
|
2027
|
+
barrier_opt.check_unblocked_operations_indexed();
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
// Both should have released all 5 operations
|
|
2031
|
+
let ref_ready = barrier_ref.get_pending_operations()
|
|
2032
|
+
.iter()
|
|
2033
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
2034
|
+
.count();
|
|
2035
|
+
let opt_ready = barrier_opt.get_pending_operations()
|
|
2036
|
+
.iter()
|
|
2037
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
2038
|
+
.count();
|
|
2039
|
+
|
|
2040
|
+
assert_eq!(ref_ready, 5);
|
|
2041
|
+
assert_eq!(opt_ready, 5);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
#[test]
|
|
2045
|
+
fn phase_7_1_1_adversarial_large_pending_buffer() {
|
|
2046
|
+
// Adversarial: Large pending buffer with low release selectivity
|
|
2047
|
+
// 50 operations, only some will be released
|
|
2048
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
2049
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
2050
|
+
|
|
2051
|
+
// Add 50 operations with escalating dependencies on single node
|
|
2052
|
+
for i in 0..50 {
|
|
2053
|
+
let mut req = VectorClock::new();
|
|
2054
|
+
req.clocks.insert("node_a".to_string(), (i + 1) as u64);
|
|
2055
|
+
barrier_ref.receive_operation(format!("op_{}", i), req.clone());
|
|
2056
|
+
barrier_opt.receive_operation(format!("op_{}", i), req);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
// Advance frontier to release only first 25 (those requiring node_a <= 25)
|
|
2060
|
+
let mut frontier = VectorClock::new();
|
|
2061
|
+
frontier.clocks.insert("node_a".to_string(), 25);
|
|
2062
|
+
|
|
2063
|
+
barrier_ref.prev_frontier.clone_from(&barrier_ref.local_frontier);
|
|
2064
|
+
barrier_ref.local_frontier.merge(&frontier);
|
|
2065
|
+
barrier_ref.check_unblocked_operations_full_scan();
|
|
2066
|
+
|
|
2067
|
+
barrier_opt.prev_frontier.clone_from(&barrier_opt.local_frontier);
|
|
2068
|
+
barrier_opt.local_frontier.merge(&frontier);
|
|
2069
|
+
barrier_opt.check_unblocked_operations_indexed();
|
|
2070
|
+
|
|
2071
|
+
// Both should release exactly 25 operations
|
|
2072
|
+
let ref_ready = barrier_ref.get_pending_operations()
|
|
2073
|
+
.iter()
|
|
2074
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
2075
|
+
.count();
|
|
2076
|
+
let opt_ready = barrier_opt.get_pending_operations()
|
|
2077
|
+
.iter()
|
|
2078
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
2079
|
+
.count();
|
|
2080
|
+
|
|
2081
|
+
assert_eq!(ref_ready, 25);
|
|
2082
|
+
assert_eq!(opt_ready, 25);
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
#[test]
|
|
2086
|
+
fn phase_7_1_1_adversarial_independent_node_dependencies() {
|
|
2087
|
+
// Adversarial: Operations depending on different independent nodes
|
|
2088
|
+
let mut barrier_ref = CausalDependencyBarrier::new();
|
|
2089
|
+
let mut barrier_opt = CausalDependencyBarrier::new();
|
|
2090
|
+
|
|
2091
|
+
// Create enough operations with dependencies on different nodes
|
|
2092
|
+
for i in 0..5 {
|
|
2093
|
+
let mut req = VectorClock::new();
|
|
2094
|
+
req.clocks.insert(format!("node_{}", i), 10);
|
|
2095
|
+
barrier_ref.receive_operation(format!("op_{}", i), req.clone());
|
|
2096
|
+
barrier_opt.receive_operation(format!("op_{}", i), req);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// Advance all nodes to their required values
|
|
2100
|
+
let mut frontier = VectorClock::new();
|
|
2101
|
+
for i in 0..5 {
|
|
2102
|
+
frontier.clocks.insert(format!("node_{}", i), 10);
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
barrier_ref.advance_frontier(&frontier);
|
|
2106
|
+
barrier_opt.advance_frontier(&frontier);
|
|
2107
|
+
|
|
2108
|
+
// All operations should be ready
|
|
2109
|
+
let ref_ready = barrier_ref.get_pending_operations()
|
|
2110
|
+
.iter()
|
|
2111
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
2112
|
+
.count();
|
|
2113
|
+
let opt_ready = barrier_opt.get_pending_operations()
|
|
2114
|
+
.iter()
|
|
2115
|
+
.filter(|p| matches!(p.state, OperationState::Ready))
|
|
2116
|
+
.count();
|
|
2117
|
+
|
|
2118
|
+
assert_eq!(ref_ready, 5);
|
|
2119
|
+
assert_eq!(opt_ready, 5);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
#[test]
|
|
2123
|
+
fn phase_7_1_1_property_index_completeness() {
|
|
2124
|
+
// Property test: Index must contain all pending operations
|
|
2125
|
+
for seed in 0..10 {
|
|
2126
|
+
let scenario = DifferentialScenario::random_with_seed(seed);
|
|
2127
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
2128
|
+
|
|
2129
|
+
for (op_id, required) in &scenario.pending_operations {
|
|
2130
|
+
barrier.receive_operation(op_id.clone(), required.clone());
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
// Every pending operation must be discoverable through index
|
|
2134
|
+
for (op_id, pending_op) in &barrier.pending {
|
|
2135
|
+
let mut found = false;
|
|
2136
|
+
for (node, ts) in &pending_op.required_dependencies.clocks {
|
|
2137
|
+
if let Some(waiters) = barrier.dependency_index.waiters.get(&(node.clone(), *ts)) {
|
|
2138
|
+
if waiters.contains(op_id) {
|
|
2139
|
+
found = true;
|
|
2140
|
+
break;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
assert!(found, "Operation {} not found in any index entry", op_id);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
#[test]
|
|
2150
|
+
fn phase_7_1_1_property_frontier_only_advances() {
|
|
2151
|
+
// Property test: Frontier never regresses in any dimension
|
|
2152
|
+
for seed in 0..10 {
|
|
2153
|
+
let scenario = DifferentialScenario::random_with_seed(seed);
|
|
2154
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
2155
|
+
|
|
2156
|
+
for (op_id, required) in &scenario.pending_operations {
|
|
2157
|
+
barrier.receive_operation(op_id.clone(), required.clone());
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
let mut prev_frontier = barrier.local_frontier.clone();
|
|
2161
|
+
|
|
2162
|
+
for advance in &scenario.frontier_advances {
|
|
2163
|
+
barrier.prev_frontier.clone_from(&barrier.local_frontier);
|
|
2164
|
+
barrier.local_frontier.merge(advance);
|
|
2165
|
+
barrier.check_unblocked_operations();
|
|
2166
|
+
|
|
2167
|
+
// Check frontier only advanced (never regressed)
|
|
2168
|
+
for (node, prev_ts) in &prev_frontier.clocks {
|
|
2169
|
+
let new_ts = barrier.local_frontier.get(node);
|
|
2170
|
+
assert!(
|
|
2171
|
+
new_ts >= *prev_ts,
|
|
2172
|
+
"Frontier regressed for {} from {} to {}",
|
|
2173
|
+
node, prev_ts, new_ts
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
prev_frontier = barrier.local_frontier.clone();
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
#[test]
|
|
2183
|
+
fn phase_7_1_1_property_ready_operations_never_waiting() {
|
|
2184
|
+
// Property test: Once an operation is marked Ready, it never regresses to Waiting
|
|
2185
|
+
for seed in 0..10 {
|
|
2186
|
+
let scenario = DifferentialScenario::random_with_seed(seed);
|
|
2187
|
+
let mut barrier = CausalDependencyBarrier::new();
|
|
2188
|
+
|
|
2189
|
+
for (op_id, required) in &scenario.pending_operations {
|
|
2190
|
+
barrier.receive_operation(op_id.clone(), required.clone());
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
let mut seen_ready = std::collections::HashSet::new();
|
|
2194
|
+
|
|
2195
|
+
for advance in &scenario.frontier_advances {
|
|
2196
|
+
barrier.prev_frontier.clone_from(&barrier.local_frontier);
|
|
2197
|
+
barrier.local_frontier.merge(advance);
|
|
2198
|
+
barrier.check_unblocked_operations();
|
|
2199
|
+
|
|
2200
|
+
for pending_op in barrier.get_pending_operations() {
|
|
2201
|
+
if matches!(pending_op.state, OperationState::Ready) {
|
|
2202
|
+
seen_ready.insert(pending_op.operation_id.clone());
|
|
2203
|
+
} else if matches!(pending_op.state, OperationState::WaitingForDependencies { .. }) {
|
|
2204
|
+
assert!(
|
|
2205
|
+
!seen_ready.contains(&pending_op.operation_id),
|
|
2206
|
+
"Operation {} regressed from Ready to Waiting",
|
|
2207
|
+
pending_op.operation_id
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|