@feltdb/core 0.15.0 → 0.16.1
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/agent-state.d.ts +20 -0
- package/dist/agent-state.d.ts.map +1 -1
- package/dist/agent-state.js +36 -0
- package/dist/build-identity.json +12 -4
- package/dist/cli/commands.js +187 -4
- package/dist/cli/publish-engine.js +116 -16
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +20 -2
- package/dist/create/build-identity.json +12 -4
- package/dist/create/managed-account.js +93 -35
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +6 -509
- package/dist/create/server-source/Cargo.toml +2 -13
- package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +356 -24
- package/dist/create/server-source/crates/feltdb-server/tests/rust_client.rs +10 -1
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
- package/dist/db.d.ts +11 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +15 -2
- package/dist/error-codes.d.ts +5 -0
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +9 -1
- package/dist/http-db.d.ts +16 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +36 -6
- package/dist/managed-recovery.d.ts +6 -0
- package/dist/managed-recovery.d.ts.map +1 -1
- package/dist/managed-recovery.js +9 -0
- package/dist/studio-app/assets/{feltdb_wasm-BjM1ylL3.js → feltdb_wasm-BUIxCuTR.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-C3D6y6DK.wasm +0 -0
- package/dist/studio-app/assets/index-BhXYAhgS.js +37 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/package.json +1 -1
- package/dist/workload.d.ts +55 -3
- package/dist/workload.d.ts.map +1 -1
- package/dist/workload.js +23 -0
- package/dist/workspace/workspace-connection.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.js +14 -3
- package/package.json +1 -1
- package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
- package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
- package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
- package/dist/create/server-source/crates/feltdb/README.md +0 -75
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
- package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
- package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
- package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
- package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
- package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
- package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
- package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
- package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
- package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
- package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
- package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7365
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
- package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
- package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
- package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
- package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
- package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
- package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
- package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
- package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -6003
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
- package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
- package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
- package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
- package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1269
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
- package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -4222
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
- package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
- package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
- package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
- package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
- package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
- package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
- package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
- package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
- package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
- package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
- package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
- package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
- package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
- package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
- package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
- package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
- package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
- package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
- package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
- package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
- package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
- package/dist/studio-app/assets/index-1wd0uKua.js +0 -30
|
@@ -1,1474 +0,0 @@
|
|
|
1
|
-
//! A FeltDB replication node, as an operating-system process.
|
|
2
|
-
//!
|
|
3
|
-
//! This exists so replication can be proved across a real process and network
|
|
4
|
-
//! boundary rather than between executors sharing a heap. It is a harness, not
|
|
5
|
-
//! a product surface: it speaks a line protocol on stdin/stdout so a test can
|
|
6
|
-
//! drive it, and it implements no replication semantics of its own. Every
|
|
7
|
-
//! decision about ordering, deduplication, durability, and convergence is made
|
|
8
|
-
//! by `DistributedTransactionExecutor`; this binary only moves bytes between
|
|
9
|
-
//! processes and reports what happened.
|
|
10
|
-
//!
|
|
11
|
-
//! ## Links are directed, one TCP connection each
|
|
12
|
-
//!
|
|
13
|
-
//! `TcpTransport` holds a single stream, and its `receive()` holds the stream
|
|
14
|
-
//! lock while awaiting the next message. A bidirectional link would therefore
|
|
15
|
-
//! deadlock the first `send()` behind the receive that is parked on the socket.
|
|
16
|
-
//!
|
|
17
|
-
//! So each direction gets its own connection: for a link X -> Y, Y binds a port
|
|
18
|
-
//! dedicated to X and only ever receives on it, and X connects to that port and
|
|
19
|
-
//! only ever sends. Three nodes in a full mesh is six directed links.
|
|
20
|
-
//!
|
|
21
|
-
//! That is a topology choice made here in the harness. It changes nothing about
|
|
22
|
-
//! the transport or the protocol.
|
|
23
|
-
//!
|
|
24
|
-
//! ## Usage
|
|
25
|
-
//!
|
|
26
|
-
//! ```text
|
|
27
|
-
//! feltdb_node --node-id A --data DIR --recv B=127.0.0.1:9111 --send B=127.0.0.1:9121
|
|
28
|
-
//! ```
|
|
29
|
-
//!
|
|
30
|
-
//! `--recv PEER=ADDR` binds ADDR and accepts the connection PEER makes to it.
|
|
31
|
-
//! `--send PEER=ADDR` connects to the address PEER is listening on for us.
|
|
32
|
-
//!
|
|
33
|
-
//! Commands on stdin, one per line:
|
|
34
|
-
//!
|
|
35
|
-
//! ```text
|
|
36
|
-
//! execute-tx RECORD commit a local transaction and replicate it to every peer
|
|
37
|
-
//! resend-last put the identical envelope back on the wire, to test dedup
|
|
38
|
-
//! query-state the local replica's state hash and applied-operation count
|
|
39
|
-
//! metrics per-link byte and message counters, straight from the transport
|
|
40
|
-
//! causal-state applied frontier, and what is durable but held back
|
|
41
|
-
//! hold-inbound PEER park messages from PEER instead of delivering them
|
|
42
|
-
//! release-inbound PEER [reverse]
|
|
43
|
-
//! deliver what was parked, optionally newest-first
|
|
44
|
-
//! peers per-link connection state
|
|
45
|
-
//! shutdown exit cleanly
|
|
46
|
-
//! ```
|
|
47
|
-
|
|
48
|
-
use feltdb::authority_failover::{AuthorityStore, ClaimResult, FencingToken, Heartbeat};
|
|
49
|
-
use feltdb::convergence::VectorClock;
|
|
50
|
-
use feltdb::distributed_transactions::{
|
|
51
|
-
CausalCapacity, DistributedTransactionExecutor, ReceiveOutcome, ReplicationMessage,
|
|
52
|
-
SubmitOutcome,
|
|
53
|
-
};
|
|
54
|
-
use feltdb::replica_acknowledgements::{
|
|
55
|
-
AcknowledgementStore, ReclamationEvidence, ReplicaAcknowledgement,
|
|
56
|
-
};
|
|
57
|
-
use feltdb::replica_membership::MembershipStore;
|
|
58
|
-
use feltdb::replication_protocol::ProtocolTransport;
|
|
59
|
-
use feltdb::state_hash::StateHash;
|
|
60
|
-
use feltdb::tcp_transport::TcpTransport;
|
|
61
|
-
use feltdb::transactions::{
|
|
62
|
-
ConsistencyContract, Operation, OperationCommand, OperationId, StateVersion,
|
|
63
|
-
};
|
|
64
|
-
use serde_json::json;
|
|
65
|
-
use std::collections::{HashMap, HashSet};
|
|
66
|
-
use std::io::{self, BufRead, Write};
|
|
67
|
-
use std::path::PathBuf;
|
|
68
|
-
use std::sync::atomic::{AtomicBool, Ordering};
|
|
69
|
-
use std::sync::Arc;
|
|
70
|
-
use tokio::sync::{mpsc, Mutex};
|
|
71
|
-
use tokio::time::{sleep, Duration};
|
|
72
|
-
|
|
73
|
-
/// One outbound connection to a peer, used only for sending.
|
|
74
|
-
///
|
|
75
|
-
/// `control` exists because `connect` needs `&mut self`. Everything else goes
|
|
76
|
-
/// through `io`, a clone: `TcpTransport` holds its stream and metrics behind
|
|
77
|
-
/// `Arc`s, so a clone is the same connection, and reading counters or sending
|
|
78
|
-
/// does not queue behind whatever else holds the mutex.
|
|
79
|
-
#[derive(Clone)]
|
|
80
|
-
struct SendLink {
|
|
81
|
-
peer: String,
|
|
82
|
-
addr: String,
|
|
83
|
-
control: Arc<Mutex<TcpTransport>>,
|
|
84
|
-
io: TcpTransport,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/// One inbound connection from a peer, used only for receiving.
|
|
88
|
-
///
|
|
89
|
-
/// The distinction matters more here: `receive()` parks on the socket holding
|
|
90
|
-
/// the transport's internal stream lock, so anything sharing an outer mutex
|
|
91
|
-
/// with it would block until a message happened to arrive.
|
|
92
|
-
#[derive(Clone)]
|
|
93
|
-
struct RecvLink {
|
|
94
|
-
peer: String,
|
|
95
|
-
#[allow(dead_code)]
|
|
96
|
-
addr: String,
|
|
97
|
-
control: Arc<Mutex<TcpTransport>>,
|
|
98
|
-
io: TcpTransport,
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/// Hand one replicated message to the executor and record what it decided.
|
|
102
|
-
///
|
|
103
|
-
/// The harness makes no ordering decision of its own here. It calls the same
|
|
104
|
-
/// `receive_replicated` the shipping receive path calls, and reports the typed
|
|
105
|
-
/// outcome back so a test can assert on "applied" versus "held back" rather
|
|
106
|
-
/// than inferring it from state.
|
|
107
|
-
fn deliver(
|
|
108
|
-
executor: &mut DistributedTransactionExecutor,
|
|
109
|
-
version: &mut StateVersion,
|
|
110
|
-
message: ReplicationMessage,
|
|
111
|
-
) -> serde_json::Value {
|
|
112
|
-
let origin = message.envelope.originating_node.clone();
|
|
113
|
-
let key = format!(
|
|
114
|
-
"{}:{}",
|
|
115
|
-
message.envelope.envelope_id.originating_node, message.envelope.envelope_id.sequence
|
|
116
|
-
);
|
|
117
|
-
let parent = version.clone();
|
|
118
|
-
match executor.receive_replicated(message, parent) {
|
|
119
|
-
Ok(ReceiveOutcome::Applied { state_hash }) => {
|
|
120
|
-
// Only a real application advances the chained version, so a held
|
|
121
|
-
// or duplicated message cannot make the node look like it moved.
|
|
122
|
-
version.state_hash = state_hash.to_string();
|
|
123
|
-
version.vector_clock.increment(&origin);
|
|
124
|
-
json!({ "envelope": key, "outcome": "applied" })
|
|
125
|
-
}
|
|
126
|
-
Ok(ReceiveOutcome::PendingDependencies { missing }) => {
|
|
127
|
-
json!({ "envelope": key, "outcome": "pending", "missing": missing })
|
|
128
|
-
}
|
|
129
|
-
Ok(ReceiveOutcome::AlreadyKnown) => {
|
|
130
|
-
json!({ "envelope": key, "outcome": "already_known" })
|
|
131
|
-
}
|
|
132
|
-
Ok(ReceiveOutcome::CapacityDeferred {
|
|
133
|
-
missing,
|
|
134
|
-
pending_entries,
|
|
135
|
-
pending_bytes,
|
|
136
|
-
}) => {
|
|
137
|
-
// Durable, but not buffered. The caller's obligation is to stop
|
|
138
|
-
// reading from this peer; the envelope is admitted from the log
|
|
139
|
-
// once the backlog drains.
|
|
140
|
-
json!({
|
|
141
|
-
"envelope": key,
|
|
142
|
-
"outcome": "capacity_deferred",
|
|
143
|
-
"missing": missing,
|
|
144
|
-
"pending_entries": pending_entries,
|
|
145
|
-
"pending_bytes": pending_bytes,
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
Err(error) => json!({ "envelope": key, "outcome": "error", "error": error }),
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
fn parse_link(value: &str) -> Result<(String, String), String> {
|
|
153
|
-
let (peer, addr) = value
|
|
154
|
-
.split_once('=')
|
|
155
|
-
.ok_or_else(|| format!("expected PEER=ADDR, got {value}"))?;
|
|
156
|
-
if peer.is_empty() || addr.is_empty() {
|
|
157
|
-
return Err(format!("expected PEER=ADDR, got {value}"));
|
|
158
|
-
}
|
|
159
|
-
Ok((peer.to_string(), addr.to_string()))
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/// Connect with retries: the peer's listener may not be bound yet, and after a
|
|
163
|
-
/// peer restarts its old socket is gone and this is how the link comes back.
|
|
164
|
-
async fn connect_with_retry(link: &SendLink, attempts: u32) -> bool {
|
|
165
|
-
for attempt in 0..attempts {
|
|
166
|
-
let mut control = link.control.lock().await;
|
|
167
|
-
if control.connect(&link.addr).await.is_ok() {
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
drop(control);
|
|
171
|
-
sleep(Duration::from_millis(25 * (attempt as u64 + 1))).await;
|
|
172
|
-
}
|
|
173
|
-
false
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
#[tokio::main]
|
|
177
|
-
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
178
|
-
let args: Vec<String> = std::env::args().collect();
|
|
179
|
-
|
|
180
|
-
let mut node_id = String::new();
|
|
181
|
-
let mut data_dir: Option<PathBuf> = None;
|
|
182
|
-
let mut send_specs: Vec<(String, String)> = Vec::new();
|
|
183
|
-
let mut recv_specs: Vec<(String, String)> = Vec::new();
|
|
184
|
-
let mut max_pending_entries: Option<usize> = None;
|
|
185
|
-
let mut max_pending_bytes: Option<usize> = None;
|
|
186
|
-
let mut authority_priority: u32 = 0;
|
|
187
|
-
let mut heartbeat_interval_ms: u64 = 1_000;
|
|
188
|
-
let mut heartbeat_failure_threshold: u32 = 5;
|
|
189
|
-
|
|
190
|
-
let mut index = 1;
|
|
191
|
-
while index < args.len() {
|
|
192
|
-
match args[index].as_str() {
|
|
193
|
-
"--node-id" => {
|
|
194
|
-
index += 1;
|
|
195
|
-
node_id = args.get(index).ok_or("--node-id requires a value")?.clone();
|
|
196
|
-
}
|
|
197
|
-
"--data" => {
|
|
198
|
-
index += 1;
|
|
199
|
-
data_dir = Some(PathBuf::from(
|
|
200
|
-
args.get(index).ok_or("--data requires a value")?,
|
|
201
|
-
));
|
|
202
|
-
}
|
|
203
|
-
"--send" => {
|
|
204
|
-
index += 1;
|
|
205
|
-
send_specs.push(parse_link(
|
|
206
|
-
args.get(index).ok_or("--send requires PEER=ADDR")?,
|
|
207
|
-
)?);
|
|
208
|
-
}
|
|
209
|
-
"--recv" => {
|
|
210
|
-
index += 1;
|
|
211
|
-
recv_specs.push(parse_link(
|
|
212
|
-
args.get(index).ok_or("--recv requires PEER=ADDR")?,
|
|
213
|
-
)?);
|
|
214
|
-
}
|
|
215
|
-
// The bound is settable so an acceptance test can reach it through
|
|
216
|
-
// the production path. Filling the shipping default would mean
|
|
217
|
-
// sending ten thousand blocked envelopes over TCP; a test that
|
|
218
|
-
// instead constructed a barrier and filled it directly would be
|
|
219
|
-
// measuring a component nothing runs, which is the defect this
|
|
220
|
-
// whole line of work exists to stop repeating.
|
|
221
|
-
"--max-pending-entries" => {
|
|
222
|
-
index += 1;
|
|
223
|
-
max_pending_entries = Some(
|
|
224
|
-
args.get(index)
|
|
225
|
-
.ok_or("--max-pending-entries requires a value")?
|
|
226
|
-
.parse::<usize>()
|
|
227
|
-
.map_err(|e| format!("--max-pending-entries: {e}"))?,
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
"--max-pending-bytes" => {
|
|
231
|
-
index += 1;
|
|
232
|
-
max_pending_bytes = Some(
|
|
233
|
-
args.get(index)
|
|
234
|
-
.ok_or("--max-pending-bytes requires a value")?
|
|
235
|
-
.parse::<usize>()
|
|
236
|
-
.map_err(|e| format!("--max-pending-bytes: {e}"))?,
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
// Static priority for authority election. Higher priority nodes are
|
|
240
|
-
// preferred during failover when multiple candidates have equal state.
|
|
241
|
-
"--authority-priority" => {
|
|
242
|
-
index += 1;
|
|
243
|
-
authority_priority = args
|
|
244
|
-
.get(index)
|
|
245
|
-
.ok_or("--authority-priority requires a value")?
|
|
246
|
-
.parse::<u32>()
|
|
247
|
-
.map_err(|e| format!("--authority-priority: {e}"))?;
|
|
248
|
-
}
|
|
249
|
-
"--heartbeat-interval-ms" => {
|
|
250
|
-
index += 1;
|
|
251
|
-
heartbeat_interval_ms = args
|
|
252
|
-
.get(index)
|
|
253
|
-
.ok_or("--heartbeat-interval-ms requires a value")?
|
|
254
|
-
.parse::<u64>()
|
|
255
|
-
.map_err(|e| format!("--heartbeat-interval-ms: {e}"))?;
|
|
256
|
-
}
|
|
257
|
-
"--heartbeat-failure-threshold" => {
|
|
258
|
-
index += 1;
|
|
259
|
-
heartbeat_failure_threshold = args
|
|
260
|
-
.get(index)
|
|
261
|
-
.ok_or("--heartbeat-failure-threshold requires a value")?
|
|
262
|
-
.parse::<u32>()
|
|
263
|
-
.map_err(|e| format!("--heartbeat-failure-threshold: {e}"))?;
|
|
264
|
-
}
|
|
265
|
-
other => return Err(format!("unknown argument {other}").into()),
|
|
266
|
-
}
|
|
267
|
-
index += 1;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if node_id.is_empty() {
|
|
271
|
-
return Err("--node-id is required".into());
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Durability is the executor's, not this binary's. The log path is per
|
|
275
|
-
// node, so each process owns its own directory on disk.
|
|
276
|
-
let log_path = data_dir.as_ref().map(|dir| {
|
|
277
|
-
std::fs::create_dir_all(dir).ok();
|
|
278
|
-
dir.join("operations.jsonl")
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Membership lives beside the log and is loaded from disk, never rebuilt
|
|
282
|
-
// from --send/--recv. A node's connection flags say who it talks to; they
|
|
283
|
-
// say nothing about who is owed history, and conflating the two is what
|
|
284
|
-
// this store exists to stop.
|
|
285
|
-
let mut membership = MembershipStore::open(
|
|
286
|
-
data_dir
|
|
287
|
-
.as_ref()
|
|
288
|
-
.map(|dir| dir.join("membership.json"))
|
|
289
|
-
.unwrap_or_else(|| PathBuf::from("membership.json")),
|
|
290
|
-
)
|
|
291
|
-
.map_err(|e| e.to_string())?;
|
|
292
|
-
|
|
293
|
-
// Acknowledgements are durable and shared with the receive tasks, because
|
|
294
|
-
// they arrive on the socket rather than through a command.
|
|
295
|
-
let acknowledgements = Arc::new(Mutex::new(
|
|
296
|
-
AcknowledgementStore::open(
|
|
297
|
-
data_dir
|
|
298
|
-
.as_ref()
|
|
299
|
-
.map(|dir| dir.join("acknowledgements.json"))
|
|
300
|
-
.unwrap_or_else(|| PathBuf::from("acknowledgements.json")),
|
|
301
|
-
)
|
|
302
|
-
.map_err(|e| e.to_string())?,
|
|
303
|
-
));
|
|
304
|
-
|
|
305
|
-
// Authority state tracks who holds write authority in the cluster. The store
|
|
306
|
-
// is durable and survives restarts, so a node that crashes can rejoin and
|
|
307
|
-
// recover its previous role. The cluster ID is set by the first `authority-claim`
|
|
308
|
-
// command, so it starts as the node's own ID.
|
|
309
|
-
let authority = Arc::new(Mutex::new(
|
|
310
|
-
AuthorityStore::open_with_failure_detector(
|
|
311
|
-
data_dir
|
|
312
|
-
.as_ref()
|
|
313
|
-
.map(|dir| dir.join("authority.json"))
|
|
314
|
-
.unwrap_or_else(|| PathBuf::from("authority.json")),
|
|
315
|
-
&node_id, // cluster_id defaults to node_id until set
|
|
316
|
-
&node_id,
|
|
317
|
-
authority_priority,
|
|
318
|
-
heartbeat_interval_ms,
|
|
319
|
-
heartbeat_failure_threshold,
|
|
320
|
-
)
|
|
321
|
-
.map_err(|e| e.to_string())?,
|
|
322
|
-
));
|
|
323
|
-
|
|
324
|
-
let initial_state = StateHash::from_hex("0".repeat(64));
|
|
325
|
-
let mut executor = DistributedTransactionExecutor::with_log(
|
|
326
|
-
node_id.clone(),
|
|
327
|
-
initial_state.clone(),
|
|
328
|
-
log_path.clone(),
|
|
329
|
-
)?;
|
|
330
|
-
|
|
331
|
-
// The bound is set before recovery, not after, because recovery restores
|
|
332
|
-
// the backlog and has to respect the same limit the live path does.
|
|
333
|
-
// Otherwise a node could restart into a backlog it would never have
|
|
334
|
-
// accepted while running, and the bound would hold only until the first
|
|
335
|
-
// crash.
|
|
336
|
-
let default_capacity = CausalCapacity::default();
|
|
337
|
-
executor.set_causal_capacity(CausalCapacity::new(
|
|
338
|
-
max_pending_entries.unwrap_or(default_capacity.max_pending_entries),
|
|
339
|
-
max_pending_bytes.unwrap_or(default_capacity.max_pending_bytes),
|
|
340
|
-
));
|
|
341
|
-
|
|
342
|
-
// Replaying the log restores the deduplication set, so a restarted node
|
|
343
|
-
// does not re-apply operations it already durably recorded.
|
|
344
|
-
let mut replayed = 0usize;
|
|
345
|
-
if let Some(path) = &log_path {
|
|
346
|
-
if path.exists() {
|
|
347
|
-
replayed = executor.load_from_disk(path.clone())?;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
for (peer, _) in send_specs.iter().chain(recv_specs.iter()) {
|
|
352
|
-
if peer != &node_id {
|
|
353
|
-
executor.register_replica(peer.clone(), initial_state.clone());
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// The running state version, advanced by every applied transaction.
|
|
358
|
-
//
|
|
359
|
-
// This matters more than it looks. `execute_transaction` derives the next
|
|
360
|
-
// hash as H(parent_hash, command), so passing a fresh zero-hash parent each
|
|
361
|
-
// time — as this binary and the old in-process tests both did — makes the
|
|
362
|
-
// resulting "state hash" a function of the last command alone, carrying no
|
|
363
|
-
// history. Two nodes that applied completely different sets of operations
|
|
364
|
-
// then report identical hashes as long as the most recent one matches, and
|
|
365
|
-
// a convergence check built on that comparison cannot fail.
|
|
366
|
-
//
|
|
367
|
-
// Chaining it makes the hash mean what its name says, and makes a node that
|
|
368
|
-
// missed an operation observably divergent. It lives beside the executor
|
|
369
|
-
// under one lock so a local commit and an arriving replication cannot
|
|
370
|
-
// interleave and chain onto a parent that is already stale.
|
|
371
|
-
let recovered_version = executor
|
|
372
|
-
.get_replica_state(&node_id)
|
|
373
|
-
.map(|replica| StateVersion::new(replica.vector_clock, replica.state_hash.to_string()))
|
|
374
|
-
.unwrap_or_else(|| StateVersion::new(VectorClock::new(), "0".repeat(64)));
|
|
375
|
-
let core = Arc::new(Mutex::new((executor, recovered_version)));
|
|
376
|
-
let should_exit = Arc::new(AtomicBool::new(false));
|
|
377
|
-
// Per-peer delivery hold.
|
|
378
|
-
//
|
|
379
|
-
// `Some(queue)` means messages arriving from that peer are received off the
|
|
380
|
-
// socket as normal and parked instead of being handed to the executor.
|
|
381
|
-
// This is how a test produces out-of-causal-order arrival without faking
|
|
382
|
-
// the network: the bytes really do cross TCP, and only the order in which
|
|
383
|
-
// the receiver is offered them is controlled. A real network reorders
|
|
384
|
-
// delivery this way; the harness just does it on demand.
|
|
385
|
-
let held: Arc<Mutex<HashMap<String, Vec<ReplicationMessage>>>> =
|
|
386
|
-
Arc::new(Mutex::new(HashMap::new()));
|
|
387
|
-
// Peers this node has stopped reading from because the causal backlog is
|
|
388
|
-
// at its bound.
|
|
389
|
-
//
|
|
390
|
-
// This is the backpressure the bounding decision commits to, and not
|
|
391
|
-
// reading is the whole mechanism: the socket's receive buffer fills, the
|
|
392
|
-
// TCP window closes, and the sender blocks. Nothing is dropped and nothing
|
|
393
|
-
// is refused, so no retransmission protocol is needed -- which is good,
|
|
394
|
-
// because there isn't one.
|
|
395
|
-
let backpressured: Arc<Mutex<HashSet<String>>> = Arc::new(Mutex::new(HashSet::new()));
|
|
396
|
-
// Kept so `resend-last` can put the identical envelope back on the wire.
|
|
397
|
-
let last_envelope: Arc<Mutex<Option<feltdb::distributed_transactions::TransactionEnvelope>>> =
|
|
398
|
-
Arc::new(Mutex::new(None));
|
|
399
|
-
|
|
400
|
-
let send_links: Vec<SendLink> = send_specs
|
|
401
|
-
.into_iter()
|
|
402
|
-
.map(|(peer, addr)| {
|
|
403
|
-
let transport = TcpTransport::new("127.0.0.1:0".to_string());
|
|
404
|
-
SendLink {
|
|
405
|
-
peer,
|
|
406
|
-
addr,
|
|
407
|
-
control: Arc::new(Mutex::new(transport.clone())),
|
|
408
|
-
io: transport,
|
|
409
|
-
}
|
|
410
|
-
})
|
|
411
|
-
.collect();
|
|
412
|
-
|
|
413
|
-
let recv_links: Vec<RecvLink> = recv_specs
|
|
414
|
-
.into_iter()
|
|
415
|
-
.map(|(peer, addr)| {
|
|
416
|
-
let transport = TcpTransport::new(addr.clone());
|
|
417
|
-
RecvLink {
|
|
418
|
-
peer,
|
|
419
|
-
addr,
|
|
420
|
-
control: Arc::new(Mutex::new(transport.clone())),
|
|
421
|
-
io: transport,
|
|
422
|
-
}
|
|
423
|
-
})
|
|
424
|
-
.collect();
|
|
425
|
-
|
|
426
|
-
// Accept, serve, and accept again. One task per link, because each parks
|
|
427
|
-
// on its own socket and must not block the others.
|
|
428
|
-
//
|
|
429
|
-
// The re-listen is what makes a peer restart survivable. `TcpTransport`'s
|
|
430
|
-
// `listen()` binds, accepts exactly one connection, and drops the listener,
|
|
431
|
-
// so after that first accept the port is closed and a peer that reconnects
|
|
432
|
-
// is refused. Looping back to `listen()` once the connection ends rebinds
|
|
433
|
-
// the port for the next one. That is a harness-level fix: the transport
|
|
434
|
-
// keeps its single-connection shape and the protocol is untouched.
|
|
435
|
-
for link in &recv_links {
|
|
436
|
-
let control = link.control.clone();
|
|
437
|
-
let transport = link.io.clone();
|
|
438
|
-
let core = core.clone();
|
|
439
|
-
let exit = should_exit.clone();
|
|
440
|
-
let peer = link.peer.clone();
|
|
441
|
-
let held = held.clone();
|
|
442
|
-
let backpressured = backpressured.clone();
|
|
443
|
-
let acknowledgements = acknowledgements.clone();
|
|
444
|
-
tokio::spawn(async move {
|
|
445
|
-
while !exit.load(Ordering::Relaxed) {
|
|
446
|
-
{
|
|
447
|
-
let mut control = control.lock().await;
|
|
448
|
-
if let Err(error) = control.listen().await {
|
|
449
|
-
eprintln!("listen for {peer} failed: {error}");
|
|
450
|
-
drop(control);
|
|
451
|
-
sleep(Duration::from_millis(100)).await;
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Serve this connection until it ends, then rebind for the next.
|
|
457
|
-
loop {
|
|
458
|
-
if exit.load(Ordering::Relaxed) {
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Do not read from a backpressured peer. While paused, keep
|
|
463
|
-
// trying to drain: the work that frees capacity may arrive
|
|
464
|
-
// on another connection, and a deferred envelope is already
|
|
465
|
-
// durable, so draining needs nothing from this socket.
|
|
466
|
-
while backpressured.lock().await.contains(&peer) {
|
|
467
|
-
if exit.load(Ordering::Relaxed) {
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
{
|
|
471
|
-
let mut guard = core.lock().await;
|
|
472
|
-
let (executor, version) = &mut *guard;
|
|
473
|
-
let parent = version.clone();
|
|
474
|
-
let _ = executor.drain_backlog(parent);
|
|
475
|
-
if executor.pending_causal_count()
|
|
476
|
-
< executor.causal_capacity().max_pending_entries
|
|
477
|
-
{
|
|
478
|
-
backpressured.lock().await.remove(&peer);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
sleep(Duration::from_millis(25)).await;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
match transport.receive().await {
|
|
485
|
-
Ok(messages) => {
|
|
486
|
-
// Acknowledgements arrive interleaved with
|
|
487
|
-
// envelopes on the same connection. They are
|
|
488
|
-
// durable before they count as evidence.
|
|
489
|
-
for ack in transport.take_acknowledgements().await {
|
|
490
|
-
let mut store = acknowledgements.lock().await;
|
|
491
|
-
if let Err(error) = store.record(ack) {
|
|
492
|
-
eprintln!("acknowledgement not recorded: {error}");
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
for message in messages {
|
|
496
|
-
{
|
|
497
|
-
let mut hold = held.lock().await;
|
|
498
|
-
if let Some(queue) = hold.get_mut(&peer) {
|
|
499
|
-
queue.push(message);
|
|
500
|
-
continue;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
let outcome = {
|
|
504
|
-
let mut guard = core.lock().await;
|
|
505
|
-
let (executor, version) = &mut *guard;
|
|
506
|
-
deliver(executor, version, message)
|
|
507
|
-
};
|
|
508
|
-
if outcome.get("outcome").and_then(|value| value.as_str())
|
|
509
|
-
== Some("capacity_deferred")
|
|
510
|
-
{
|
|
511
|
-
backpressured.lock().await.insert(peer.clone());
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
Err(_) => break,
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Establish outbound links before announcing readiness, so a test that
|
|
523
|
-
// writes immediately is not racing the topology.
|
|
524
|
-
for link in &send_links {
|
|
525
|
-
connect_with_retry(link, 20).await;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
println!(
|
|
529
|
-
"STARTED {}",
|
|
530
|
-
json!({
|
|
531
|
-
"node": node_id,
|
|
532
|
-
"replayed": replayed,
|
|
533
|
-
"send": send_links.iter().map(|l| l.peer.clone()).collect::<Vec<_>>(),
|
|
534
|
-
"recv": recv_links.iter().map(|l| l.peer.clone()).collect::<Vec<_>>(),
|
|
535
|
-
})
|
|
536
|
-
);
|
|
537
|
-
io::stdout().flush().ok();
|
|
538
|
-
|
|
539
|
-
let (commands, mut incoming) = mpsc::channel::<String>(100);
|
|
540
|
-
tokio::task::spawn_blocking(move || {
|
|
541
|
-
for line in io::stdin().lock().lines().map_while(Result::ok) {
|
|
542
|
-
let line = line.trim().to_string();
|
|
543
|
-
if !line.is_empty() && commands.blocking_send(line).is_err() {
|
|
544
|
-
break;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
while let Some(command) = incoming.recv().await {
|
|
550
|
-
let (verb, argument) = match command.split_once(' ') {
|
|
551
|
-
Some((verb, rest)) => (verb, rest.trim().to_string()),
|
|
552
|
-
None => (command.as_str(), String::new()),
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
match verb {
|
|
556
|
-
"execute-tx" | "authority-write" => {
|
|
557
|
-
let record = if verb == "authority-write" {
|
|
558
|
-
let mut parts = argument.splitn(2, ' ');
|
|
559
|
-
let token = parts.next().and_then(|value| value.parse::<u64>().ok());
|
|
560
|
-
let record = parts.next().unwrap_or("item").to_string();
|
|
561
|
-
let validation = match token {
|
|
562
|
-
Some(token) => authority
|
|
563
|
-
.lock()
|
|
564
|
-
.await
|
|
565
|
-
.validate_quorum_write(FencingToken(token), 2),
|
|
566
|
-
None => Err(feltdb::authority_failover::AuthorityError::StaleToken {
|
|
567
|
-
provided: FencingToken(0),
|
|
568
|
-
current: authority.lock().await.state().current_token(),
|
|
569
|
-
}),
|
|
570
|
-
};
|
|
571
|
-
if let Err(error) = validation {
|
|
572
|
-
println!(
|
|
573
|
-
"AUTHORITY_WRITE_REJECTED {}",
|
|
574
|
-
json!({ "record": record, "reason": error.to_string() })
|
|
575
|
-
);
|
|
576
|
-
io::stdout().flush().ok();
|
|
577
|
-
continue;
|
|
578
|
-
}
|
|
579
|
-
record
|
|
580
|
-
} else if argument.is_empty() {
|
|
581
|
-
"item".to_string()
|
|
582
|
-
} else {
|
|
583
|
-
argument
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
let mut fields = HashMap::new();
|
|
587
|
-
fields.insert("record".to_string(), json!(record.clone()));
|
|
588
|
-
|
|
589
|
-
// The identity comes from the executor, which derives it from the
|
|
590
|
-
// durable log. This binary used to keep its own counter,
|
|
591
|
-
// starting at 1 on every launch, so a restarted node re-issued
|
|
592
|
-
// identities its own earlier operations already held.
|
|
593
|
-
let (parent, seq) = {
|
|
594
|
-
let guard = core.lock().await;
|
|
595
|
-
(guard.1.clone(), guard.0.next_origin_sequence())
|
|
596
|
-
};
|
|
597
|
-
let operation = Operation::new(
|
|
598
|
-
OperationId::new(node_id.clone(), seq),
|
|
599
|
-
parent.clone(),
|
|
600
|
-
format!("tx_{record}"),
|
|
601
|
-
OperationCommand {
|
|
602
|
-
op_type: "set".to_string(),
|
|
603
|
-
collection: "items".to_string(),
|
|
604
|
-
record_id: record.clone(),
|
|
605
|
-
fields,
|
|
606
|
-
},
|
|
607
|
-
node_id.clone(),
|
|
608
|
-
);
|
|
609
|
-
|
|
610
|
-
let envelope = {
|
|
611
|
-
let mut guard = core.lock().await;
|
|
612
|
-
let (executor, version) = &mut *guard;
|
|
613
|
-
// The admitting entry point: local work is refused when the
|
|
614
|
-
// causal backlog is at its bound, before anything is
|
|
615
|
-
// executed or persisted, so a refusal leaves nothing to
|
|
616
|
-
// undo.
|
|
617
|
-
let result = executor.submit_local_transaction(
|
|
618
|
-
format!("tx_{record}"),
|
|
619
|
-
version.clone(),
|
|
620
|
-
vec![operation],
|
|
621
|
-
ConsistencyContract::local(),
|
|
622
|
-
);
|
|
623
|
-
if matches!(result, Ok(SubmitOutcome::Admitted(_))) {
|
|
624
|
-
if let Some(replica) = executor.get_replica_state(&node_id) {
|
|
625
|
-
version.state_hash = replica.state_hash.to_string();
|
|
626
|
-
version.vector_clock.increment(&node_id);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
result
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
match envelope {
|
|
633
|
-
Ok(SubmitOutcome::CapacityExceeded {
|
|
634
|
-
pending_entries,
|
|
635
|
-
pending_bytes,
|
|
636
|
-
limit_entries,
|
|
637
|
-
limit_bytes,
|
|
638
|
-
}) => {
|
|
639
|
-
// A distinct outcome, not an error: the caller should
|
|
640
|
-
// retry, and nothing was admitted or lost.
|
|
641
|
-
println!(
|
|
642
|
-
"TX_REFUSED {}",
|
|
643
|
-
json!({
|
|
644
|
-
"record": record,
|
|
645
|
-
"reason": "CAPACITY_EXCEEDED",
|
|
646
|
-
"pending_entries": pending_entries,
|
|
647
|
-
"pending_bytes": pending_bytes,
|
|
648
|
-
"limit_entries": limit_entries,
|
|
649
|
-
"limit_bytes": limit_bytes,
|
|
650
|
-
})
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
Ok(SubmitOutcome::Admitted(envelope)) => {
|
|
654
|
-
*last_envelope.lock().await = Some(envelope.clone());
|
|
655
|
-
let mut delivered = Vec::new();
|
|
656
|
-
let mut failed = Vec::new();
|
|
657
|
-
for link in &send_links {
|
|
658
|
-
let message = ReplicationMessage::new(
|
|
659
|
-
envelope.clone(),
|
|
660
|
-
node_id.clone(),
|
|
661
|
-
link.peer.clone(),
|
|
662
|
-
seq,
|
|
663
|
-
);
|
|
664
|
-
let sent = link.io.send(message.clone()).await;
|
|
665
|
-
match sent {
|
|
666
|
-
Ok(()) => delivered.push(link.peer.clone()),
|
|
667
|
-
Err(_) => {
|
|
668
|
-
// The peer may have restarted, which drops
|
|
669
|
-
// our end of the socket. Reconnect and try
|
|
670
|
-
// once more before reporting a failure.
|
|
671
|
-
if connect_with_retry(link, 3).await {
|
|
672
|
-
if link.io.send(message).await.is_ok() {
|
|
673
|
-
delivered.push(link.peer.clone());
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
failed.push(link.peer.clone());
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
println!(
|
|
682
|
-
"TX_EXECUTED {}",
|
|
683
|
-
json!({
|
|
684
|
-
"record": record,
|
|
685
|
-
// The identity the executor actually issued.
|
|
686
|
-
"sequence": envelope.envelope_id.sequence,
|
|
687
|
-
"delivered": delivered,
|
|
688
|
-
"failed": failed,
|
|
689
|
-
})
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
Err(error) => println!("ERROR {}", json!({ "reason": error })),
|
|
693
|
-
}
|
|
694
|
-
io::stdout().flush().ok();
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// Re-send the most recent envelope, unchanged, to every peer.
|
|
698
|
-
//
|
|
699
|
-
// Deduplication is a claimed property of the executor, and it is
|
|
700
|
-
// only interesting if the duplicate actually crosses the wire.
|
|
701
|
-
// Issuing another `execute-tx` would not test it: that produces a
|
|
702
|
-
// new envelope id and is a different transaction.
|
|
703
|
-
// `resend SEQUENCE` — put one specific envelope from this node's
|
|
704
|
-
// durable log back on the wire.
|
|
705
|
-
//
|
|
706
|
-
// A peer re-sending an operation another node is missing is the
|
|
707
|
-
// primitive catch-up is built from. This is not catch-up: nothing
|
|
708
|
-
// decides *what* to send, a test does. It exists so the receiver's
|
|
709
|
-
// behaviour can be exercised on a gap that a restart left behind.
|
|
710
|
-
// `forward ORIGIN SEQUENCE` -- put an envelope this node holds, but
|
|
711
|
-
// did not originate, back on the wire.
|
|
712
|
-
//
|
|
713
|
-
// `resend` deliberately only replays this node's own envelopes. A
|
|
714
|
-
// peer relaying an operation that a *third* node is missing is a
|
|
715
|
-
// different primitive, and it is the one a backpressured receiver
|
|
716
|
-
// needs: when a node stops reading from the peer whose envelopes it
|
|
717
|
-
// deferred, the dependency it is waiting for cannot arrive on that
|
|
718
|
-
// link. It has to come from somewhere else, which is precisely the
|
|
719
|
-
// case the read loop's keep-draining-while-paused behaviour exists
|
|
720
|
-
// to serve.
|
|
721
|
-
"forward" => {
|
|
722
|
-
let mut parts = argument.split_whitespace();
|
|
723
|
-
let origin = parts.next().unwrap_or_default().to_string();
|
|
724
|
-
let wanted: Option<u64> = parts.next().and_then(|value| value.parse().ok());
|
|
725
|
-
let envelope = {
|
|
726
|
-
let guard = core.lock().await;
|
|
727
|
-
let (executor, _) = &*guard;
|
|
728
|
-
executor.operation_log.as_ref().and_then(|log| {
|
|
729
|
-
log.load_all().ok().and_then(|envelopes| {
|
|
730
|
-
envelopes.into_iter().find(|candidate| {
|
|
731
|
-
candidate.envelope_id.originating_node == origin
|
|
732
|
-
&& Some(candidate.envelope_id.sequence) == wanted
|
|
733
|
-
})
|
|
734
|
-
})
|
|
735
|
-
})
|
|
736
|
-
};
|
|
737
|
-
match envelope {
|
|
738
|
-
Some(envelope) => {
|
|
739
|
-
let mut delivered = Vec::new();
|
|
740
|
-
for link in &send_links {
|
|
741
|
-
let message = ReplicationMessage::new(
|
|
742
|
-
envelope.clone(),
|
|
743
|
-
node_id.clone(),
|
|
744
|
-
link.peer.clone(),
|
|
745
|
-
envelope.envelope_id.sequence,
|
|
746
|
-
);
|
|
747
|
-
// Forwarding, like re-sending, is an explicit
|
|
748
|
-
// recovery operation. Prefer a fresh connection so
|
|
749
|
-
// a peer restart cannot leave this write accepted
|
|
750
|
-
// by a stale local socket and delivered nowhere.
|
|
751
|
-
let _ = connect_with_retry(link, 3).await;
|
|
752
|
-
if link.io.send(message).await.is_ok() {
|
|
753
|
-
delivered.push(link.peer.clone());
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
println!(
|
|
757
|
-
"FORWARDED {}",
|
|
758
|
-
json!({
|
|
759
|
-
"origin": origin,
|
|
760
|
-
"sequence": wanted,
|
|
761
|
-
"delivered": delivered,
|
|
762
|
-
})
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
None => println!(
|
|
766
|
-
"FORWARDED {}",
|
|
767
|
-
json!({ "origin": origin, "sequence": wanted, "delivered": [], "found": false })
|
|
768
|
-
),
|
|
769
|
-
}
|
|
770
|
-
io::stdout().flush().ok();
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
"resend" => {
|
|
774
|
-
let wanted: Option<u64> = argument.trim().parse().ok();
|
|
775
|
-
let envelope = {
|
|
776
|
-
let guard = core.lock().await;
|
|
777
|
-
let (executor, _) = &*guard;
|
|
778
|
-
executor.operation_log.as_ref().and_then(|log| {
|
|
779
|
-
log.load_all().ok().and_then(|envelopes| {
|
|
780
|
-
envelopes.into_iter().find(|candidate| {
|
|
781
|
-
candidate.envelope_id.originating_node == node_id
|
|
782
|
-
&& Some(candidate.envelope_id.sequence) == wanted
|
|
783
|
-
})
|
|
784
|
-
})
|
|
785
|
-
})
|
|
786
|
-
};
|
|
787
|
-
match envelope {
|
|
788
|
-
Some(envelope) => {
|
|
789
|
-
let mut delivered = Vec::new();
|
|
790
|
-
for link in &send_links {
|
|
791
|
-
let message = ReplicationMessage::new(
|
|
792
|
-
envelope.clone(),
|
|
793
|
-
node_id.clone(),
|
|
794
|
-
link.peer.clone(),
|
|
795
|
-
envelope.envelope_id.sequence,
|
|
796
|
-
);
|
|
797
|
-
// Re-sending is the recovery primitive used after a
|
|
798
|
-
// peer restart. A write to the old TCP socket can
|
|
799
|
-
// still return success after the peer has exited,
|
|
800
|
-
// because that only confirms acceptance by the
|
|
801
|
-
// local kernel. Try to establish a fresh connection
|
|
802
|
-
// first. If the peer never restarted, the connect
|
|
803
|
-
// attempt leaves the established stream untouched.
|
|
804
|
-
let _ = connect_with_retry(link, 3).await;
|
|
805
|
-
if link.io.send(message).await.is_ok() {
|
|
806
|
-
delivered.push(link.peer.clone());
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
println!(
|
|
810
|
-
"RESENT {}",
|
|
811
|
-
json!({ "envelope": envelope.envelope_id, "delivered": delivered })
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
None => println!(
|
|
815
|
-
"ERROR {}",
|
|
816
|
-
json!({ "reason": format!("no envelope {argument} in this node's log") })
|
|
817
|
-
),
|
|
818
|
-
}
|
|
819
|
-
io::stdout().flush().ok();
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
"resend-last" => {
|
|
823
|
-
let envelope = last_envelope.lock().await.clone();
|
|
824
|
-
match envelope {
|
|
825
|
-
Some(envelope) => {
|
|
826
|
-
let mut delivered = Vec::new();
|
|
827
|
-
for link in &send_links {
|
|
828
|
-
let message = ReplicationMessage::new(
|
|
829
|
-
envelope.clone(),
|
|
830
|
-
node_id.clone(),
|
|
831
|
-
link.peer.clone(),
|
|
832
|
-
envelope.envelope_id.sequence,
|
|
833
|
-
);
|
|
834
|
-
if link.io.send(message).await.is_ok() {
|
|
835
|
-
delivered.push(link.peer.clone());
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
println!(
|
|
839
|
-
"RESENT {}",
|
|
840
|
-
json!({ "envelope": envelope.envelope_id, "delivered": delivered })
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
None => println!("ERROR {}", json!({ "reason": "nothing sent yet" })),
|
|
844
|
-
}
|
|
845
|
-
io::stdout().flush().ok();
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
"query-state" => {
|
|
849
|
-
let guard = core.lock().await;
|
|
850
|
-
let (executor, _) = &*guard;
|
|
851
|
-
let canonical = executor
|
|
852
|
-
.canonical_state()
|
|
853
|
-
.map(|state| state.hash().to_string())
|
|
854
|
-
.unwrap_or_else(|error| format!("error: {error}"));
|
|
855
|
-
match executor.get_replica_state(&node_id) {
|
|
856
|
-
Some(replica) => println!(
|
|
857
|
-
"STATE {}",
|
|
858
|
-
json!({
|
|
859
|
-
"hash": replica.state_hash.to_string(),
|
|
860
|
-
"operations_applied": replica.operations_applied,
|
|
861
|
-
// Order-independent, and therefore the one to
|
|
862
|
-
// compare across replicas. The chained hash above
|
|
863
|
-
// detects a missing operation in an ordered
|
|
864
|
-
// history; it calls two nodes that applied the same
|
|
865
|
-
// concurrent operations in different orders
|
|
866
|
-
// divergent, which they are not.
|
|
867
|
-
"canonical_hash": canonical,
|
|
868
|
-
})
|
|
869
|
-
),
|
|
870
|
-
None => println!("ERROR {}", json!({ "reason": "no local replica" })),
|
|
871
|
-
}
|
|
872
|
-
io::stdout().flush().ok();
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// What the causal receiver is holding, and why.
|
|
876
|
-
// Membership commands. Deliberately no compaction frontier and no
|
|
877
|
-
// acknowledgement tracking: this establishes who exists and who is
|
|
878
|
-
// required, and reclaims nothing.
|
|
879
|
-
"cluster-create" => {
|
|
880
|
-
let result = membership.create_cluster(argument.trim());
|
|
881
|
-
println!(
|
|
882
|
-
"CLUSTER {}",
|
|
883
|
-
match result {
|
|
884
|
-
Ok(()) => json!({ "cluster_id": argument.trim(), "created": true }),
|
|
885
|
-
Err(error) => json!({ "created": false, "error": error.to_string() }),
|
|
886
|
-
}
|
|
887
|
-
);
|
|
888
|
-
io::stdout().flush().ok();
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
"replica-join" | "replica-approve" | "replica-suspect" | "replica-seen"
|
|
892
|
-
| "replica-remove" => {
|
|
893
|
-
let mut parts = argument.split_whitespace();
|
|
894
|
-
let replica = parts.next().unwrap_or_default().to_string();
|
|
895
|
-
let result = match verb {
|
|
896
|
-
"replica-join" => {
|
|
897
|
-
let node = parts.next().unwrap_or(&replica).to_string();
|
|
898
|
-
membership.join_replica(&replica, &node)
|
|
899
|
-
}
|
|
900
|
-
"replica-approve" => membership.approve_replica(&replica),
|
|
901
|
-
"replica-suspect" => membership.suspect_replica(&replica),
|
|
902
|
-
"replica-seen" => membership.mark_seen(&replica),
|
|
903
|
-
_ => membership.remove_replica(&replica),
|
|
904
|
-
};
|
|
905
|
-
println!(
|
|
906
|
-
"REPLICA {}",
|
|
907
|
-
match result {
|
|
908
|
-
Ok(()) => json!({ "replica_id": replica, "action": verb, "ok": true }),
|
|
909
|
-
Err(error) =>
|
|
910
|
-
json!({ "replica_id": replica, "action": verb, "ok": false, "error": error.to_string() }),
|
|
911
|
-
}
|
|
912
|
-
);
|
|
913
|
-
io::stdout().flush().ok();
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// Report what this node has durably applied, to every peer.
|
|
917
|
-
//
|
|
918
|
-
// The frontier is read from the executor, where it advances only in
|
|
919
|
-
// record_applied -- after an fsynced envelope is applied. So the
|
|
920
|
-
// statement "durably applied through this frontier" is true by
|
|
921
|
-
// construction rather than by assertion.
|
|
922
|
-
"ack-broadcast" => {
|
|
923
|
-
let (frontier, marks) = {
|
|
924
|
-
let guard = core.lock().await;
|
|
925
|
-
let (executor, _) = &*guard;
|
|
926
|
-
(
|
|
927
|
-
executor.causal_frontier().clone(),
|
|
928
|
-
executor.origin_sequence_high_water(),
|
|
929
|
-
)
|
|
930
|
-
};
|
|
931
|
-
let view: Vec<String> = membership
|
|
932
|
-
.get_members()
|
|
933
|
-
.into_iter()
|
|
934
|
-
.map(|replica| replica.replica_id)
|
|
935
|
-
.collect();
|
|
936
|
-
let replica_id = membership
|
|
937
|
-
.get_members()
|
|
938
|
-
.into_iter()
|
|
939
|
-
.find(|replica| replica.node_id == node_id)
|
|
940
|
-
.map(|replica| replica.replica_id)
|
|
941
|
-
.unwrap_or_else(|| node_id.clone());
|
|
942
|
-
|
|
943
|
-
let ack = ReplicaAcknowledgement::new(
|
|
944
|
-
replica_id.clone(),
|
|
945
|
-
node_id.clone(),
|
|
946
|
-
frontier,
|
|
947
|
-
marks,
|
|
948
|
-
view,
|
|
949
|
-
);
|
|
950
|
-
|
|
951
|
-
// A node's own acknowledgement is evidence it holds first-hand:
|
|
952
|
-
// it knows its own durable frontier without being told. Without
|
|
953
|
-
// this a node would be permanently blocked on itself, since
|
|
954
|
-
// nothing else can report on its behalf.
|
|
955
|
-
{
|
|
956
|
-
let mut store = acknowledgements.lock().await;
|
|
957
|
-
let _ = store.record(ack.clone());
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
let mut delivered = Vec::new();
|
|
961
|
-
for link in &send_links {
|
|
962
|
-
// Acknowledgements are durable recovery evidence and are
|
|
963
|
-
// commonly rebroadcast after a peer restart. Refresh the
|
|
964
|
-
// connection first for the same reason as explicit
|
|
965
|
-
// envelope replay.
|
|
966
|
-
let _ = connect_with_retry(link, 3).await;
|
|
967
|
-
if link.io.send_acknowledgement(ack.clone()).await.is_ok() {
|
|
968
|
-
delivered.push(link.peer.clone());
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
println!(
|
|
972
|
-
"ACK_SENT {}",
|
|
973
|
-
json!({
|
|
974
|
-
"replica_id": replica_id,
|
|
975
|
-
"frontier": ack.applied_frontier.clocks,
|
|
976
|
-
"delivered": delivered,
|
|
977
|
-
})
|
|
978
|
-
);
|
|
979
|
-
io::stdout().flush().ok();
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
"acknowledgements" => {
|
|
983
|
-
let store = acknowledgements.lock().await;
|
|
984
|
-
println!("ACKS {}", json!({ "acknowledgements": store.all() }));
|
|
985
|
-
io::stdout().flush().ok();
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// What this node's evidence permits. Computes; reclaims nothing.
|
|
989
|
-
"reclamation-evidence" => {
|
|
990
|
-
let local: Vec<String> = membership
|
|
991
|
-
.get_required_replicas()
|
|
992
|
-
.into_iter()
|
|
993
|
-
.map(|replica| replica.replica_id)
|
|
994
|
-
.collect();
|
|
995
|
-
let store = acknowledgements.lock().await;
|
|
996
|
-
let report = match store.evidence(&local) {
|
|
997
|
-
ReclamationEvidence::Blocked { waiting_on } => json!({
|
|
998
|
-
"state": "BLOCKED",
|
|
999
|
-
"waiting_on": waiting_on,
|
|
1000
|
-
"required_union": store.required_union(&local),
|
|
1001
|
-
}),
|
|
1002
|
-
ReclamationEvidence::SafeThrough { frontier, covered } => json!({
|
|
1003
|
-
"state": "SAFE_THROUGH",
|
|
1004
|
-
"frontier": frontier.clocks,
|
|
1005
|
-
"covered": covered,
|
|
1006
|
-
"required_union": store.required_union(&local),
|
|
1007
|
-
}),
|
|
1008
|
-
};
|
|
1009
|
-
println!("EVIDENCE {}", report);
|
|
1010
|
-
io::stdout().flush().ok();
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
"members" => {
|
|
1014
|
-
println!(
|
|
1015
|
-
"MEMBERS {}",
|
|
1016
|
-
json!({
|
|
1017
|
-
"cluster_id": membership.cluster().map(|c| c.cluster_id.clone()),
|
|
1018
|
-
"members": membership.get_members(),
|
|
1019
|
-
"all": membership.all_replicas(),
|
|
1020
|
-
})
|
|
1021
|
-
);
|
|
1022
|
-
io::stdout().flush().ok();
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
"required-replicas" => {
|
|
1026
|
-
println!(
|
|
1027
|
-
"REQUIRED {}",
|
|
1028
|
-
json!({ "required": membership.get_required_replicas() })
|
|
1029
|
-
);
|
|
1030
|
-
io::stdout().flush().ok();
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
"causal-state" => {
|
|
1034
|
-
let guard = core.lock().await;
|
|
1035
|
-
let (executor, _) = &*guard;
|
|
1036
|
-
let paused = {
|
|
1037
|
-
let mut peers: Vec<String> =
|
|
1038
|
-
backpressured.lock().await.iter().cloned().collect();
|
|
1039
|
-
peers.sort();
|
|
1040
|
-
peers
|
|
1041
|
-
};
|
|
1042
|
-
println!(
|
|
1043
|
-
"CAUSAL {}",
|
|
1044
|
-
json!({
|
|
1045
|
-
"frontier": executor.causal_frontier().clocks,
|
|
1046
|
-
"pending": executor.pending_causal_keys(),
|
|
1047
|
-
"pending_count": executor.pending_causal_count(),
|
|
1048
|
-
"pending_metadata_bytes": executor.pending_causal_metadata_bytes(),
|
|
1049
|
-
// Occupancy against the bound that is now enforced.
|
|
1050
|
-
// Current and maximum, for both dimensions, because a
|
|
1051
|
-
// backlog that filled and drained between two reads is
|
|
1052
|
-
// invisible to a current-value gauge.
|
|
1053
|
-
"pending_payload_bytes": executor.pending_causal_payload_bytes(),
|
|
1054
|
-
"deferred_count": executor.deferred_causal_count(),
|
|
1055
|
-
"deferred": executor.deferred_causal_keys(),
|
|
1056
|
-
"max_pending_entries_seen": executor.max_causal_occupancy().0,
|
|
1057
|
-
"max_pending_payload_bytes_seen": executor.max_causal_occupancy().1,
|
|
1058
|
-
"limit_entries": executor.causal_capacity().max_pending_entries,
|
|
1059
|
-
"limit_bytes": executor.causal_capacity().max_pending_bytes,
|
|
1060
|
-
// Peers this node has stopped reading from. This is the
|
|
1061
|
-
// observable for backpressure: the pause lives in the
|
|
1062
|
-
// socket read loop, so without it a test can only infer
|
|
1063
|
-
// the behaviour from timing.
|
|
1064
|
-
//
|
|
1065
|
-
// Locked after `core`, matching the order the read loop
|
|
1066
|
-
// takes them in.
|
|
1067
|
-
"backpressured": paused,
|
|
1068
|
-
})
|
|
1069
|
-
);
|
|
1070
|
-
io::stdout().flush().ok();
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
// Park messages from a peer instead of delivering them, so a test
|
|
1074
|
-
// can choose the order the receiver is offered them in.
|
|
1075
|
-
"hold-inbound" => {
|
|
1076
|
-
let mut hold = held.lock().await;
|
|
1077
|
-
hold.entry(argument.clone()).or_default();
|
|
1078
|
-
println!("HELD {}", json!({ "peer": argument }));
|
|
1079
|
-
io::stdout().flush().ok();
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// Deliver what was parked, optionally newest-first, then resume
|
|
1083
|
-
// normal delivery. Each message goes through the same production
|
|
1084
|
-
// receiver as an unheld one.
|
|
1085
|
-
// `release-inbound PEER [reverse] [COUNT]`
|
|
1086
|
-
//
|
|
1087
|
-
// COUNT releases only that many and keeps holding the rest, which
|
|
1088
|
-
// is what lets a test offer an operation while deliberately
|
|
1089
|
-
// withholding the dependency it needs.
|
|
1090
|
-
"release-inbound" => {
|
|
1091
|
-
let mut parts = argument.split_whitespace();
|
|
1092
|
-
let peer = parts.next().unwrap_or_default().to_string();
|
|
1093
|
-
let mut reverse = false;
|
|
1094
|
-
let mut limit: Option<usize> = None;
|
|
1095
|
-
for token in parts {
|
|
1096
|
-
if token == "reverse" {
|
|
1097
|
-
reverse = true;
|
|
1098
|
-
} else if let Ok(value) = token.parse::<usize>() {
|
|
1099
|
-
limit = Some(value);
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
let mut queued = { held.lock().await.remove(&peer).unwrap_or_default() };
|
|
1104
|
-
if reverse {
|
|
1105
|
-
queued.reverse();
|
|
1106
|
-
}
|
|
1107
|
-
let take = limit.unwrap_or(queued.len()).min(queued.len());
|
|
1108
|
-
let remainder = queued.split_off(take);
|
|
1109
|
-
|
|
1110
|
-
let mut outcomes = Vec::new();
|
|
1111
|
-
for message in queued {
|
|
1112
|
-
let mut guard = core.lock().await;
|
|
1113
|
-
let (executor, version) = &mut *guard;
|
|
1114
|
-
outcomes.push(deliver(executor, version, message));
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
// Anything not released stays held, so the hold is still in
|
|
1118
|
-
// force for messages that arrive afterwards.
|
|
1119
|
-
if !remainder.is_empty() || limit.is_some() {
|
|
1120
|
-
held.lock().await.insert(peer.clone(), remainder);
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
println!(
|
|
1124
|
-
"RELEASED {}",
|
|
1125
|
-
json!({ "peer": peer, "reverse": reverse, "outcomes": outcomes })
|
|
1126
|
-
);
|
|
1127
|
-
io::stdout().flush().ok();
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
// The proof that the network carried the work. Counters come from
|
|
1131
|
-
// the transport itself, so a test can assert bytes actually moved
|
|
1132
|
-
// rather than inferring it from converged state.
|
|
1133
|
-
"metrics" => {
|
|
1134
|
-
let mut sent = json!({});
|
|
1135
|
-
let mut received = json!({});
|
|
1136
|
-
for link in &send_links {
|
|
1137
|
-
let metrics = link.io.metrics();
|
|
1138
|
-
sent[&link.peer] = json!({
|
|
1139
|
-
"bytes": metrics.get_bytes_sent(),
|
|
1140
|
-
"messages": metrics.get_messages_sent(),
|
|
1141
|
-
"connection_changes": metrics.get_connection_state_changes(),
|
|
1142
|
-
});
|
|
1143
|
-
}
|
|
1144
|
-
for link in &recv_links {
|
|
1145
|
-
let metrics = link.io.metrics();
|
|
1146
|
-
received[&link.peer] = json!({
|
|
1147
|
-
"bytes": metrics.get_bytes_received(),
|
|
1148
|
-
"messages": metrics.get_messages_received(),
|
|
1149
|
-
"connection_changes": metrics.get_connection_state_changes(),
|
|
1150
|
-
});
|
|
1151
|
-
}
|
|
1152
|
-
println!("METRICS {}", json!({ "sent": sent, "received": received }));
|
|
1153
|
-
io::stdout().flush().ok();
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
"list-operations" => {
|
|
1157
|
-
let guard = core.lock().await;
|
|
1158
|
-
let (executor, _) = &*guard;
|
|
1159
|
-
|
|
1160
|
-
// Get all operations from the log
|
|
1161
|
-
let mut all_ops = Vec::new();
|
|
1162
|
-
let mut applied_ops = Vec::new();
|
|
1163
|
-
let pending_ops = executor.pending_causal_keys();
|
|
1164
|
-
let deferred_ops = executor.deferred_causal_keys();
|
|
1165
|
-
|
|
1166
|
-
if let Some(ref log) = executor.operation_log {
|
|
1167
|
-
if let Ok(envelopes) = log.load_all() {
|
|
1168
|
-
for env in &envelopes {
|
|
1169
|
-
let key = format!(
|
|
1170
|
-
"{}:{}",
|
|
1171
|
-
env.envelope_id.originating_node, env.envelope_id.sequence
|
|
1172
|
-
);
|
|
1173
|
-
all_ops.push(key.clone());
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
// Get applied operations from the barrier
|
|
1179
|
-
for key in executor.causal_barrier.applied_keys() {
|
|
1180
|
-
applied_ops.push(key.to_string());
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
println!(
|
|
1184
|
-
"OPERATIONS {}",
|
|
1185
|
-
json!({
|
|
1186
|
-
"all": all_ops,
|
|
1187
|
-
"applied": applied_ops,
|
|
1188
|
-
"pending": pending_ops,
|
|
1189
|
-
"deferred": deferred_ops,
|
|
1190
|
-
"operations_applied": executor.get_replica_state(&node_id).map(|r| r.operations_applied).unwrap_or(0),
|
|
1191
|
-
})
|
|
1192
|
-
);
|
|
1193
|
-
io::stdout().flush().ok();
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
"operation-inventory" => {
|
|
1197
|
-
let guard = core.lock().await;
|
|
1198
|
-
let (executor, _) = &*guard;
|
|
1199
|
-
|
|
1200
|
-
// Get all operations from the log
|
|
1201
|
-
let mut operations = json!({
|
|
1202
|
-
"applied": [],
|
|
1203
|
-
"pending": [],
|
|
1204
|
-
"deferred": [],
|
|
1205
|
-
});
|
|
1206
|
-
|
|
1207
|
-
if let Some(ref log) = executor.operation_log {
|
|
1208
|
-
if let Ok(envelopes) = log.load_all() {
|
|
1209
|
-
let applied_keys = executor.causal_barrier.applied_keys();
|
|
1210
|
-
let pending_keys = executor.pending_causal_keys();
|
|
1211
|
-
let deferred_keys = executor.deferred_causal_keys();
|
|
1212
|
-
|
|
1213
|
-
for env in &envelopes {
|
|
1214
|
-
let key = format!(
|
|
1215
|
-
"{}:{}",
|
|
1216
|
-
env.envelope_id.originating_node, env.envelope_id.sequence
|
|
1217
|
-
);
|
|
1218
|
-
|
|
1219
|
-
if applied_keys.contains(&key) {
|
|
1220
|
-
operations["applied"].as_array_mut().unwrap().push(json!({
|
|
1221
|
-
"id": key,
|
|
1222
|
-
"origin": env.envelope_id.originating_node,
|
|
1223
|
-
"sequence": env.envelope_id.sequence,
|
|
1224
|
-
}));
|
|
1225
|
-
} else if pending_keys.contains(&key) {
|
|
1226
|
-
operations["pending"].as_array_mut().unwrap().push(json!({
|
|
1227
|
-
"id": key,
|
|
1228
|
-
"origin": env.envelope_id.originating_node,
|
|
1229
|
-
"sequence": env.envelope_id.sequence,
|
|
1230
|
-
}));
|
|
1231
|
-
} else if deferred_keys.contains(&key) {
|
|
1232
|
-
operations["deferred"].as_array_mut().unwrap().push(json!({
|
|
1233
|
-
"id": key,
|
|
1234
|
-
"origin": env.envelope_id.originating_node,
|
|
1235
|
-
"sequence": env.envelope_id.sequence,
|
|
1236
|
-
}));
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
println!("INVENTORY {}", operations);
|
|
1243
|
-
io::stdout().flush().ok();
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
"peers" => {
|
|
1247
|
-
let mut state = json!({});
|
|
1248
|
-
for link in &send_links {
|
|
1249
|
-
state[format!("send:{}", link.peer)] = json!(link.io.is_connected());
|
|
1250
|
-
}
|
|
1251
|
-
for link in &recv_links {
|
|
1252
|
-
state[format!("recv:{}", link.peer)] = json!(link.io.is_connected());
|
|
1253
|
-
}
|
|
1254
|
-
println!("PEERS {state}");
|
|
1255
|
-
io::stdout().flush().ok();
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
// Authority failover commands.
|
|
1259
|
-
//
|
|
1260
|
-
// These commands implement the distributed authority protocol that prevents
|
|
1261
|
-
// split-brain and allows automatic failover when the authority node fails.
|
|
1262
|
-
|
|
1263
|
-
// Claim authority for this node. This generates a new fencing token that
|
|
1264
|
-
// supersedes any previous authority. The claim succeeds only if no other
|
|
1265
|
-
// node has a higher token and appears healthy.
|
|
1266
|
-
"authority-claim" => {
|
|
1267
|
-
let mut guard = authority.lock().await;
|
|
1268
|
-
let result = guard.claim_authority();
|
|
1269
|
-
match result {
|
|
1270
|
-
Ok(ClaimResult::Granted { token }) => {
|
|
1271
|
-
println!(
|
|
1272
|
-
"AUTHORITY_CLAIMED {}",
|
|
1273
|
-
json!({
|
|
1274
|
-
"node": node_id,
|
|
1275
|
-
"fencing_token": token.0,
|
|
1276
|
-
"role": "AUTHORITY",
|
|
1277
|
-
})
|
|
1278
|
-
);
|
|
1279
|
-
}
|
|
1280
|
-
Ok(ClaimResult::Rejected {
|
|
1281
|
-
current_authority,
|
|
1282
|
-
current_token,
|
|
1283
|
-
}) => {
|
|
1284
|
-
println!(
|
|
1285
|
-
"AUTHORITY_REJECTED {}",
|
|
1286
|
-
json!({
|
|
1287
|
-
"current_authority": current_authority,
|
|
1288
|
-
"current_token": current_token.0,
|
|
1289
|
-
"reason": "another authority is still active",
|
|
1290
|
-
})
|
|
1291
|
-
);
|
|
1292
|
-
}
|
|
1293
|
-
Ok(ClaimResult::Fenced { token }) => {
|
|
1294
|
-
println!(
|
|
1295
|
-
"AUTHORITY_FENCED {}",
|
|
1296
|
-
json!({
|
|
1297
|
-
"node": node_id,
|
|
1298
|
-
"fencing_token": token.0,
|
|
1299
|
-
"reason": "this node is fenced",
|
|
1300
|
-
})
|
|
1301
|
-
);
|
|
1302
|
-
}
|
|
1303
|
-
Err(e) => {
|
|
1304
|
-
println!("ERROR {}", json!({ "reason": e.to_string() }));
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
io::stdout().flush().ok();
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
// Accept a new authority announced by another node.
|
|
1311
|
-
// Format: authority-accept NODE_ID TOKEN
|
|
1312
|
-
"authority-accept" => {
|
|
1313
|
-
let mut parts = argument.split_whitespace();
|
|
1314
|
-
let authority_node = parts.next().unwrap_or_default().to_string();
|
|
1315
|
-
let token_value: u64 = parts.next().unwrap_or("0").parse().unwrap_or(0);
|
|
1316
|
-
let token = FencingToken(token_value);
|
|
1317
|
-
|
|
1318
|
-
let mut guard = authority.lock().await;
|
|
1319
|
-
let result = guard.accept_authority(&authority_node, token);
|
|
1320
|
-
match result {
|
|
1321
|
-
Ok(()) => {
|
|
1322
|
-
let state = guard.state();
|
|
1323
|
-
println!(
|
|
1324
|
-
"AUTHORITY_ACCEPTED {}",
|
|
1325
|
-
json!({
|
|
1326
|
-
"authority_node": authority_node,
|
|
1327
|
-
"fencing_token": token.0,
|
|
1328
|
-
"local_role": format!("{:?}", state.local_role),
|
|
1329
|
-
})
|
|
1330
|
-
);
|
|
1331
|
-
}
|
|
1332
|
-
Err(e) => {
|
|
1333
|
-
println!("ERROR {}", json!({ "reason": e.to_string() }));
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
io::stdout().flush().ok();
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
// Query the current authority state.
|
|
1340
|
-
"authority-state" => {
|
|
1341
|
-
let guard = authority.lock().await;
|
|
1342
|
-
let state = guard.state();
|
|
1343
|
-
let operations_applied = {
|
|
1344
|
-
let core_guard = core.lock().await;
|
|
1345
|
-
let (executor, _) = &*core_guard;
|
|
1346
|
-
executor
|
|
1347
|
-
.get_replica_state(&node_id)
|
|
1348
|
-
.map(|r| r.operations_applied)
|
|
1349
|
-
.unwrap_or(0)
|
|
1350
|
-
};
|
|
1351
|
-
println!(
|
|
1352
|
-
"AUTHORITY {}",
|
|
1353
|
-
json!({
|
|
1354
|
-
"cluster_id": state.cluster_id,
|
|
1355
|
-
"local_node": state.local_node_id,
|
|
1356
|
-
"local_role": format!("{:?}", state.local_role),
|
|
1357
|
-
"is_authority": state.is_authority(),
|
|
1358
|
-
"is_fenced": state.is_fenced(),
|
|
1359
|
-
"authority_node": state.authority_node(),
|
|
1360
|
-
"current_token": state.current_token().0,
|
|
1361
|
-
"highest_seen_token": state.highest_seen_token.0,
|
|
1362
|
-
"operations_applied": operations_applied,
|
|
1363
|
-
})
|
|
1364
|
-
);
|
|
1365
|
-
io::stdout().flush().ok();
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
// Record a heartbeat from a peer. This is used for failure detection.
|
|
1369
|
-
// Format: heartbeat NODE_ID [TOKEN] [OPS_APPLIED] [IS_AUTHORITY]
|
|
1370
|
-
"heartbeat" => {
|
|
1371
|
-
let mut parts = argument.split_whitespace();
|
|
1372
|
-
let from_node = parts.next().unwrap_or_default().to_string();
|
|
1373
|
-
|
|
1374
|
-
if from_node.is_empty() {
|
|
1375
|
-
println!(
|
|
1376
|
-
"ERROR {}",
|
|
1377
|
-
json!({ "reason": "heartbeat requires NODE_ID" })
|
|
1378
|
-
);
|
|
1379
|
-
} else {
|
|
1380
|
-
let mut guard = authority.lock().await;
|
|
1381
|
-
guard.heartbeat(&from_node);
|
|
1382
|
-
println!(
|
|
1383
|
-
"HEARTBEAT_RECORDED {}",
|
|
1384
|
-
json!({
|
|
1385
|
-
"from_node": from_node,
|
|
1386
|
-
})
|
|
1387
|
-
);
|
|
1388
|
-
}
|
|
1389
|
-
io::stdout().flush().ok();
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
// Check if the current authority has failed based on heartbeat timeout.
|
|
1393
|
-
"authority-failed" => {
|
|
1394
|
-
let guard = authority.lock().await;
|
|
1395
|
-
let failed = guard.authority_failed();
|
|
1396
|
-
let failed_nodes = guard.failed_nodes();
|
|
1397
|
-
let state = guard.state();
|
|
1398
|
-
println!(
|
|
1399
|
-
"AUTHORITY_CHECK {}",
|
|
1400
|
-
json!({
|
|
1401
|
-
"authority_failed": failed,
|
|
1402
|
-
"current_authority": state.authority_node(),
|
|
1403
|
-
"failed_nodes": failed_nodes,
|
|
1404
|
-
})
|
|
1405
|
-
);
|
|
1406
|
-
io::stdout().flush().ok();
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
// Step down from authority role voluntarily.
|
|
1410
|
-
"authority-stepdown" => {
|
|
1411
|
-
let mut guard = authority.lock().await;
|
|
1412
|
-
let was_authority = guard.state().is_authority();
|
|
1413
|
-
let result = guard.step_down();
|
|
1414
|
-
match result {
|
|
1415
|
-
Ok(()) => {
|
|
1416
|
-
println!(
|
|
1417
|
-
"AUTHORITY_STEPDOWN {}",
|
|
1418
|
-
json!({
|
|
1419
|
-
"node": node_id,
|
|
1420
|
-
"was_authority": was_authority,
|
|
1421
|
-
"now_role": format!("{:?}", guard.state().local_role),
|
|
1422
|
-
})
|
|
1423
|
-
);
|
|
1424
|
-
}
|
|
1425
|
-
Err(e) => {
|
|
1426
|
-
println!("ERROR {}", json!({ "reason": e.to_string() }));
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
io::stdout().flush().ok();
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
// Recover from fenced state after catching up.
|
|
1433
|
-
"authority-recover" => {
|
|
1434
|
-
let mut guard = authority.lock().await;
|
|
1435
|
-
let was_fenced = guard.state().is_fenced();
|
|
1436
|
-
let result = guard.recover_from_fenced();
|
|
1437
|
-
match result {
|
|
1438
|
-
Ok(()) => {
|
|
1439
|
-
println!(
|
|
1440
|
-
"AUTHORITY_RECOVERED {}",
|
|
1441
|
-
json!({
|
|
1442
|
-
"node": node_id,
|
|
1443
|
-
"was_fenced": was_fenced,
|
|
1444
|
-
"now_role": format!("{:?}", guard.state().local_role),
|
|
1445
|
-
})
|
|
1446
|
-
);
|
|
1447
|
-
}
|
|
1448
|
-
Err(e) => {
|
|
1449
|
-
println!("ERROR {}", json!({ "reason": e.to_string() }));
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
io::stdout().flush().ok();
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
"shutdown" => {
|
|
1456
|
-
should_exit.store(true, Ordering::Relaxed);
|
|
1457
|
-
println!("SHUTDOWN");
|
|
1458
|
-
io::stdout().flush().ok();
|
|
1459
|
-
sleep(Duration::from_millis(50)).await;
|
|
1460
|
-
std::process::exit(0);
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
_ => {
|
|
1464
|
-
println!(
|
|
1465
|
-
"ERROR {}",
|
|
1466
|
-
json!({ "reason": format!("unknown command {verb}") })
|
|
1467
|
-
);
|
|
1468
|
-
io::stdout().flush().ok();
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
Ok(())
|
|
1474
|
-
}
|