Repository navigation
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueTip 🎯 Customize your risk assessments
✅ Automatic approvals, with your rules
|
|
2db232e to
ceb64e1
Compare
The sync contract in #1341 says threads that make their first call at once build one engine client and load the keyset once. The request counter could not show that: it counted the keyset load and the key requests together. The counting connection now also counts the load-keyset requests that succeed, read by _engine.keyset_loads(). A failed load caches nothing and is not counted. New live tests: - 8 threads start their first encrypt together, in a new process: one client is built, and the keyset loads once. - 8 threads each encrypt 100 models, and one decrypt opens all 800. - A SIGINT during a 5000-value encrypt, in a new process: one KeyboardInterrupt, after the call returns, with all rows or none, and the next encrypt works. The concurrency test also runs on the deterministic build, so it runs on every pull request with no credentials. The README now describes the contract: synchronous calls, the GIL released, one client per process, and Ctrl-C after the call returns. It also says that requests within one call go out in sequence, that each request times out after 10 seconds, and that a Cipher is made once per model, not once per web request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1394) The interrupt test passed a call cut short by the signal. It now times a reference encrypt first, sends SIGINT a quarter of the way into a second one, and requires the interrupt after half the reference. A cut-short call raises at the quarter. A reference under 0.2 seconds fails, since the signal would have no time to land in the call. The model, the threads, and the interrupt helpers are now one module, tests/_sync_contract.py. The live and deterministic concurrency tests, the two child scripts, and the gate test use it. The child scripts are files, not strings. The half-second loop that waited for a second interrupt is gone: the code after the interrupt gives Python many checks, and a second one fails the process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The keyset load counter was a second hand-written counter beside the request counter, with its own match in the connection and its own function in core, in the extension and in the stub. The connection now keeps one map from endpoint to count, read by _engine.requests(). request_count() is its sum, and the keyset loads are its "load-keyset" entry. A request counts when it is sent, whether or not it succeeds. The tests of a refused call rely on that: a request sent by mistake fails with test credentials, and a count of successes would miss it. The two-Cipher test now checks that the second Cipher adds no load, so a load retried in an earlier test does not fail it. The interrupt test now times the fastest of three reference calls, and doubles the values from 5000 until a call takes 0.4 seconds, up to 80,000. A fast link gets a longer call, not a failure. pytest now puts tests/ on the path, so the deterministic test imports the shared helpers with no path code. The poisoned-lock recovery in client.rs is one helper, locked(), in place of four copies. The README now names both timeouts: 10 seconds for each ZeroKMS request, and 30 seconds for each access token request. It says when a token request is made: on the first call when no fresh token is stored, and shortly before the token expires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ceb64e1 to
d3de32e
Compare
Summary
The Python SDK's thread and interrupt rules are now tested through the public API. Several threads can use one
Cipherat once, and they share one connection to CipherStash. A Ctrl-C during a call waits for the call to finish, and theCipherstill works afterwards. The README now describes these rules for users.Changes
_engine.requests()returns the counts, for example{"load-keyset": 1, "retrieve-key": 3}._engine.request_count()is now their sum.tests/_sync_contract.pyhas the test model, the thread runner, and the interrupt helpers.tests/on the path. So the deterministic tests can import the module.tests/_first_call.pyruns in a new Python process. Its 8 threads start their firstencrypttogether, behind athreading.Barrier. The live test checks that the process built one client, and loaded the keyset once.encryptcalls on oneCipher. Onedecryptthen opens all 800 rows, and each row must equal its model.tests/test_concurrency_live.py.tests_deterministic/test_concurrency.py. So it runs on every pull request, with no credentials.encrypt:tests/_interrupt.pyruns in a new Python process.KeyboardInterruptarrives, after at least half the reference time. A call cut short would raise at the quarter.encryptworks.Cipherinstances: the existing live test now also checks that the secondCipheradds no keyset load.Cipheruses it.Cipherfor each model, not one for each web request.client.rs: one helper,locked(), now recovers a poisoned lock, in place of four copies.Verification
mise run test:cargo: all core and extension tests pass.cargo fmt --check: no findings, with and without all features.gate-probetests.mise run test:deterministic: 19 passed, with the new concurrency test.gate-probebuild: 2 passed, with the shared helpers.livejob on this PR is their first run._sync_contractin the deterministic test. pyright does not read pytest'spythonpathsetting. pytest itself imports it correctly.Related
Closes #1394. Refs #1341.
Review notes
tests/_sync_contract.py, then the two child scripts.mise run test:liveanyway. Only a ZeroKMS on the same machine is that fast.🤖 Generated with Claude Code