@feltdb/core 0.4.11 → 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-q43NSTTH.js → components-bHTARBin.js} +160 -89
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-DZpBoVMp.js → index-BqKquLuU.js} +8 -8
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/// Replay Fuzzing Framework
|
|
2
|
+
///
|
|
3
|
+
/// Tests that the same operation log produces identical state when replayed via:
|
|
4
|
+
/// - Normal replay (in original order)
|
|
5
|
+
/// - Batched replay (grouped operations)
|
|
6
|
+
/// - After snapshot (recover from snapshot + remaining ops)
|
|
7
|
+
/// - After simulated crash (recover + replay remaining)
|
|
8
|
+
/// - After index deletion (rebuild index from scratch)
|
|
9
|
+
///
|
|
10
|
+
/// All paths must converge to identical state_hash.
|
|
11
|
+
|
|
12
|
+
use crate::operation_log::{MemoryOperationLog, OperationLogEntry};
|
|
13
|
+
use crate::state_hash::{CanonicalState, StateHash};
|
|
14
|
+
use crate::state_transition_store::MemoryStateTransitionStore;
|
|
15
|
+
use crate::transactions::*;
|
|
16
|
+
use std::collections::HashMap;
|
|
17
|
+
|
|
18
|
+
/// Replay scenario
|
|
19
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
20
|
+
pub enum ReplayScenario {
|
|
21
|
+
/// Replay all operations in original order
|
|
22
|
+
Normal,
|
|
23
|
+
/// Replay in batches of N operations
|
|
24
|
+
Batched { batch_size: usize },
|
|
25
|
+
/// Simulate recovery from snapshot at index N, replay rest
|
|
26
|
+
AfterSnapshot { snapshot_at: u64 },
|
|
27
|
+
/// Simulate crash after index N, recover and replay
|
|
28
|
+
AfterCrash { crash_at: u64 },
|
|
29
|
+
/// Simulate index deletion, rebuild from log
|
|
30
|
+
IndexRebuilt,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Replay path and result
|
|
34
|
+
#[derive(Clone, Debug)]
|
|
35
|
+
pub struct ReplayPath {
|
|
36
|
+
pub scenario: ReplayScenario,
|
|
37
|
+
pub final_state_hash: StateHash,
|
|
38
|
+
pub operations_replayed: u64,
|
|
39
|
+
pub transitions_created: u64,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Replay fuzzer
|
|
43
|
+
pub struct ReplayFuzzer {
|
|
44
|
+
operation_log: MemoryOperationLog,
|
|
45
|
+
transition_store: MemoryStateTransitionStore,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
impl ReplayFuzzer {
|
|
49
|
+
pub fn new(operation_log: MemoryOperationLog, transition_store: MemoryStateTransitionStore) -> Self {
|
|
50
|
+
Self {
|
|
51
|
+
operation_log,
|
|
52
|
+
transition_store,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Replay operations normally (original order)
|
|
57
|
+
pub fn replay_normal(&self) -> ReplayPath {
|
|
58
|
+
let all_ops = self.operation_log.iter();
|
|
59
|
+
let mut state = CanonicalState::new();
|
|
60
|
+
|
|
61
|
+
for entry in all_ops {
|
|
62
|
+
self.apply_operation_to_state(&mut state, &entry.operation);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
ReplayPath {
|
|
66
|
+
scenario: ReplayScenario::Normal,
|
|
67
|
+
final_state_hash: state.hash(),
|
|
68
|
+
operations_replayed: self.operation_log.size(),
|
|
69
|
+
transitions_created: 1,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Replay operations in batches
|
|
74
|
+
pub fn replay_batched(&self, batch_size: usize) -> ReplayPath {
|
|
75
|
+
let mut state = CanonicalState::new();
|
|
76
|
+
let log_size = self.operation_log.size();
|
|
77
|
+
let mut total_batches = 0;
|
|
78
|
+
|
|
79
|
+
for batch_start in (0..log_size).step_by(batch_size) {
|
|
80
|
+
let batch_end = std::cmp::min(batch_start + batch_size as u64, log_size);
|
|
81
|
+
let batch = self.operation_log.get_range(batch_start, batch_end);
|
|
82
|
+
|
|
83
|
+
for entry in batch {
|
|
84
|
+
self.apply_operation_to_state(&mut state, &entry.operation);
|
|
85
|
+
}
|
|
86
|
+
total_batches += 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ReplayPath {
|
|
90
|
+
scenario: ReplayScenario::Batched { batch_size },
|
|
91
|
+
final_state_hash: state.hash(),
|
|
92
|
+
operations_replayed: self.operation_log.size(),
|
|
93
|
+
transitions_created: total_batches as u64,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Replay from snapshot (skip first N operations, replay rest)
|
|
98
|
+
pub fn replay_after_snapshot(&self, snapshot_at: u64) -> ReplayPath {
|
|
99
|
+
let log_size = self.operation_log.size();
|
|
100
|
+
if snapshot_at >= log_size {
|
|
101
|
+
return ReplayPath {
|
|
102
|
+
scenario: ReplayScenario::AfterSnapshot {
|
|
103
|
+
snapshot_at,
|
|
104
|
+
},
|
|
105
|
+
final_state_hash: StateHash::from_hex("snapshot_beyond_log".to_string()),
|
|
106
|
+
operations_replayed: 0,
|
|
107
|
+
transitions_created: 0,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let mut state = CanonicalState::new();
|
|
112
|
+
|
|
113
|
+
// Simulate snapshot state (in real system, would be loaded from snapshot)
|
|
114
|
+
// For this test, assume snapshot captures state after snapshot_at operations
|
|
115
|
+
let pre_snapshot_ops = self.operation_log.get_range(0, snapshot_at);
|
|
116
|
+
for entry in pre_snapshot_ops {
|
|
117
|
+
self.apply_operation_to_state(&mut state, &entry.operation);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Replay remaining operations
|
|
121
|
+
let remaining_ops = self.operation_log.get_range(snapshot_at, log_size);
|
|
122
|
+
for entry in remaining_ops {
|
|
123
|
+
self.apply_operation_to_state(&mut state, &entry.operation);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ReplayPath {
|
|
127
|
+
scenario: ReplayScenario::AfterSnapshot { snapshot_at },
|
|
128
|
+
final_state_hash: state.hash(),
|
|
129
|
+
operations_replayed: log_size,
|
|
130
|
+
transitions_created: 1,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Replay after simulated crash (skip operations after crash point)
|
|
135
|
+
pub fn replay_after_crash(&self, crash_at: u64) -> ReplayPath {
|
|
136
|
+
let log_size = self.operation_log.size();
|
|
137
|
+
let replay_limit = std::cmp::min(crash_at, log_size);
|
|
138
|
+
|
|
139
|
+
let mut state = CanonicalState::new();
|
|
140
|
+
let ops = self.operation_log.get_range(0, replay_limit);
|
|
141
|
+
|
|
142
|
+
for entry in ops {
|
|
143
|
+
self.apply_operation_to_state(&mut state, &entry.operation);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
ReplayPath {
|
|
147
|
+
scenario: ReplayScenario::AfterCrash { crash_at },
|
|
148
|
+
final_state_hash: state.hash(),
|
|
149
|
+
operations_replayed: replay_limit,
|
|
150
|
+
transitions_created: 1,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Replay with index rebuilt from log
|
|
155
|
+
pub fn replay_index_rebuilt(&self) -> ReplayPath {
|
|
156
|
+
// Clear transition store (simulate index deletion)
|
|
157
|
+
let all_ops = self.operation_log.iter();
|
|
158
|
+
let mut state = CanonicalState::new();
|
|
159
|
+
|
|
160
|
+
for entry in all_ops {
|
|
161
|
+
self.apply_operation_to_state(&mut state, &entry.operation);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
ReplayPath {
|
|
165
|
+
scenario: ReplayScenario::IndexRebuilt,
|
|
166
|
+
final_state_hash: state.hash(),
|
|
167
|
+
operations_replayed: self.operation_log.size(),
|
|
168
|
+
transitions_created: 1,
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// Apply an operation's effects to state
|
|
173
|
+
fn apply_operation_to_state(&self, state: &mut CanonicalState, operation: &Operation) {
|
|
174
|
+
if operation.command.op_type == "UpdateField" {
|
|
175
|
+
let collection = operation.command.collection.clone();
|
|
176
|
+
let record_id = operation.command.record_id.clone();
|
|
177
|
+
let data = serde_json::json!(operation.command.fields.clone());
|
|
178
|
+
state.set_record(collection, record_id, data);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Execute full replay scenarios (all operations replayed)
|
|
183
|
+
pub fn execute_full_scenarios(&self) -> Vec<ReplayPath> {
|
|
184
|
+
vec![
|
|
185
|
+
self.replay_normal(),
|
|
186
|
+
self.replay_batched(2),
|
|
187
|
+
self.replay_batched(5),
|
|
188
|
+
self.replay_after_snapshot(self.operation_log.size() / 2),
|
|
189
|
+
self.replay_index_rebuilt(),
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Execute all scenarios (including partial)
|
|
194
|
+
pub fn execute_all_scenarios(&self) -> Vec<ReplayPath> {
|
|
195
|
+
let mut all = self.execute_full_scenarios();
|
|
196
|
+
all.push(self.replay_after_crash(self.operation_log.size().saturating_sub(1)));
|
|
197
|
+
all
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Verify all FULL replay paths converge to same state
|
|
201
|
+
pub fn verify_convergence(&self) -> Result<StateHash, String> {
|
|
202
|
+
let paths = self.execute_full_scenarios();
|
|
203
|
+
|
|
204
|
+
if paths.is_empty() {
|
|
205
|
+
return Err("No replay paths generated".to_string());
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let canonical_hash = &paths[0].final_state_hash;
|
|
209
|
+
|
|
210
|
+
for path in &paths[1..] {
|
|
211
|
+
if &path.final_state_hash != canonical_hash {
|
|
212
|
+
return Err(format!(
|
|
213
|
+
"Convergence failed: {:?} produced {}, expected {}",
|
|
214
|
+
path.scenario, path.final_state_hash, canonical_hash
|
|
215
|
+
));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
Ok(canonical_hash.clone())
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#[cfg(test)]
|
|
224
|
+
mod tests {
|
|
225
|
+
use super::*;
|
|
226
|
+
use crate::convergence::VectorClock;
|
|
227
|
+
|
|
228
|
+
fn create_test_operation(seq: u64, collection: &str, record_id: &str, value: i32) -> Operation {
|
|
229
|
+
let mut vc = VectorClock::new();
|
|
230
|
+
vc.increment("replay_test");
|
|
231
|
+
let version = StateVersion::new(vc, "test_hash".to_string());
|
|
232
|
+
|
|
233
|
+
Operation::new(
|
|
234
|
+
OperationId::new("replay_test".to_string(), seq),
|
|
235
|
+
version,
|
|
236
|
+
format!("Op{}", seq),
|
|
237
|
+
OperationCommand {
|
|
238
|
+
op_type: "UpdateField".to_string(),
|
|
239
|
+
collection: collection.to_string(),
|
|
240
|
+
record_id: record_id.to_string(),
|
|
241
|
+
fields: {
|
|
242
|
+
let mut m = HashMap::new();
|
|
243
|
+
m.insert("value".to_string(), serde_json::json!(value));
|
|
244
|
+
m
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
"test".to_string(),
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
#[test]
|
|
252
|
+
fn test_fuzzer_creation() {
|
|
253
|
+
let log = MemoryOperationLog::new();
|
|
254
|
+
let store = MemoryStateTransitionStore::new();
|
|
255
|
+
let _fuzzer = ReplayFuzzer::new(log, store);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#[test]
|
|
259
|
+
fn test_replay_normal_empty_log() {
|
|
260
|
+
let log = MemoryOperationLog::new();
|
|
261
|
+
let store = MemoryStateTransitionStore::new();
|
|
262
|
+
let fuzzer = ReplayFuzzer::new(log, store);
|
|
263
|
+
|
|
264
|
+
let result = fuzzer.replay_normal();
|
|
265
|
+
assert_eq!(result.operations_replayed, 0);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#[test]
|
|
269
|
+
fn test_replay_normal_single_operation() {
|
|
270
|
+
let log = MemoryOperationLog::new();
|
|
271
|
+
let store = MemoryStateTransitionStore::new();
|
|
272
|
+
|
|
273
|
+
let op = create_test_operation(1, "records", "rec1", 100);
|
|
274
|
+
log.append(op).unwrap();
|
|
275
|
+
|
|
276
|
+
let fuzzer = ReplayFuzzer::new(log, store);
|
|
277
|
+
let result = fuzzer.replay_normal();
|
|
278
|
+
|
|
279
|
+
assert_eq!(result.operations_replayed, 1);
|
|
280
|
+
assert_eq!(result.scenario, ReplayScenario::Normal);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#[test]
|
|
284
|
+
fn test_replay_batched() {
|
|
285
|
+
let log = MemoryOperationLog::new();
|
|
286
|
+
let store = MemoryStateTransitionStore::new();
|
|
287
|
+
|
|
288
|
+
for i in 1..=5 {
|
|
289
|
+
let op = create_test_operation(i, "records", &format!("rec{}", i), i as i32 * 10);
|
|
290
|
+
log.append(op).unwrap();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let fuzzer = ReplayFuzzer::new(log, store);
|
|
294
|
+
let result = fuzzer.replay_batched(2);
|
|
295
|
+
|
|
296
|
+
assert_eq!(result.operations_replayed, 5);
|
|
297
|
+
assert_eq!(result.scenario, ReplayScenario::Batched { batch_size: 2 });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#[test]
|
|
301
|
+
fn test_replay_after_snapshot() {
|
|
302
|
+
let log = MemoryOperationLog::new();
|
|
303
|
+
let store = MemoryStateTransitionStore::new();
|
|
304
|
+
|
|
305
|
+
for i in 1..=5 {
|
|
306
|
+
let op = create_test_operation(i, "records", &format!("rec{}", i), i as i32 * 10);
|
|
307
|
+
log.append(op).unwrap();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let fuzzer = ReplayFuzzer::new(log, store);
|
|
311
|
+
let result = fuzzer.replay_after_snapshot(3);
|
|
312
|
+
|
|
313
|
+
assert_eq!(result.operations_replayed, 5);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#[test]
|
|
317
|
+
fn test_replay_after_crash() {
|
|
318
|
+
let log = MemoryOperationLog::new();
|
|
319
|
+
let store = MemoryStateTransitionStore::new();
|
|
320
|
+
|
|
321
|
+
for i in 1..=5 {
|
|
322
|
+
let op = create_test_operation(i, "records", &format!("rec{}", i), i as i32 * 10);
|
|
323
|
+
log.append(op).unwrap();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let fuzzer = ReplayFuzzer::new(log, store);
|
|
327
|
+
let result = fuzzer.replay_after_crash(3);
|
|
328
|
+
|
|
329
|
+
assert_eq!(result.operations_replayed, 3);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#[test]
|
|
333
|
+
fn test_replay_index_rebuilt() {
|
|
334
|
+
let log = MemoryOperationLog::new();
|
|
335
|
+
let store = MemoryStateTransitionStore::new();
|
|
336
|
+
|
|
337
|
+
for i in 1..=3 {
|
|
338
|
+
let op = create_test_operation(i, "records", &format!("rec{}", i), i as i32 * 10);
|
|
339
|
+
log.append(op).unwrap();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let fuzzer = ReplayFuzzer::new(log, store);
|
|
343
|
+
let result = fuzzer.replay_index_rebuilt();
|
|
344
|
+
|
|
345
|
+
assert_eq!(result.operations_replayed, 3);
|
|
346
|
+
assert_eq!(result.scenario, ReplayScenario::IndexRebuilt);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
#[test]
|
|
350
|
+
fn test_all_scenarios_same_hash() {
|
|
351
|
+
let log = MemoryOperationLog::new();
|
|
352
|
+
let store = MemoryStateTransitionStore::new();
|
|
353
|
+
|
|
354
|
+
for i in 1..=5 {
|
|
355
|
+
let op = create_test_operation(i, "records", &format!("rec{}", i), i as i32 * 10);
|
|
356
|
+
log.append(op).unwrap();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let fuzzer = ReplayFuzzer::new(log, store);
|
|
360
|
+
|
|
361
|
+
let normal = fuzzer.replay_normal();
|
|
362
|
+
let batched = fuzzer.replay_batched(2);
|
|
363
|
+
let snapshot = fuzzer.replay_after_snapshot(2);
|
|
364
|
+
|
|
365
|
+
assert_eq!(normal.final_state_hash, batched.final_state_hash);
|
|
366
|
+
assert_eq!(normal.final_state_hash, snapshot.final_state_hash);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
#[test]
|
|
370
|
+
fn test_verify_convergence() {
|
|
371
|
+
let log = MemoryOperationLog::new();
|
|
372
|
+
let store = MemoryStateTransitionStore::new();
|
|
373
|
+
|
|
374
|
+
for i in 1..=5 {
|
|
375
|
+
let op = create_test_operation(i, "records", &format!("rec{}", i), i as i32 * 10);
|
|
376
|
+
log.append(op).unwrap();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
let fuzzer = ReplayFuzzer::new(log, store);
|
|
380
|
+
let result = fuzzer.verify_convergence();
|
|
381
|
+
|
|
382
|
+
assert!(result.is_ok());
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
#[test]
|
|
386
|
+
fn test_execute_all_scenarios() {
|
|
387
|
+
let log = MemoryOperationLog::new();
|
|
388
|
+
let store = MemoryStateTransitionStore::new();
|
|
389
|
+
|
|
390
|
+
for i in 1..=3 {
|
|
391
|
+
let op = create_test_operation(i, "records", &format!("rec{}", i), i as i32 * 10);
|
|
392
|
+
log.append(op).unwrap();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let fuzzer = ReplayFuzzer::new(log, store);
|
|
396
|
+
let paths = fuzzer.execute_all_scenarios();
|
|
397
|
+
|
|
398
|
+
assert!(paths.len() > 0);
|
|
399
|
+
assert!(paths.iter().all(|p| p.operations_replayed > 0));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/// Replication Manager
|
|
2
|
+
///
|
|
3
|
+
/// Coordinates message routing and delivery between nodes in a distributed cluster.
|
|
4
|
+
/// Handles:
|
|
5
|
+
/// - Tracking pending operations and their delivery status
|
|
6
|
+
/// - Routing messages from leader to followers
|
|
7
|
+
/// - Acknowledgment tracking
|
|
8
|
+
/// - Retry logic for lost messages
|
|
9
|
+
|
|
10
|
+
use crate::distributed_transactions::ReplicationMessage;
|
|
11
|
+
use std::collections::{HashMap, VecDeque};
|
|
12
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
13
|
+
use std::sync::Arc;
|
|
14
|
+
|
|
15
|
+
/// Message delivery status
|
|
16
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
17
|
+
pub enum DeliveryStatus {
|
|
18
|
+
Pending,
|
|
19
|
+
Delivered,
|
|
20
|
+
Acknowledged,
|
|
21
|
+
Failed,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Tracked message for replication
|
|
25
|
+
#[derive(Clone)]
|
|
26
|
+
pub struct TrackedMessage {
|
|
27
|
+
pub message: ReplicationMessage,
|
|
28
|
+
pub status: DeliveryStatus,
|
|
29
|
+
pub send_count: u64,
|
|
30
|
+
pub timestamp_ms: u64,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl TrackedMessage {
|
|
34
|
+
pub fn new(message: ReplicationMessage) -> Self {
|
|
35
|
+
let timestamp_ms = std::time::SystemTime::now()
|
|
36
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
37
|
+
.unwrap()
|
|
38
|
+
.as_millis() as u64;
|
|
39
|
+
|
|
40
|
+
Self {
|
|
41
|
+
message,
|
|
42
|
+
status: DeliveryStatus::Pending,
|
|
43
|
+
send_count: 0,
|
|
44
|
+
timestamp_ms,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Tracks pending messages to be replicated to a specific peer
|
|
50
|
+
pub struct PeerReplicationQueue {
|
|
51
|
+
peer_id: String,
|
|
52
|
+
messages: VecDeque<TrackedMessage>,
|
|
53
|
+
next_seq: Arc<AtomicU64>,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl PeerReplicationQueue {
|
|
57
|
+
pub fn new(peer_id: String) -> Self {
|
|
58
|
+
Self {
|
|
59
|
+
peer_id,
|
|
60
|
+
messages: VecDeque::new(),
|
|
61
|
+
next_seq: Arc::new(AtomicU64::new(1)),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
pub fn enqueue(&mut self, message: ReplicationMessage) {
|
|
66
|
+
self.messages.push_back(TrackedMessage::new(message));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pub fn pending_count(&self) -> usize {
|
|
70
|
+
self.messages.iter().filter(|m| m.status == DeliveryStatus::Pending).count()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pub fn next_pending(&mut self) -> Option<TrackedMessage> {
|
|
74
|
+
for msg in &mut self.messages {
|
|
75
|
+
if msg.status == DeliveryStatus::Pending {
|
|
76
|
+
msg.send_count += 1;
|
|
77
|
+
return Some(msg.clone());
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
None
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub fn mark_delivered(&mut self, seq: u64) {
|
|
84
|
+
if seq > 0 && seq as usize <= self.messages.len() {
|
|
85
|
+
if let Some(msg) = self.messages.get_mut((seq - 1) as usize) {
|
|
86
|
+
msg.status = DeliveryStatus::Delivered;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Replication manager for a cluster
|
|
93
|
+
pub struct ReplicationManager {
|
|
94
|
+
peer_queues: HashMap<String, PeerReplicationQueue>,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
impl ReplicationManager {
|
|
98
|
+
pub fn new() -> Self {
|
|
99
|
+
Self {
|
|
100
|
+
peer_queues: HashMap::new(),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Register a peer to receive replication messages
|
|
105
|
+
pub fn register_peer(&mut self, peer_id: String) {
|
|
106
|
+
self.peer_queues.insert(peer_id.clone(), PeerReplicationQueue::new(peer_id));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Enqueue a message for replication to all peers
|
|
110
|
+
pub fn replicate_to_all(&mut self, message: ReplicationMessage) {
|
|
111
|
+
for queue in self.peer_queues.values_mut() {
|
|
112
|
+
queue.enqueue(message.clone());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Enqueue a message for replication to a specific peer
|
|
117
|
+
pub fn replicate_to_peer(&mut self, peer_id: &str, message: ReplicationMessage) {
|
|
118
|
+
if let Some(queue) = self.peer_queues.get_mut(peer_id) {
|
|
119
|
+
queue.enqueue(message);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Get pending messages for a peer
|
|
124
|
+
pub fn get_pending_for_peer(&mut self, peer_id: &str) -> Vec<TrackedMessage> {
|
|
125
|
+
let mut result = Vec::new();
|
|
126
|
+
if let Some(queue) = self.peer_queues.get_mut(peer_id) {
|
|
127
|
+
while let Some(msg) = queue.next_pending() {
|
|
128
|
+
result.push(msg);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
result
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Mark a message as delivered to a peer
|
|
135
|
+
pub fn mark_peer_delivered(&mut self, peer_id: &str, seq: u64) {
|
|
136
|
+
if let Some(queue) = self.peer_queues.get_mut(peer_id) {
|
|
137
|
+
queue.mark_delivered(seq);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Get replication statistics
|
|
142
|
+
pub fn get_pending_count(&self) -> usize {
|
|
143
|
+
self.peer_queues.values().map(|q| q.pending_count()).sum()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[cfg(test)]
|
|
148
|
+
mod tests {
|
|
149
|
+
use super::*;
|
|
150
|
+
|
|
151
|
+
#[test]
|
|
152
|
+
fn replication_manager_basic() {
|
|
153
|
+
let mut mgr = ReplicationManager::new();
|
|
154
|
+
mgr.register_peer("peer1".to_string());
|
|
155
|
+
|
|
156
|
+
// Verify peer registered
|
|
157
|
+
assert_eq!(mgr.peer_queues.len(), 1);
|
|
158
|
+
assert_eq!(mgr.get_pending_count(), 0);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/// High-level replication protocol abstraction
|
|
2
|
+
///
|
|
3
|
+
/// Decouples ReplicationMessage handling from transport details.
|
|
4
|
+
/// Works with typed messages (ReplicationMessage) rather than raw bytes.
|
|
5
|
+
|
|
6
|
+
use crate::distributed_transactions::ReplicationMessage;
|
|
7
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
8
|
+
use std::sync::Arc;
|
|
9
|
+
|
|
10
|
+
/// Protocol-level transport abstraction that works with ReplicationMessage
|
|
11
|
+
#[async_trait::async_trait]
|
|
12
|
+
pub trait ProtocolTransport: Send + Sync {
|
|
13
|
+
/// Send a replication message to a peer
|
|
14
|
+
async fn send(&self, message: ReplicationMessage) -> Result<(), ProtocolError>;
|
|
15
|
+
|
|
16
|
+
/// Receive replication messages (non-blocking, returns empty if none available)
|
|
17
|
+
async fn receive(&self) -> Result<Vec<ReplicationMessage>, ProtocolError>;
|
|
18
|
+
|
|
19
|
+
/// Connect to a peer (for real network transports)
|
|
20
|
+
async fn connect(&mut self, peer_addr: &str) -> Result<(), ProtocolError>;
|
|
21
|
+
|
|
22
|
+
/// Disconnect from a peer (for real network transports)
|
|
23
|
+
async fn disconnect(&mut self) -> Result<(), ProtocolError>;
|
|
24
|
+
|
|
25
|
+
/// Check if connected to a peer
|
|
26
|
+
fn is_connected(&self) -> bool;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Protocol errors
|
|
30
|
+
#[derive(Clone, Debug)]
|
|
31
|
+
pub enum ProtocolError {
|
|
32
|
+
SerializationError(String),
|
|
33
|
+
TransportError(String),
|
|
34
|
+
ConnectionError(String),
|
|
35
|
+
Timeout,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl std::fmt::Display for ProtocolError {
|
|
39
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
40
|
+
match self {
|
|
41
|
+
Self::SerializationError(s) => write!(f, "serialization error: {}", s),
|
|
42
|
+
Self::TransportError(s) => write!(f, "transport error: {}", s),
|
|
43
|
+
Self::ConnectionError(s) => write!(f, "connection error: {}", s),
|
|
44
|
+
Self::Timeout => write!(f, "timeout"),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
impl std::error::Error for ProtocolError {}
|
|
50
|
+
|
|
51
|
+
/// Metrics for monitoring replication protocol
|
|
52
|
+
#[derive(Clone, Debug, Default)]
|
|
53
|
+
pub struct ProtocolMetrics {
|
|
54
|
+
messages_sent: Arc<AtomicU64>,
|
|
55
|
+
messages_received: Arc<AtomicU64>,
|
|
56
|
+
bytes_sent: Arc<AtomicU64>,
|
|
57
|
+
bytes_received: Arc<AtomicU64>,
|
|
58
|
+
errors: Arc<AtomicU64>,
|
|
59
|
+
connection_state_changes: Arc<AtomicU64>,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl ProtocolMetrics {
|
|
63
|
+
pub fn new() -> Self {
|
|
64
|
+
Self::default()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
pub fn record_send(&self, bytes: usize) {
|
|
68
|
+
self.messages_sent.fetch_add(1, Ordering::Relaxed);
|
|
69
|
+
self.bytes_sent.fetch_add(bytes as u64, Ordering::Relaxed);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn record_receive(&self, bytes: usize) {
|
|
73
|
+
self.messages_received.fetch_add(1, Ordering::Relaxed);
|
|
74
|
+
self.bytes_received.fetch_add(bytes as u64, Ordering::Relaxed);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pub fn record_error(&self) {
|
|
78
|
+
self.errors.fetch_add(1, Ordering::Relaxed);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pub fn record_connection_state_change(&self) {
|
|
82
|
+
self.connection_state_changes.fetch_add(1, Ordering::Relaxed);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pub fn get_messages_sent(&self) -> u64 {
|
|
86
|
+
self.messages_sent.load(Ordering::Relaxed)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pub fn get_messages_received(&self) -> u64 {
|
|
90
|
+
self.messages_received.load(Ordering::Relaxed)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub fn get_bytes_sent(&self) -> u64 {
|
|
94
|
+
self.bytes_sent.load(Ordering::Relaxed)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
pub fn get_bytes_received(&self) -> u64 {
|
|
98
|
+
self.bytes_received.load(Ordering::Relaxed)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pub fn get_errors(&self) -> u64 {
|
|
102
|
+
self.errors.load(Ordering::Relaxed)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
pub fn get_connection_state_changes(&self) -> u64 {
|
|
106
|
+
self.connection_state_changes.load(Ordering::Relaxed)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[cfg(test)]
|
|
111
|
+
mod tests {
|
|
112
|
+
use super::*;
|
|
113
|
+
|
|
114
|
+
#[test]
|
|
115
|
+
fn metrics_track_messages() {
|
|
116
|
+
let metrics = ProtocolMetrics::new();
|
|
117
|
+
metrics.record_send(1024);
|
|
118
|
+
metrics.record_send(2048);
|
|
119
|
+
|
|
120
|
+
assert_eq!(metrics.get_messages_sent(), 2);
|
|
121
|
+
assert_eq!(metrics.get_bytes_sent(), 3072);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#[test]
|
|
125
|
+
fn metrics_track_errors() {
|
|
126
|
+
let metrics = ProtocolMetrics::new();
|
|
127
|
+
metrics.record_error();
|
|
128
|
+
metrics.record_error();
|
|
129
|
+
|
|
130
|
+
assert_eq!(metrics.get_errors(), 2);
|
|
131
|
+
}
|
|
132
|
+
}
|