Skip to content

fix(storage): Fix full_snapshot RPC deadlock - #1330

Merged
gtema merged 1 commit into
mainfrom
claude/github-issue-1329-hvc1bi
Sep 20, 2026
Merged

gtema merged 1 commit into
mainfrom
claude/github-issue-1329-hvc1bi

Conversation

@gtema

@gtema gtema commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1329: a purged-log leader never resumed replicating to a newly
added learner.

Root cause: NetworkConnection::full_snapshot (crates/storage/src/network.rs)
awaited the client-streaming Snapshot RPC's response (client.snapshot(rx).await?)
before sending anything into the mpsc channel backing its request
stream (rx). The server side (RaftServiceImpl::snapshot,
crates/storage/src/grpc/raft_service.rs) only replies once it has read
the entire request stream. Since both the "await the response" and
"produce the stream" code lived in the same task, in that order, the
task deadlocked against itself on every attempt to send a snapshot —
silently, since the surrounding retry loop in openraft's
SnapshotTransmitter only logs on Err, and a hung .await produces
neither an Err nor any further tracing.

This is why a learner added after the leader's log was snapshotted and
purged never caught up: openraft correctly detects the need for a
snapshot and issues Command::ReplicateSnapshot, but the actual RPC
call to transmit it then hung forever, so RaftMetrics.last_log_index
on the learner stayed None indefinitely. This reproduced identically
with real, separate OS threads/runtimes per node, ruling out the
test-harness explanation the issue raised as an open question — it's a
deterministic bug in the RPC client code, not a threading/harness
artifact.

The fix: since the whole snapshot already lives in memory
(Vec<u8>) by the time full_snapshot runs, build the complete
request stream eagerly with futures::stream::iter(...) instead of
funneling it through a channel fed by code that comes after the
.await. This removes the self-deadlock entirely and needs no
separate feeder task.

Also updates a stale doc comment on test_join_adopts_cluster_dek_inner
that described this bug (discovered while working on #1298) as an
open, unfixed gap.

Test plan

  • Added test_purge_then_join_learner_catches_up_via_snapshot to
    crates/storage/tests/test_cluster.rs: brings up a single-node
    cluster, writes 20 keys, triggers snapshot() + purge_log(),
    brings up a fresh node, adds it as a learner via a raw add_learner
    call, and asserts it catches up via InstallSnapshot within 10s.
    Confirmed this test hangs forever against the pre-fix code and
    passes reliably after the fix.
  • cargo test -p openstack-keystone-distributed-storage --test test_cluster -- --test-threads=1: all 13 tests pass.
  • cargo test -p openstack-keystone-distributed-storage --lib: all 95
    unit tests pass.
  • cargo clippy -p openstack-keystone-distributed-storage --lib --tests and cargo fmt -p openstack-keystone-distributed-storage -- --check: clean.
  • pre-commit run --files crates/storage/src/network.rs crates/storage/tests/test_cluster.rs and the committed commit-msg
    hook: both pass.

@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown

🧪 Tempest Identity Compatibility Results (advisory, non-blocking)

rust

Totals
======
Ran: 133 tests in 30.1253 sec.
 - Passed: 64
 - Skipped: 4
 - Expected Fail: 0
 - Unexpected Success: 0
 - Failed: 65
Sum of execute time for each test: 24.4748 sec.
Failed test IDs
===== FAILED TESTS (target=rust) =====
tempest.api.identity.admin.v3.test_application_credentials.ApplicationCredentialsV3AdminTest.test_create_application_credential_with_roles[id-3b3dd48f-3388-406a-a9e6-4d078a552d0e]
tempest.api.identity.admin.v3.test_inherits.InheritsV3TestJSON.test_inherit_assign_check_revoke_roles_on_projects_group[id-26021436-d5a4-4256-943c-ded01e0d4b45]
tempest.api.identity.admin.v3.test_inherits.InheritsV3TestJSON.test_inherit_assign_check_revoke_roles_on_projects_user[id-18b70e45-7687-4b72-8277-b8f1a47d7591]
tempest.api.identity.admin.v3.test_inherits.InheritsV3TestJSON.test_inherit_assign_list_check_revoke_roles_on_domains_group[id-c7a8dda2-be50-4fb4-9a9c-e830771078b1]
tempest.api.identity.admin.v3.test_inherits.InheritsV3TestJSON.test_inherit_assign_list_check_revoke_roles_on_domains_user[id-4e6f0366-97c8-423c-b2be-41eae6ac91c8]
tempest.api.identity.admin.v3.test_inherits.InheritsV3TestJSON.test_inherit_assign_list_revoke_user_roles_on_domain[id-3acf666e-5354-42ac-8e17-8b68893bcd36]
tempest.api.identity.admin.v3.test_inherits.InheritsV3TestJSON.test_inherit_assign_list_revoke_user_roles_on_project_tree[id-9f02ccd9-9b57-46b4-8f77-dd5a736f3a06]
tempest.api.identity.admin.v3.test_domains.DomainsTestJSON.test_create_domain_without_description[id-2abf8764-309a-4fa9-bc58-201b799817ad]
tempest.api.identity.admin.v3.test_domains.DomainsTestJSON.test_create_update_delete_domain[id-f2f5b44a-82e8-4dad-8084-0661ea3b18cf,smoke]
tempest.api.identity.admin.v3.test_default_project_id.TestDefaultProjectId.test_default_project_id[id-d6110661-6a71-49a7-a453-b5e26640ff6d]
tempest.api.identity.admin.v3.test_domains.DomainsTestJSON.test_domain_delete_cascades_content[id-d8d318b7-d1b3-4c37-94c5-3c5ba0b121ea]
tempest.api.identity.admin.v3.test_domains.DomainsTestJSON.test_list_domains_filter_by_enabled[id-3fd19840-65c1-43f8-b48c-51bdd066dff9]
tempest.api.identity.admin.v3.test_credentials.CredentialsTestJSON.test_credentials_create_get_update_delete[id-7cd59bf9-bda4-4c72-9467-d21cab278355,smoke]
tempest.api.identity.admin.v3.test_list_projects.ListProjectsTestJSON.test_list_projects_with_enabled[id-0fe7a334-675a-4509-b00e-1c4b95d5dae8]
tempest.api.identity.admin.v3.test_list_projects.ListProjectsTestJSON.test_list_projects_with_parent[id-6edc66f5-2941-4a17-9526-4073311c1fac]
setUpClass (tempest.api.identity.admin.v3.test_endpoint_groups.EndPointGroupsTest)
tempest.api.identity.admin.v3.test_groups.GroupsV3TestJSON.test_group_users_add_list_delete[id-1598521a-2f36-4606-8df9-30772bd51339,smoke]
tempest.api.identity.admin.v3.test_groups.GroupsV3TestJSON.test_list_user_groups[id-64573281-d26a-4a52-b899-503cb0f4e4ec]
tempest.api.identity.admin.v3.test_domain_configuration.DomainConfigurationTestJSON.test_create_domain_config_and_show_config_groups_and_options[id-9e3ff13c-f597-4f01-9377-d6c06c2a1477]
tempest.api.identity.admin.v3.test_domain_configuration.DomainConfigurationTestJSON.test_create_update_and_delete_domain_config_groups_and_opts[id-c7510fa2-6661-4170-9c6b-4783a80651e9]
tempest.api.identity.admin.v3.test_domain_configuration.DomainConfigurationTestJSON.test_show_default_group_config_and_options[id-11a02bf0-6f94-4380-b3b0-c8dc18fc0d22]
tempest.api.identity.admin.v3.test_oauth_consumers.OAUTHConsumersV3Test.test_create_and_show_consumer[id-c8307ea6-a86c-47fd-ae7b-5b3b2caca76d]
tempest.api.identity.admin.v3.test_oauth_consumers.OAUTHConsumersV3Test.test_delete_consumer[id-fdfa1b7f-2a31-4354-b2c7-f6ae20554f93]
tempest.api.identity.admin.v3.test_oauth_consumers.OAUTHConsumersV3Test.test_list_consumers[id-09ca50de-78f2-4ffb-ac71-f2254036b2b8]
tempest.api.identity.admin.v3.test_oauth_consumers.OAUTHConsumersV3Test.test_update_consumer[id-080a9b1a-c009-47c0-9979-5305bf72e3dc]
tempest.api.identity.admin.v3.test_domains_negative.DomainsNegativeTestJSON.test_create_domain_with_name_length_over_64[id-37b1bbf2-d664-4785-9a11-333438586eae,negative]
tempest.api.identity.admin.v3.test_domains_negative.DomainsNegativeTestJSON.test_delete_active_domain[gate,id-1f3fbff5-4e44-400d-9ca1-d953f05f609b,negative]
tempest.api.identity.admin.v3.test_list_users.UsersV3TestJSON.test_list_users_with_not_enabled[id-bff8bf2f-9408-4ef5-b63a-753c8c2124eb]
tempest.api.identity.admin.v3.test_project_tags.IdentityV3ProjectTagsTest.test_list_update_delete_project_tags[id-7c123aac-999d-416a-a0fb-84b915ab10de]
tempest.api.identity.admin.v3.test_projects_negative.ProjectsNegativeStaticTestJSON.test_create_project_by_unauthorized_user[id-8fba9de2-3e1f-4e77-812a-60cb68f8df13,negative]
tempest.api.identity.admin.v3.test_projects_negative.ProjectsNegativeStaticTestJSON.test_create_projects_name_length_over_64[id-502b6ceb-b0c8-4422-bf53-f08fdb21e2f0,negative]
tempest.api.identity.admin.v3.test_projects_negative.ProjectsNegativeStaticTestJSON.test_list_projects_by_unauthorized_user[id-24c49279-45dd-4155-887a-cb738c2385aa,negative]
tempest.api.identity.admin.v3.test_projects.ProjectsTestJSON.test_create_is_domain_project[id-a7eb9416-6f9b-4dbb-b71b-7f73aaef59d5]
tempest.api.identity.admin.v3.test_projects.ProjectsTestJSON.test_project_create_with_parent[id-1854f9c0-70bc-4d11-a08a-1c789d339e3d]
tempest.api.identity.admin.v3.test_tokens.TokensV3TestJSON.test_get_available_domain_scopes[id-ec5ecb05-af64-4c04-ac86-4d9f6f12f185]
tempest.api.identity.admin.v3.test_projects.ProjectsTestJSON.test_project_get_equals_list[id-d1db68b6-aebe-4fa0-b79d-d724d2e21162]
tempest.api.identity.admin.v3.test_tokens.TokensV3TestJSON.test_rescope_token[id-565fa210-1da1-4563-999b-f7b5b67cf112]
tempest.api.identity.admin.v3.test_list_projects.ListProjectsStaticTestJSON.test_list_projects[id-1d830662-22ad-427c-8c3e-4ec854b0af44]
tempest.api.identity.admin.v3.test_trusts.TrustsV3TestJSON.test_get_trusts_all[id-4773ebd5-ecbf-4255-b8d8-b63e6f72b65d,smoke]
tempest.api.identity.admin.v3.test_users.UsersV3TestJSON.test_list_user_projects[id-a831e70c-e35b-430b-92ed-81ebbc5437b8]
tempest.api.identity.admin.v3.test_trusts.TrustsV3TestJSON.test_get_trusts_query[id-6268b345-87ca-47c0-9ce3-37792b43403a]
tempest.api.identity.v3.test_tokens.TokensV3Test.test_validate_token[id-a9512ac3-3909-48a4-b395-11f438e16260]
tempest.api.identity.admin.v3.test_trusts.TrustsV3TestJSON.test_trust_expire[id-0ed14b66-cefd-4b5c-a964-65759453e292]
tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_assignments_for_domain_roles[id-3859df7e-5b78-4e4d-b10e-214c8953842a]
tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_assignments_for_implied_roles_create_delete[id-c8828027-df48-4021-95df-b65b92c7429e]
tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_grant_list_revoke_role_to_group_on_domain[id-4bf8a70b-e785-413a-ad53-9f91ce02faa7]
tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_grant_list_revoke_role_to_group_on_project[id-cbf11737-1904-4690-9613-97bcbb3df1c4]
tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_grant_list_revoke_role_to_group_on_system[id-c888fe4f-8018-48db-b959-542225c1b4b6]
tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_grant_list_revoke_role_to_user_on_domain[id-6c9a2940-3625-43a3-ac02-5dcec62ef3bd]
tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_implied_domain_roles[id-eb1e1c24-1bc4-4d47-9748-e127a1852c82]
tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_implied_roles_create_check_show_delete[id-c90c316c-d706-4728-bcba-eb1912081b69]
tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_role_create_update_show_list[id-18afc6c0-46cf-4911-824e-9989cc056c3a,smoke]
tempest.api.identity.admin.v3.test_trusts.TrustsV3TestJSON.test_trust_impersonate[id-5a0a91a4-baef-4a14-baba-59bf4d7fcace]
tempest.api.identity.admin.v3.test_trusts.TrustsV3TestJSON.test_trust_noimpersonate[id-ed2a8779-a7ac-49dc-afd7-30f32f936ed2]
tempest.api.identity.v3.test_catalog.IdentityCatalogTest.test_catalog_standardization[id-56b57ced-22b8-4127-9b8a-565dfb0207e2]
tempest.api.identity.admin.v3.test_users_negative.UsersNegativeTest.test_create_user_for_non_existent_domain[id-e75f006c-89cc-477b-874d-588e4eab4b17,negative]
tempest.api.identity.v3.test_application_credentials.ApplicationCredentialsV3Test.test_create_application_credential[id-8080c75c-eddc-4786-941a-c2da7039ae61]
tempest.api.identity.v3.test_application_credentials.ApplicationCredentialsV3Test.test_create_application_credential_expires[id-852daf0c-42b5-4239-8466-d193d0543ed3]
tempest.api.identity.v3.test_application_credentials.ApplicationCredentialsV3Test.test_list_application_credentials[id-ff0cd457-6224-46e7-b79e-0ada4964a8a6]
tempest.api.identity.v3.test_application_credentials.ApplicationCredentialsV3Test.test_query_application_credentials[id-9bb5e5cc-5250-493a-8869-8b665f6aa5f6]
tempest.api.identity.v3.test_api_discovery.TestApiDiscovery.test_api_media_types[id-657c1970-4722-4189-8831-7325f3bc4265,smoke]
tempest.api.identity.v3.test_api_discovery.TestApiDiscovery.test_api_version_resources[id-b9232f5e-d9e5-4d97-b96c-28d3db4de1bd,smoke]
tempest.api.identity.v3.test_api_discovery.TestApiDiscovery.test_identity_v3_existence[id-79aec9ae-710f-4c54-a4fc-3aa25b4feac3]
tempest.api.identity.v3.test_api_discovery.TestApiDiscovery.test_list_api_versions[id-721f480f-35b6-46c7-846e-047e6acea0dc,smoke]
tempest.api.identity.v3.test_projects.IdentityV3ProjectsTest.test_list_projects_returns_only_authorized_projects[id-86128d46-e170-4644-866a-cc487f699e1d]
===== END FAILED TESTS (target=rust) =====

View full run

@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown

🦢 Load Test Results

Goose Attack Report

Plan Overview

Action Started Stopped Elapsed Users
Increasing 26-09-19 18:22:50 26-09-19 18:23:12 00:00:22 0 → 45
Maintaining 26-09-19 18:23:12 26-09-19 18:23:42 00:00:30 45
Decreasing 26-09-19 18:23:42 26-09-19 18:23:43 00:00:01 0 ← 45

Request Metrics

Method Name # Requests # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
DELETE DELETE /v3/auth/tokens 45 0 526.07 86 671 1.50 0.00
DELETE DELETE /v3/projects/:id (teardown) 1 0 51.00 51 51 0.03 0.00
DELETE DELETE /v3/roles/:id (teardown) 1 0 126.00 126 126 0.03 0.00
DELETE DELETE /v3/users/:id (teardown) 2 0 97.00 74 120 0.07 0.00
DELETE DELETE /v4/mappings/rulesets/:mapping_id (teardown) 1 0 123.00 123 123 0.03 0.00
DELETE DELETE /v4/oauth2/:domain_id/clients/:provider_id (teardown) 1 0 79.00 79 79 0.03 0.00
GET 617 0 572.87 281 800 20.57 0.00
GET GET /SCIM/v2/:domain_id/Users (sync reconcile list) 1755 1755 16.95 1 988 58.50 58.50
GET GET /v3/auth/tokens (validate new) 45 0 534.71 129 684 1.50 0.00
GET GET /v3/auth/tokens (validate revoked) 46 0 473.28 29 643 1.53 0.00
GET GET /v3/projects/:id 57 0 541.04 258 668 1.90 0.00
GET GET /v3/projects/:id (catalog) 83 0 532.65 345 694 2.77 0.00
GET GET /v3/role_assignments 42 0 547.88 397 690 1.40 0.00
GET GET /v3/role_assignments?role.id 44 0 528.30 410 642 1.47 0.00
GET GET /v3/role_assignments?scope.domain.id 43 0 520.05 380 629 1.43 0.00
GET GET /v3/role_assignments?user.id 43 0 518.12 414 671 1.43 0.00
GET GET /v3/roles 114 0 534.64 311 703 3.80 0.00
GET GET /v3/roles/:id 57 0 531.14 346 638 1.90 0.00
GET GET /v3/users/:id 110 0 555.65 398 748 3.67 0.00
GET GET /v3/users/:id (catalog) 107 0 557.96 375 734 3.57 0.00
GET GET /v4/api-keys 28 0 561.18 389 686 0.93 0.00
GET GET /v4/api-keys/:client_id 27 0 556.15 407 659 0.90 0.00
GET GET /v4/mappings/rulesets 29 0 528.79 383 648 0.97 0.00
GET GET /v4/mappings/rulesets/:mapping_id 28 0 549.32 409 671 0.93 0.00
GET GET /v4/oauth2/:domain_id/.well-known/openid-configuration 879 0 50.22 11 159 29.30 0.00
GET GET /v4/oauth2/:domain_id/clients 28 0 536.14 419 645 0.93 0.00
GET GET /v4/oauth2/:domain_id/clients/:provider_id 29 0 528.62 286 702 0.97 0.00
GET GET /v4/oauth2/:domain_id/jwks 878 0 51.78 9 258 29.27 0.00
GET GET /v4/scim_realms 18 0 541.94 404 622 0.60 0.00
GET GET /v4/scim_realms/:domain_id/:provider_id 19 0 520.89 428 597 0.63 0.00
POST POST /v3/auth/tokens 44 0 498.75 345 595 1.47 0.00
POST POST /v3/auth/tokens (password, invalid) 33 0 935.73 569 1454 1.10 0.00
POST POST /v3/auth/tokens (password, project-scoped) 57 0 1221.95 527 1730 1.90 0.00
POST POST /v3/auth/tokens (password, unscoped) 54 0 1011.69 591 1859 1.80 0.00
POST POST /v3/auth/tokens (rescope to system) 100 0 607.32 457 748 3.33 0.00
POST POST /v3/auth/tokens (rescope, invalid project) 61 0 498.85 323 658 2.03 0.00
POST POST /v3/auth/tokens (system-scoped, bootstrap admin) 50 0 1236.20 488 1697 1.67 0.00
POST POST /v4/api-keys/:client_id/revoke (teardown) 1 0 103.00 103 103 0.03 0.00
PUT PUT /v4/scim_realms/:domain_id/:provider_id 18 0 588.22 369 756 0.60 0.00
Aggregated 5595 1755 245.41 1 1859 186.50 58.50

Response Time Metrics

Method Name 50%ile (ms) 60%ile (ms) 70%ile (ms) 80%ile (ms) 90%ile (ms) 95%ile (ms) 99%ile (ms) 100%ile (ms)
DELETE DELETE /v3/auth/tokens 500 600 600 600 600 600 671 671
DELETE DELETE /v3/projects/:id (teardown) 51 51 51 51 51 51 51 51
DELETE DELETE /v3/roles/:id (teardown) 126 126 126 126 126 126 126 126
DELETE DELETE /v3/users/:id (teardown) 74 74 74 120 120 120 120 120
DELETE DELETE /v4/mappings/rulesets/:mapping_id (teardown) 123 123 123 123 123 123 123 123
DELETE DELETE /v4/oauth2/:domain_id/clients/:provider_id (teardown) 79 79 79 79 79 79 79 79
GET 600 600 600 600 700 700 700 800
GET GET /SCIM/v2/:domain_id/Users (sync reconcile list) 6 8 9 11 15 21 470 988
GET GET /v3/auth/tokens (validate new) 500 600 600 600 600 684 684 684
GET GET /v3/auth/tokens (validate revoked) 490 500 500 500 600 600 600 600
GET GET /v3/projects/:id 500 600 600 600 600 600 668 668
GET GET /v3/projects/:id (catalog) 500 500 600 600 600 600 694 694
GET GET /v3/role_assignments 600 600 600 600 600 690 690 690
GET GET /v3/role_assignments?role.id 500 500 600 600 600 600 600 600
GET GET /v3/role_assignments?scope.domain.id 500 500 600 600 600 600 600 600
GET GET /v3/role_assignments?user.id 500 500 600 600 600 600 671 671
GET GET /v3/roles 500 600 600 600 600 600 700 700
GET GET /v3/roles/:id 500 500 600 600 600 600 600 600
GET GET /v3/users/:id 500 600 600 600 600 700 700 700
GET GET /v3/users/:id (catalog) 600 600 600 600 600 700 700 700
GET GET /v4/api-keys 600 600 600 600 686 686 686 686
GET GET /v4/api-keys/:client_id 600 600 600 600 600 600 659 659
GET GET /v4/mappings/rulesets 500 500 600 600 600 600 600 600
GET GET /v4/mappings/rulesets/:mapping_id 500 600 600 600 600 671 671 671
GET GET /v4/oauth2/:domain_id/.well-known/openid-configuration 44 50 59 70 86 98 130 159
GET GET /v4/oauth2/:domain_id/clients 500 600 600 600 600 600 600 600
GET GET /v4/oauth2/:domain_id/clients/:provider_id 500 500 600 600 600 700 700 700
GET GET /v4/oauth2/:domain_id/jwks 44 51 60 71 86 100 140 258
GET GET /v4/scim_realms 600 600 600 600 600 600 600 600
GET GET /v4/scim_realms/:domain_id/:provider_id 500 500 500 597 597 597 597 597
POST POST /v3/auth/tokens 500 500 500 595 595 595 595 595
POST POST /v3/auth/tokens (password, invalid) 900 1,000 1,000 1,000 1,000 1,000 1,000 1,000
POST POST /v3/auth/tokens (password, project-scoped) 1,000 1,000 1,000 1,000 1,730 1,730 1,730 1,730
POST POST /v3/auth/tokens (password, unscoped) 1,000 1,000 1,000 1,000 1,000 1,859 1,859 1,859
POST POST /v3/auth/tokens (rescope to system) 600 600 700 700 700 700 700 700
POST POST /v3/auth/tokens (rescope, invalid project) 500 500 500 600 600 600 658 658
POST POST /v3/auth/tokens (system-scoped, bootstrap admin) 1,000 1,000 1,000 1,000 1,697 1,697 1,697 1,697
POST POST /v4/api-keys/:client_id/revoke (teardown) 103 103 103 103 103 103 103 103
PUT PUT /v4/scim_realms/:domain_id/:provider_id 600 600 700 700 700 700 756 756
Aggregated 51 92 490 600 600 700 1,000 1,859

Status Code Metrics

Method Name Status Codes
DELETE DELETE /v3/auth/tokens 45 [204]
DELETE DELETE /v3/projects/:id (teardown) 1 [204]
DELETE DELETE /v3/roles/:id (teardown) 1 [204]
DELETE DELETE /v3/users/:id (teardown) 2 [204]
DELETE DELETE /v4/mappings/rulesets/:mapping_id (teardown) 1 [204]
DELETE DELETE /v4/oauth2/:domain_id/clients/:provider_id (teardown) 1 [204]
GET 617 [200]
GET GET /SCIM/v2/:domain_id/Users (sync reconcile list) 1,724 [429], 31 [401]
GET GET /v3/auth/tokens (validate new) 45 [200]
GET GET /v3/auth/tokens (validate revoked) 46 [404]
GET GET /v3/projects/:id 57 [200]
GET GET /v3/projects/:id (catalog) 83 [200]
GET GET /v3/role_assignments 42 [200]
GET GET /v3/role_assignments?role.id 44 [200]
GET GET /v3/role_assignments?scope.domain.id 43 [200]
GET GET /v3/role_assignments?user.id 43 [200]
GET GET /v3/roles 114 [200]
GET GET /v3/roles/:id 57 [200]
GET GET /v3/users/:id 110 [200]
GET GET /v3/users/:id (catalog) 107 [200]
GET GET /v4/api-keys 28 [200]
GET GET /v4/api-keys/:client_id 27 [200]
GET GET /v4/mappings/rulesets 29 [200]
GET GET /v4/mappings/rulesets/:mapping_id 28 [200]
GET GET /v4/oauth2/:domain_id/.well-known/openid-configuration 879 [200]
GET GET /v4/oauth2/:domain_id/clients 28 [200]
GET GET /v4/oauth2/:domain_id/clients/:provider_id 29 [200]
GET GET /v4/oauth2/:domain_id/jwks 878 [200]
GET GET /v4/scim_realms 18 [200]
GET GET /v4/scim_realms/:domain_id/:provider_id 19 [200]
POST POST /v3/auth/tokens 44 [201]
POST POST /v3/auth/tokens (password, invalid) 33 [401]
POST POST /v3/auth/tokens (password, project-scoped) 57 [201]
POST POST /v3/auth/tokens (password, unscoped) 54 [201]
POST POST /v3/auth/tokens (rescope to system) 100 [201]
POST POST /v3/auth/tokens (rescope, invalid project) 61 [401]
POST POST /v3/auth/tokens (system-scoped, bootstrap admin) 50 [201]
POST POST /v4/api-keys/:client_id/revoke (teardown) 1 [200]
PUT PUT /v4/scim_realms/:domain_id/:provider_id 18 [200]
Aggregated 3,344 [200], 51 [204], 125 [401], 1,724 [429], 46 [404], 305 [201]

Transaction Metrics

Transaction # Times Run # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
ReadHeavy
0.0 1 0 38.00 38 38 0.03 0.00
0.1 110 0 580.64 305 746 3.67 0.00
0.2 110 0 533.36 312 684 3.67 0.00
0.3 112 0 538.03 281 690 3.73 0.00
TokenLifecycle
1.0 0 0 0.00 0 0 0.00 0.00
1.1 46 0 2034.35 753 2333 1.53 0.00
ValidateToken
2.0 0 0 0.00 0 0 0.00 0.00
2.1 95 0 640.07 419 800 3.17 0.00
UserCRUD
3.0 0 0 0.00 0 0 0.00 0.00
3.1 0 0 0.00 0 0 0.00 0.00
3.2 110 0 555.70 398 748 3.67 0.00
3.3 2 0 97.00 74 120 0.07 0.00
ProjectCRUD
4.0 0 0 0.00 0 0 0.00 0.00
4.1 0 0 0.00 0 0 0.00 0.00
4.2 57 0 541.05 258 668 1.90 0.00
4.3 1 0 51.00 51 51 0.03 0.00
RoleCRUD
5.0 0 0 0.00 0 0 0.00 0.00
5.1 0 0 0.00 0 0 0.00 0.00
5.2 57 0 531.14 346 638 1.90 0.00
5.3 1 0 126.00 126 126 0.03 0.00
RoleList
6.0 0 0 0.00 0 0 0.00 0.00
6.1 114 0 534.66 311 703 3.80 0.00
RoleAssignmentList
7.0 0 0 0.00 0 0 0.00 0.00
7.1 42 0 547.88 397 690 1.40 0.00
7.2 43 0 518.19 414 671 1.43 0.00
7.3 43 0 520.09 380 629 1.43 0.00
7.4 44 0 528.36 410 642 1.47 0.00
UserRead
8.0 0 0 0.00 0 0 0.00 0.00
8.1 106 0 591.74 431 727 3.53 0.00
8.2 107 0 557.98 375 734 3.57 0.00
ProjectRead
9.0 0 0 0.00 0 0 0.00 0.00
9.1 84 0 561.30 416 688 2.80 0.00
9.2 83 0 532.66 345 694 2.77 0.00
PasswordAuth
10.0 54 0 1011.70 591 1859 1.80 0.00
10.1 57 0 1221.95 527 1730 1.90 0.00
PasswordAuthNegative
11.0 33 0 935.76 569 1454 1.10 0.00
Rescope
12.0 0 0 0.00 0 0 0.00 0.00
12.1 100 0 607.35 457 748 3.33 0.00
RescopeNegative
13.0 0 0 0.00 0 0 0.00 0.00
13.1 61 0 498.93 323 658 2.03 0.00
SystemScopeAuth
14.0 50 0 1236.30 488 1697 1.67 0.00
ApiKeyCRUD
15.0 0 0 0.00 0 0 0.00 0.00
15.1 0 0 0.00 0 0 0.00 0.00
15.2 27 0 556.15 407 659 0.90 0.00
15.3 28 0 561.25 389 686 0.93 0.00
15.4 1 0 103.00 103 103 0.03 0.00
MappingCRUD
16.0 0 0 0.00 0 0 0.00 0.00
16.1 0 0 0.00 0 0 0.00 0.00
16.2 28 0 549.36 409 671 0.93 0.00
16.3 29 0 528.79 383 648 0.97 0.00
16.4 1 0 123.00 123 123 0.03 0.00
OAuth2ClientCRUD
17.0 0 0 0.00 0 0 0.00 0.00
17.1 0 0 0.00 0 0 0.00 0.00
17.2 29 0 528.62 286 702 0.97 0.00
17.3 28 0 536.18 419 645 0.93 0.00
17.4 1 0 79.00 79 79 0.03 0.00
OAuth2Discovery
18.0 878 0 51.81 9 258 29.27 0.00
18.1 879 0 50.24 11 159 29.30 0.00
ScimRealmRead
19.0 0 0 0.00 0 0 0.00 0.00
19.1 19 0 520.89 428 597 0.63 0.00
19.2 18 0 542.00 404 622 0.60 0.00
19.3 18 0 588.28 369 756 0.60 0.00
ScimSync
20.0 0 0 0.00 0 0 0.00 0.00
20.1 0 0 0.00 0 0 0.00 0.00
20.2 0 0 0.00 0 0 0.00 0.00
20.3 1755 0 16.98 1 988 58.50 0.00
20.4 1754 0 0.00 0 0 58.47 0.00
20.5 1754 0 0.00 0 0 58.47 0.00
20.6 1 0 0.00 0 0 0.03 0.00
Aggregated 8971 0 153.06 1 2333 299.03 0.00

Scenario Metrics

Transaction # Users # Times Run Average (ms) Min (ms) Max (ms) Scenarios/s Iterations
ReadHeavy 6 108 1660.56 1456 1995 3.60 18.00
TokenLifecycle 3 43 2086.23 1802 2333 1.43 14.33
ValidateToken 2 93 640.45 419 800 3.10 46.50
UserCRUD 2 108 556.99 398 748 3.60 54.00
ProjectCRUD 1 56 546.11 378 668 1.87 56.00
RoleCRUD 1 56 532.23 346 638 1.87 56.00
RoleList 2 112 538.37 347 703 3.73 56.00
RoleAssignmentList 3 42 2118.24 1844 2385 1.40 14.00
UserRead 4 105 1155.72 964 1349 3.50 26.25
ProjectRead 3 81 1099.30 919 1260 2.70 27.00
PasswordAuth 4 53 2272.60 1478 3482 1.77 13.25
PasswordAuthNegative 1 32 939.06 569 1454 1.07 32.00
Rescope 2 98 608.37 457 748 3.27 49.00
RescopeNegative 1 60 500.37 323 658 2.00 60.00
SystemScopeAuth 2 48 1253.15 834 1697 1.60 24.00
ApiKeyCRUD 1 27 1122.78 891 1313 0.90 27.00
MappingCRUD 1 28 1075.54 907 1251 0.93 28.00
OAuth2ClientCRUD 1 28 1074.00 882 1303 0.93 28.00
OAuth2Discovery 3 877 102.60 27 302 29.23 292.33
ScimRealmRead 1 18 1656.39 1474 1824 0.60 18.00
ScimSync 1 1754 16.99 1 988 58.47 1754.00
Aggregated 45 3827 352.55 1 3482 127.57 2693.67

Error Metrics

Method Name # Error
GET GET /SCIM/v2/:domain_id/Users (sync reconcile list) 48 401 Unauthorized: GET /SCIM/v2/:domain_id/Users (sync reconcile list)
POST POST /SCIM/v2/:domain_id/Users (sync provision) 1 401 Unauthorized: POST /SCIM/v2/:domain_id/Users (sync provision)
POST POST /v3/auth/tokens (password, invalid) 78 401 Unauthorized: POST /v3/auth/tokens (password, invalid)
POST POST /v3/auth/tokens (rescope, invalid project) 152 401 Unauthorized: POST /v3/auth/tokens (rescope, invalid project)
GET GET /v3/auth/tokens (validate revoked) 154 404 Not Found: GET /v3/auth/tokens (validate revoked)
GET GET /SCIM/v2/:domain_id/Users (sync reconcile list) 1788 429 Too Many Requests: GET /SCIM/v2/:domain_id/Users (sync reconcile list)

View full report

@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Projectkeystone
Branchclaude/github-issue-1329-hvc1bi
Testbedubuntu-latest
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
nanoseconds (ns)
(Result Δ%)
Upper Boundary
nanoseconds (ns)
(Limit %)
Command_Serde/apply/remove📈 view plot
🚷 view threshold
126,530.00 ns
(-56.58%)Baseline: 291,440.62 ns
1,448,357.86 ns
(8.74%)
Command_Serde/apply/set📈 view plot
🚷 view threshold
141,450.00 ns
(-69.29%)Baseline: 460,542.28 ns
3,321,060.90 ns
(4.26%)
Command_Serde/pack/delete📈 view plot
🚷 view threshold
127.06 ns
(+9.15%)Baseline: 116.41 ns
158.91 ns
(79.96%)
Command_Serde/pack/delete_index📈 view plot
🚷 view threshold
111.97 ns
(+8.13%)Baseline: 103.55 ns
139.44 ns
(80.30%)
Command_Serde/pack/set📈 view plot
🚷 view threshold
209.34 ns
(+10.51%)Baseline: 189.43 ns
253.54 ns
(82.57%)
Command_Serde/pack/set_index📈 view plot
🚷 view threshold
112.03 ns
(+8.19%)Baseline: 103.55 ns
139.99 ns
(80.02%)
Command_Serde/unpack/delete📈 view plot
🚷 view threshold
188.63 ns
(+3.08%)Baseline: 183.00 ns
270.92 ns
(69.63%)
Command_Serde/unpack/delete_index📈 view plot
🚷 view threshold
158.05 ns
(+3.69%)Baseline: 152.42 ns
219.60 ns
(71.97%)
Command_Serde/unpack/set📈 view plot
🚷 view threshold
309.30 ns
(+18.14%)Baseline: 261.82 ns
375.22 ns
(82.43%)
Command_Serde/unpack/set_index📈 view plot
🚷 view threshold
157.01 ns
(+4.37%)Baseline: 150.43 ns
215.09 ns
(73.00%)
Payload_encryption/pack/remove_cmd📈 view plot
🚷 view threshold
124.03 ns
(+13.48%)Baseline: 109.30 ns
151.95 ns
(81.62%)
Payload_encryption/pack/set_cmd📈 view plot
🚷 view threshold
208.27 ns
(+5.17%)Baseline: 198.03 ns
274.32 ns
(75.92%)
Payload_encryption/unpack/remove_cmd📈 view plot
🚷 view threshold
201.56 ns
(+5.32%)Baseline: 191.38 ns
284.65 ns
(70.81%)
Payload_encryption/unpack/set_cmd📈 view plot
🚷 view threshold
303.74 ns
(+12.24%)Baseline: 270.61 ns
390.67 ns
(77.75%)
Raft_1Node_Latency/prefix/1node📈 view plot
🚷 view threshold
3,469,300.00 ns
(+12.36%)Baseline: 3,087,777.33 ns
8,510,044.82 ns
(40.77%)
Raft_1Node_Latency/read/1node📈 view plot
🚷 view threshold
42,841.00 ns
(+14.95%)Baseline: 37,269.47 ns
53,010.19 ns
(80.82%)
Raft_1Node_Latency/remove/1node📈 view plot
🚷 view threshold
359,300.00 ns
(-70.56%)Baseline: 1,220,650.31 ns
11,552,389.31 ns
(3.11%)
Raft_1Node_Latency/write/1node📈 view plot
🚷 view threshold
375,560.00 ns
(-62.40%)Baseline: 998,917.03 ns
7,587,887.21 ns
(4.95%)
build_snapshot/default📈 view plot
🚷 view threshold
112,550.00 ns
(-7.83%)Baseline: 122,106.27 ns
267,279.45 ns
(42.11%)
fernet token/project📈 view plot
🚷 view threshold
1,447.30 ns
(+3.74%)Baseline: 1,395.15 ns
1,829.28 ns
(79.12%)
get_data_keyspace📈 view plot
🚷 view threshold
0.32 ns
(+4.23%)Baseline: 0.31 ns
0.39 ns
(83.07%)
get_db📈 view plot
🚷 view threshold
0.33 ns
(+5.48%)Baseline: 0.31 ns
0.39 ns
(84.06%)
get_fernet_token_timestamp/project📈 view plot
🚷 view threshold
155.73 ns
(+12.55%)Baseline: 138.37 ns
183.99 ns
(84.64%)
get_keyspace📈 view plot
🚷 view threshold
4.36 ns
(-24.81%)Baseline: 5.80 ns
12.70 ns
(34.33%)
🐰 View full continuous benchmarking report in Bencher

full_snapshot() awaited the client-streaming Snapshot RPC's response
before sending anything into the channel backing its request stream.
The server (raft_service.rs) only replies after it has read the
entire request stream, so nothing ever fed that channel and every
snapshot install deadlocked silently. A learner added after the
leader's log was purged therefore never caught up: RaftMetrics.
last_log_index stayed None forever, with no error logged anywhere
(GitHub issue #1329).

The whole snapshot already lives in memory by this point, so build
the full request stream eagerly with stream::iter instead of a
channel fed after the await returns. This removes the self-deadlock
entirely; no separate feeder task is needed.

Add a regression test that snapshots and purges a single-node
leader's log, then adds a learner and asserts it catches up via
InstallSnapshot, exercising the path GitHub issue #1293's own
"purge, join, assert reads" integration test needs and was blocked
on.

Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
@gtema
gtema force-pushed the claude/github-issue-1329-hvc1bi branch from 357a4da to 1a245e5 Compare September 19, 2026 17:48
@gtema
gtema added this pull request to the merge queue Sep 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 20, 2026
@gtema
gtema added this pull request to the merge queue Sep 20, 2026

gtema commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

The merge-queue run was dequeued for CI_FAILURE. Looked at the failing "CI" run: the Test (x86_64-unknown-linux-gnu) job failed at the Run REST API SCIMv2 tests step, specifically in the tools/start-api.sh nextest setup script — it printed Keystone server is started and running in the background! then 12s later Server failed to start. and timed out (SETUP FAIL [16.260s]).

This isn't this PR's diff: it only touches crates/storage/src/network.rs (the raft peer-to-peer gRPC client) and a raft-only integration test, neither exercised by the SQL-backed test_api/SCIMv2 suite. The preceding Run REST API tests step in the same job (which also boots a live keystone server) passed a few seconds earlier, and this exact job (Test (x86_64-unknown-linux-gnu)) already passed on this same commit when CI ran directly against the PR branch (not through the merge queue) — see the green run. The pattern (old server process killed, new one started immediately after, health check timing out on a fresh bind) looks like a runner-timing flake rather than a real regression.

I've kicked off a re-run of the failed job to confirm before asking for a re-queue: https://github.com/openstack-experimental/keystone/actions/runs/35493140847


Generated by Claude Code

Merged via the queue into main with commit b4db875 Sep 20, 2026
37 checks passed
@gtema
gtema deleted the claude/github-issue-1329-hvc1bi branch September 20, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

raft: leader never resumes replicating to a newly added learner once its log has been purged

1 participant